swift进阶_kaifa进阶 swift - CSDN
精华内容
参与话题
  • Swift4 进阶

    2020-07-29 14:19:33
    objc-中国系列书籍,全部的书籍,高清,程序员可以看看
  • Swift学习进阶

    千次阅读 2019-08-16 11:34:15
    青铜选手 【AR扫任意福字得福卡】:活动时间2019年1月25日00:00-2月4日22:00,每日限得2张福。 【为福气林浇水得福卡】:活动时间2019年1月25日00:00-2月4日22:00,每日限得2张福。 【答答星球答题得福卡】:...

    1.print(str)

    • print函数能够将表达式的结果输出到控制台,类似C的printf函数和OC的NSLog函数。
    • print函数定义:
    public func print(_ items: Any..., //任意个数,任何类型的参数它们将输出到控制台
     separator: String = default,//输出多个参数之间的分隔符
      terminator: String = default)//输出字符串之后的结束符号
    

    参数separatorterminator都可以省略,separator只有字输出数据大于1时才有意义。

    • 示例:
    print("a","b","c","d","e", separator: "|", terminator: "-")
    

    打印结果为a|b|c|d|e-,用|将打印的字母分开,用-来结束打印。

    • 自定义打印日志:
    //自定义log p28
    func hjLog(mes:Any){
        print("file:\(#file) column:\(#column)  line:\(#line) \(mes)")
    }
    

    由于Swift中不能定义宏,只能定义一个打印日志的方法,上面方法打印文件路径,行数,及要打印的内容,可以方便的定位到打印的位置。

    2.浮点型取余

    • Swift中允许浮点型取余,但是在Swift3之后取余运算符%不能应用于浮点数运算,需要使用public func truncatingRemainder(dividingBy other: Double) -> Double方法来计算浮点型取余。
    var c = 19.22
    //浮点型取余 
    let d = c.truncatingRemainder(dividingBy: 6.1)
    

    3.Swift中的数据类型

    • 根据这些类型在赋值或给函数传递时的方式,可分为值类型引用类型值类型就是创建一个副本,把副本赋值或传递过去,这样在函数的调用过程中不会影响原始数据;引用类型就是把数据本身的引用(即指针)赋值或传递过去在函数的调用过程中会影响原始数据。
    • Swift中整型、浮点型、布尔型、字符型、字符串、元组、集合、枚举。结构体值类型,而类属于引用类型。如Swift中String是值类型、NSString是引用类型。
    • 整型、浮点型、布尔型、字符型、字符串、元组、集合等类型本质上都是结构体类型,结构体有构造函数,通过构造函数创建并初始化实例。
    • 用于判断的几种方式:
      1. ==比较基本类型的值是否相等
      2. ===比较对象类型,是不是同一个对象
      3. is判断某个实例是否为某种类型
      4. as强制类型转换

    4.Swift中的for循环

    • Swift3之后C语言风格的for语句不再使用, Swift3之后for语句只能与in关键字结合使用。
    • 示例1:只需要知道连续区间的值(只需要循环变量)
    //打印1到9平方的值
    for i in 1..<10{
        print("\(i)x\(i)=\(i*i)")
    }
    
    • 示例2:只需要连续获取集合元素的值(不需要循环变量)
    //打印集合元素
    let nums = [1,2,3,4,5,6,7,8,9]
    for item in nums {
        print(item)
    }
    
    • 示例3:既需要集合元素的值,又需要脚标(需要循环变量),可以使用结合的enumerated()方法
    let nums = [1,2,3,4,5,6,7,8,9]
    for(index,item) in nums.enumerated(){
        print("\(index):\(item)")
    }
    
    • Swift中的区间分为两种,全闭合区间...和左闭右开区间..<

    5. break语句

    • break语句可用于循环语句结构,(Swift中的循环语句有while、repeat-while、for ),作用是强制退出循环结构,不执行循环结构中的剩余语句。由于Swift的选择switch默认会添加break,所以一般不需要收到添加break,但添加上也不会出问题。
    for i in 1...10{
        print("\(i)x\(i)=\(i*i)")
        if i == 5{
            break;
        }
    }
    
    • break可以配合标签使用
    label1: for x in 0..<5 {
        label2: for y in (1...5).reversed(){
            if x == y {
                break label1
            }
            print("(x,y)=(\(x),\(y))")
        }
    }
    

    默认情况下break只会跳出最近的内循环,而示例中条件成立时直接跳出循环,给外循环添加了一个标签label1,然后break后面指定这个标签,当条件成立就跳出该标签的外循环。reversed()是反向变量区间。

    6.Set集合

    • Swift集合有数组、字典、Set集合数组是一组有序的由相同类型元素构成的集合;字典由两部分组成,一个键集合,一个值集合,键集合不能有重复元素,而值集合可以重复,键值成对出现;Set集合是由一串无序的,不能重复的相同类型元素构成的集合。
    • 与OC任何对象类型不同,三种集合都强调是元素的类型一致,但这里的类型指的是泛型。如果想包含任何类型可指定为Any
    //正规声明
    var arr : Array<Any> = [1,2,"3",1.22]
    print(arr[1])
    
    //简写声明
    let dic:[AnyHashable : Any] = [1:2,2:3,"3":4,4:"5"]
    //字典的key和value都为任何类型,有可能是可选类型,获取元素值时要解包
    print(dic[1]!)
    
    let set:Set<String> = ["1","12","123"]
    print(set.first!)
    
    • Array和Dictionary有简写的声明,而Set集合没有简写的声明。因为Set和Array唯一的区别是无序且不重复(当不考虑叙述而且没有重复的元素时,二者可互相替换),如果简写了就无法区分是Set还是Array了。
    • 三种集合强调的点不同, Array强调有序;Set强调不重复(无序);字典强调key唯一(不能重复),通过key取值,也是无序的。
    • Set的一般操作:
    let set:Set<String> = ["1","123","12","12","1234"]
    print("第一个元素\(set.first!)")
    print("元素个数\(set.count)")
    var set2:Set<String> = ["1234","1","123","12"]
    if set == set2{
        print("set等于set2")
    }
    //插入一个元素
    set2.insert("插入")
    //删除某个元素
    let item = "1"
    set2.remove(item)
    print(set2)
    //删除一个元素,这里并不是第一个元素,而是随机的
    set2.removeFirst()
    print(set2)
    //判断是否包含某个元素
    if set.contains("1234"){
        print("set有该元素")
    }
    

    Set一般操作
    结果可以看出Set的first方法获取的并不一定是第一个元素,而是随机的。多个重复的元素在Set中只算一个,从count可以看出。

    • Set集合遍历
    for item in set{
        print(item)
    }
    
    for (index,item) in set.enumerated(){
        print("\(index+1):\(item)")
    }
    

    Set遍历
    注意:Set的enumerated()方法可以取出Set的索引和元素, (index,item)是元组类型。这里的index是循环遍量,可以表示循环次数,而不是元素的序号脚标。

    • Set集合间的运算,首先了解一下几个概念(A,B是两个Set集合):
    1. 交集:属于A且属于B的元素集合
    2. 并集:属于A或属于B的元素集合
    3. 异或集合:A与B的并集元素集合去掉A与B的交集集合中的元素后剩下元素的集合
    4. 差集:属于A而不属于B的元素集合称A与B的差集。(A与B的并集去掉B中所有元素后的集合)
    5. 子集:B中所有元素都A的元素,那么久称B是A的子集。但是Set集合运算过程中不涉及Set子集概念。
    let A:Set<String> = ["a","b","c","d"]
    let B:Set<String> = ["c","d","e","f"]
    print("A与B的交集 = \(A.intersection(B))")
    print("A与B的并集 = \(A.union(B))")
    print("A与B异或集合 = \(A.symmetricDifference(B))")
    let C = A.subtracting(B)
    print("A与B差集 = \(C)")
    if C.isSubset(of: A){
       print("C是A的子集")
    }
    

    Set集合间的操作

    7.函数

    7.1 .Swift 函数参数
    • Swift中的函数参数很灵活,具体体现在传递参数有多种形式。
    • 我们可以为每个参数提供标签,为调用者说明参数的含义,这些变迁命名应该唯一,并且有意义:
       func rectangleArea(W width:Double,H height:Double) ->Double{
       return width*height
    }
    print(rectangleArea(W: 10.0, H: 10.0))
    

    W,H就是参数标签,外部调用时会提示使用。

    • 如果定义函数没有声明标签,原则上也是可以的
    func rectangleArea2(width:Double,height:Double) ->Double{
      return width*height
    }
    
    print(rectangleArea2(width: 10.0, height: 10.0))
    
    • 省略参数标签,在Swift3以后,调用函数时要求指定所有参数的标签,除非函数定义是使用下划线_关键字声明的标签。
    func rectangleArea3(_ width:Double,_ height:Double) ->Double{
      return width*height
    }
    print(rectangleArea3(10,10))
    
    • 给参数设默认值,当调用时可以忽略该参数,调用时如果没传值就取默认值,如果赋值了就会覆盖掉默认值
    func test(a:Int = 10,b:Int) -> Int{
      return a+b
    }
    print(test(b: 20)) //30
    print(test(a: 1, b: 2)) //3
    
    • 可变参数:参数个数可以变化,调用时可以接受不确定数量的参数,这些参数具有相同的类型,有点像传入了一个数组
    func sum(nums:Int...)->Int{
      var total = 0
      for num in nums{
       total += num
      }
      return total
    }
    print(sum(nums: 1,2,3))
    print(sum(nums: 1,2,3,4,5,6,8))
    

    sum函数是用来求多个整型的函数,参数nums:Int…是Int类型的可变参数,在函数体重nums被认为是一个Double类型的数组

    • 类型参数的引用传递:除了类是引用类型,其他如整型…都是值类型,但有时候我们想在函数内部改变函数外面参数的值,这样就需要将值类型参数以引用类型方式传递。
    func increment(value: inout Double,auto:Double = 1.0){
       value += auto
    }
    var value:Double = 10.0
    print(value)
    increment(value: &value)
    print(value) //11.0
    

    参数value是需要增长的数值,它被设计为inout类型,inout修饰的参数称为输入输出参数,value必须是变量不能是let修饰的常量。

    7.2. Swift函数返回值
    • 函数返回值分为无返回值和有返回值,无返回值其类型是void,可以省略不写;有返回值又分为单个返回值,和多个返回值,单个返回值就是返回一种类型,多个返回值可以返回多个类型,将这些不同类型的返回值放到元组中返回就可以了。
    7.3. Swift函数类型
    • 每个函数都有一个类型,使用函数类型与使用其他数据类型一样,可以声明变量或常量,也可以作为其他函数参数或返回值使用。
    • 用函数类型声明常量或变量
    func rectangleArea(width: Double,height:Double) -> Double {
        return width * height
    }
    let rectangleArea1: (Double,Double)->Double = rectangleArea(width:height:)
    print(rectangleArea1(10,10)) //100.0
    
    var rectangleArea2: (Double,Double)->Double = rectangleArea(width:height:)
    rectangleArea2(20,20)
    rectangleArea2 = rectangleArea(width:height:)
    print(rectangleArea2(30,30)) //900.0
    
    • 用函数类型作为函数返回类型使用
    //计算矩形面积
    func rectangleArea(width: Double,height:Double) -> Double {
        return width * height
    }
    //计算三角形面积
    func triangleArea(width: Double,height:Double) -> Double {
        return width * height/2.0
    }
    
    //作为函数返回类型使用
    func getArea(type:String) -> (Double,Double)->Double {
        var returnFunc: (Double,Double)->Double
        switch type {
        case "矩形":
         returnFunc = rectangleArea
        default://三角形
          returnFunc = triangleArea
        }
        return returnFunc
    }
    let rectangleFunc = getArea(type: "矩形")
    print("矩形面积:\(rectangleFunc(20,20))")  //矩形面积:400.0
    let triangleFunc = getArea(type: "三角形")
    print("三角形面积:\(triangleFunc(20,20))") //三角形面积:200.0
    

    getArea返回值类型为函数类型,常量rectangleFunctriangleFunc只是接收了getArea函数的返回值,是将计算矩形面积和三角形面积的函数真正声明了,rectangleFunc(20,20)和triangleFunc(20,20)才是对计算面积的函数的真正调用。总之就是getArea函数调用是为了声明rectangleAreatriangleArea,返回的函数常量或变量的调用才是真正用于计算面积的,

    • 用函数类型做为函数参数类型使用
    //计算矩形面积
    func rectangleArea(width: Double,height:Double) -> Double {
        return width * height
    }
    //计算三角形面积
    func triangleArea(width: Double,height:Double) -> Double {
        return width * height/2.0
    }
    //作为参数类型使用
    func getAreabByFunc(funcName:(Double,Double)->Double,a:Double,b:Double)->Double {
        let area = funcName(a,b) //对传进来函数参数的真正调用
        return area;
    }
    print("矩形面积:\(getAreabByFunc(funcName: rectangleArea, a: 10, b: 10))")  //矩形面积:100.0
    print("三角形面积:\(getAreabByFunc(funcName: triangleFunc, a: 10, b: 10))") //三角形面积:50.0
    

    getAreabByFunc调用值只需要将函数名传进来就可以,传进来的函数参数直接在里面调用。
    7.4. Swift嵌套函数

    • 将函数定义在另外的函数体重,称为嵌套函数
    //嵌套函数
    func calculate(opr:String) -> (Int,Int) -> Int {
        //定义加函数
        func add(a:Int,b:Int) -> Int{
            return a + b
        }
        //定义减函数
        func sub(a:Int,b:Int) -> Int{
            return a - b
        }
        var result: (Int,Int) -> Int
        switch opr {
        case "+":
            result = add
        case "-":
            result = sub
        default:
            result = add
        }
        return result
    }
    let addfunc = calculate(opr: "+") // 声明加函数
    print("5+5 = \(addfunc(5,5))") //5+5 = 10
    let subfunc = calculate(opr: "-")// 声明减函数
    print("5-5 = \(subfunc(5,5))")//5-5 = 0
    

    嵌套函数的作用域在外函数体内,但我们可以定义外函数的返回值类型为嵌套函数类型,从而将嵌套函数出啊递给外函数,被其调用者调用。

    8.运算符重载

    • Swift中除了class类型是引用类型,其他整型,浮点型,数组,结构体等都是值类型,对于引用类型通常用===!===来判断是不是同一个对象;对于值类型通常用==!=来判断两个值是否相等。
    • 示例:自定义一个机构体,判断两个实例是否相等。
    struct Student {
        var name = ""
        var no = 0
        var age = 0
    }
    
    var stu1 = Student()
    stu1.name = "张三"
    stu1.no = 1
    stu1.age = 18
    
    var stu2 = Student()
    stu2.name = "张三"
    stu2.no = 1
    stu2.age = 18
    
    if stu1 == stu2{
        print("是同一个学生")
    }else{
        print("不是同一个学生")
    }
    

    在这里插入图片描述
    可以发现在运行时报错了,报错说明为==不能用于两个Student结构体实例操作。说了stu1和stu2不能用于比较。我们需要在这些类型中重载==!=运算符号。即定义相等规则。

    struct Student {
        var name = ""
        var no = 0
        var age = 0
    }
    //定义重载==号运算符符
    func == (lsh:Student,rhs:Student) -> Bool {
        return lsh.name == rhs.name && lsh.no == rhs.no && lsh.age == rhs.age
    }
    //定义重载!=号运算符符
    func != (lsh:Student,rhs:Student) -> Bool {
        return (lsh.name != rhs.name || lsh.no != rhs.no || lsh.age != rhs.age)
    }
    
    var stu1 = Student()
    stu1.name = "张三"
    stu1.no = 1
    stu1.age = 18
    
    var stu2 = Student()
    stu2.name = "张三"
    stu2.no = 1
    stu2.age = 18
    
    if stu1 == stu2{
        print("是同一个学生")
    }else{
        print("不是同一个学生")
    }
    

    在这里插入图片描述

    9.类型嵌套

    • Swift中的类、结构体和枚举可以进行嵌套。优点是支持访问它外部的成员(包括方法、属性和其他嵌套类型),嵌套可以有多个层次。
    class Employee {
        var name = ""
        var no = 0
        var job = ""
        var day = WeekDays.Friday
        var dept = Department()
        
        struct Department {
            var no = 10
            var name = "Sales"
        }
        enum WeekDays{
            case Monday,Tuesday,Wednesday,Thursday,Friday
            
             struct Day {
                static var mes = "Today is ..."
            }
        }
        
        
    }
    let emplo = Employee()
    print(emplo.day)
    print(emplo.dept.name)
    print(Employee.WeekDays.Day.mes)
    

    10.类和结构体的异同。

    • 相同点:
      1. 定义存储属性;
      2. 定义方法;
      3. 定义下标;
      4. 定义构造函数;
      5. 定义扩展;
      6. 实现协议
    • 不同点:(只有类才有的功能)
      1. 能够继承另外一个类;
      2. 能够核对运行时对象的类型;
      3. 析构对象释放资源;
      4. 引用计数允许一个实例有多个引用。
    • 选择的原则:结构体是值类型,每一个实例没有独一无二的标识,而类是引用类型,每一个实例都是独一无二的标识。
    class Employee{ //员工类
      var no = ""
      var name = ""
      var dept: Department?
      
    }
    struct Department{//部门结构体
      var no = ""
      var name = ""
    }
    

    上面示例可以看出,由于员工的编号都是独一无二,每个员工是独立的个体,所以员工可以声明成类Employee;如果具有相同部门标号和部门名称,我们就认为是它们是相同的部门,所以就可以把部门设计为机构体Department。

    11.属性与下标。

    • Swift中的属性分为存储属性计算属性。存储属性就是oc中的数据成员,计算属性不存储数据,但可以通过计算其他属性返回数据。
    class Employee{
      let no = 0
      var firstName = "Tony"
      var lastName = "Guan"
      lazy var dept: Department = Department() //延迟存储属性
      var fullName: String{  //计算属性
          get{
           return firstName + "." + lastName
              
          }
          set(newFullName){
              let names = newFullName.components(separatedBy: ".")
              firstName = names.first!
              lastName = names.last!
          }
    //        set{
    //            let names = newValue.components(separatedBy: ".")
    //            firstName = names.first!
    //            lastName = names.last!
    //        }
      }
      
    }
    struct Department{
      var no = ""
      var name = ""
    }
    let emp = Employee()
    //emp.no = 10;编译会报错 ,常量属性不允许被修改。
    print(emp.fullName)
    emp.fullName = "Jack.Ma"
    print(emp.fullName)
    let dept = Department()
    

    上面类Employee中的no、firstName、lastName、dept都是存储属性,fullName是计算属性,其中dept是延迟存储属性。

    • 延迟存储属性:存储属性前面加上lazy关键字声明,就是延迟存储属性,只在第一次访问时加载,如果不访问就不会创建,这样可以减少内存占用,注意存储属性没有延迟加载一说,添加到存储属性前面会报错的。
    • 计算属性:计算属性本身不存储数据,而是从其他属性计算得到数据。计算属性提供一个Getter(取值访问器)来获取值,以及一个可选的(可以不实现set方法)Settter(设置访问器)来间接设置其他属性或变量的值,语法如下。
    面向对象的类型(classstructenum) 类型名{
    存储属性
    var 计算属性名: 属性数据类型{
     get{
     return 计算后的语句值
     }
     set(新属性值){
     语句组
    }
    }
    }
    

    其中新属性值是要赋值给属性值的,当然也可以不写,Swift提供了一个默认的变量newValue去接收新传入的值。上面set(newFullName)可以省略如下:

      set{
               let names = newValue.components(separatedBy: ".")
               firstName = names.first!
               lastName = names.last!
           }
    
    • 只读计算属性:计算属性只有Getter访问器,没有Setter访问器,只能取值而不能赋值。(只读存储属性就是let修饰的存储属性)
    class Employee{
       let no = 0
       var firstName = "Tony"
       var lastName = "Guan"
       lazy var dept: Department = Department()
       var fullName: String{
           get{
            return firstName + "." + lastName
               
           }
       }
    }
    let emp = Employee()
    //emp.no = 10;
    print(emp.fullName)
    // emp.fullName = "Jack.Ma" //不能赋值
    

    fullName就是只读计算属性,不能给其赋值,结构体,枚举的计算属性也是类似,这里不再赘述。只读属性可以简化去掉get关键字和括号,上面只读属性可以简化为:

       var fullName: String {
            return firstName + "." + lastName   
           }
    
    • 存储属性观察者:Swift的属性观察者有两个,willSet:观察者在修改之前调用,didSet观察者在修改之后调用 语法格式如下。
    面向对象类型(class/struct) 类型名{
       ...
       var 存储属性值: 属性数据类型 = 初始值{
           willSet(新值){
               ...
           }
           didSet(旧值){
               ...
           }
       }
    }
    

    示例

    class Employee1{
        let no = 0
        var name = "Tony"{
            willSet(newName){
                print("员工新名字:\(newName)")
            }
            didSet(oldName){
                print("员工旧名字:\(oldName)")
            }
        }
    }
    struct Department1{
        var no = 1{
            willSet{
                print("部门新编号:\(newValue)")
            }
            didSet{
                print("部门旧编号:\(oldValue)")
            }
        }
        var name = "移动开发部"
    }
    
    let emp1 = Employee1()
    emp1.name = "Jack"
    //结构体是值类型,必须用声明为变量才能修改其属性
    var dept1 = Department1()
    dept1.no = 10
    

    Employee1类中给name存储属性添加了观察者, willSet(newName)中的newName是要传进来的新值,didSet(oldName)中的oldName是新值传进来之前的旧值;参数的声明可以省略。Department1结构体中的no就省略了观察者参数,Swift提供了对应默认参数,新值默认是newValue,旧值默认是oldValue。这里需要说明下枚举只有计算属性没有存储属性,所以枚举不支持属性观察者

    • 静态属性:在属性前面加static关键字,类中也可以加class关键字,这样的属性称为静态属性。类,结构体,枚举都可以定义静态属性(也包括静态存储属性和静态计算属性)。这里需要注意的是枚举中没有实例存储属性但是可以有静态存储属性
    struct Account {
        var amount = 0.0 //账户金额
        var ower = "" //账户名
        static let monthRate = 0.00688 //月利率
        static var yearRate : Double{  //计算存储属性不能用let修饰,及不能是常量
            return monthRate * 12
        }
        var owerInterest:Double{ //年利息
            return Account.yearRate * amount
        }
    }
    //访问静态属性
    print(Account.yearRate)
    var account = Account()
    //访问实例属性
    account.amount = 100000.0
    print(account.owerInterest)
    
    
    class Account {
        var amount = 0.0 //账户金额
        var ower = "" //账户名
        static let monthRate = 0.00688 //月利率
        class var yearRate : Double{  //class换成static子类就不能重写该属性
            return monthRate * 12
        }
        var owerInterest:Double{ //年利息
            return Account.yearRate * amount
        }
       static var test:Int = 10{ //静态属性也支持添加属性监听者
            willSet{
                print(newValue)
            }
            didSet{
                print(oldValue)
            }
        }
    }
    //访问静态属性
    print(Account.yearRate)
    var account = Account()
    //访问实例属性
    account.amount = 100000.0
    print(account.owerInterest)
    
    class Account2:Account{
        override class var yearRate : Double{  //class换成static子类就不能重写该属性
            return monthRate * 12 * 1.1
        }
    }
    

    这里枚举与结构体类似不再举例,类中static修饰的属性为类的静态属性,class修饰的属性称为类的类属性,区别是类的类属性可以被子类重写,但是class不能修饰存储属性,static却可以

    • 实例属性和静态属性总结
      1. 类、结构体,枚举都支持静态存储属性、静态计算属性,实例计算属性;但是只有类和结构体支持实例存储属性,枚举不支持实例存储属性。
      2. 基于第一条可知只有类和结构体支持添加属性观察者(因为只有存储属性才能添加属性观察者)
      3. 延迟属性只能是延迟存储属性
      4. 计算属性必须是var声明的
      5. let修饰的存储属性,前可以加static称为静态存储属性,不能加class
      6. class修饰的属性可以被重写,但static修饰的属性不允许被重写。
      7. class只能修饰计算属性。
    • 下标:Swift中,我们可以定义一些集合类型,,它们可以回有一些集合类型的存储属性,这些属性可通过下标访问,其如法格式如下。
    面向对象类型(class/struct/enum) 类型名{
       ...
       subscript (参数:参数数据类型)->返回值类型{
           get{
               return 返回值
           }
           set(新属性值){
               ...
           }
       }
    }
    

    Swift中没有提供二维数组,但是我们可以通过下标自定义一个二维数组。

    struct DoubleDimensionalArray {
       let rows:Int,colums:Int
       var grid: [Int]
       init(rows:Int,colums:Int) {
           self.rows = rows //行数
           self.colums = colums //列数
           grid = Array(repeating: 0, count: rows * colums) //初始化数组都为0
       }
       subscript(row:Int,col:Int) -> Int{
           get{
               return grid[row * colums + col] //返回对应的脚标取出二维数组的值
           }
           set{
               grid[row * colums + col] = newValue //通过脚标给二维数组赋值
           }
       }
    }
    //初始化一个10行10列的二维数组
    var arr = DoubleDimensionalArray(rows: 10, colums: 10)
    for i in 0..<10 {
       for j in 0..<10{
           arr[i,j] = i*j //通过脚标给二维数组赋值为脚标之和
       }
    }
    
    for i in 0..<10 {
       for j in 0..<10{
           print("\t \(arr[i,j])",terminator: " ") //通过脚标获取二维数组中的值
       }
       print("\n")
    }
    

    Swift自定义二维数组

    11.方法。

    • Swift中,方法是在类,结构体,枚举中定义的函数,分为实例方法和静态方法.
    • 方法和函数的区别:方法是在在类,结构体,枚举内部定义的.方法调用前面要有主体,而函数就不需要.
    • 我们在枚举和结构体方法掐面添加关键字mutatting,将方法声明为可以变方法,可变方法能够修改值类型变量属性,但不能修改值类型常量属性.也就说不可变方法值类型属性是都不能访问的,但引用类型的属性是可以访问的。
    • static修饰的方法为静态方法,当然类中class修饰的方法类方法.与计算属性类似,实例方法中既可以访问实例属性和方法又可以访问静态属性和方法,但是静态方法不能访问实例属性和实例方法,只能访问静态属性和方法.
    • class修饰的方法能被重写,static修饰的方法不能被重写.

    12.重写

    • 一个类继承另一个类的属性,方法,下标等特征后,子类可以重写(override)这些特征。
    • 重写实例属性:实例属性重写一方面可以Getter和Setter访问器,另一方面可以重写属性观察者。
    class Person {
        var name: String
        var age: Int
        init(name:String,age:Int) {
            self.name = name
            self.age = age
        }
    }
    class Student: Person {
        var school: String
        override var age:Int {
            get{
                return super.age
            }
            set{
                super.age = newValue<8?8:newValue
            }
        }
    }
    

    从属性重写来看,子类本身并不存储数据,数据存储在父类的存储属性中,子类将其变成了计算属性并重写。
    注意:一个属性重写了Getter和Setter访问器后就不能重写观察者。另外常量属性和只读计算属性也都不能重写属性观察者。

    • 重写静态属性:
    class Account{
        class var staticProp:Double{
            return 0.0668 * 1000000
        }
    }
    class TermAccount: Account {
        override static var staticProp:Double{
            return 0.0700*1000000
        }
    }
    

    Account的静态属性staticProp只能用class修饰,因为要在子类TermAccount重写该静态属性,所以该属性能被继承才行,而TermAccount可以用class也可以用static修饰,因为没有子类继承TermAccount的staticProp属性。

    • 重写实例静态方法:
    class Person {
        var name: String
        var age: Int
        init(name:String,age:Int) {
            self.name = name
            self.age = age
        }
        func description() -> String {
            return "\(name)的年龄为:\(age)"
        }
    }
    
    class Student: Person {
        var school: String
        override var age:Int {
            get{
                return super.age
            }
            set{
                super.age = newValue
            }
        }
        override init(name:String,age:Int) {
            self.school = "清华大学"
            super.init(name: name, age: age)
            
        }
        override func description() -> String {
            return "\(name)的年龄为:\(age),所在学校为\(school)"
        }
    }
    

    静态方法重写与实例方法重写类似,在方法名钱加上override关键字即可,但是只有class修饰的静态方法才能被被继承被重写,static修饰的静态方法不能被重写。

    • 下标重写:对下标重写也是重写Getter和Setter访问器,类似于属性,这里不再举例。
    • final关键字:final关键字可以声明类、属性、方法、和下标。final关键字可以声明类不能被继承,声明的属性、方法和下标不能被重写。

    13.类检查与转换(is、as、as!、as?、AnyObject、Any)

    • 使用is进行类型检查:is操作符可以判断一个实例是否是某个类的类型。(只要是该类型以及该类型的父类以及父类的父类类型,返回都为true,类似于oc的isKindOfClass方法)
    //Student继承于Person
    let  stu =  Student(name: "马云", age: 18)
    if stu is Student{
        print("马云18岁是一个学生")
    }
    if stu is Person{
        print("马云18岁是一个人")
    }
    

    在这里插入图片描述

    • 使用as、as!、as?进行类型转换:
      1. as操作符:用于向上转型(子类转换成父类),因为向上转型很少进行,所以代码中很少能够看到使用as操作符(通常都是向下转型:父类转出子类)。
      2. as!操作符: 在类型转换过程中对可选值进行拆包,转换结果是费可选类型。将费可选类型转换为非可选类型,将可选类型转换为非可选类型。
      3. as?操作符: 在类型转换过程中不进行裁包,转换结果是可选类型。将非可选类型转换为可选类型,将可选类型转换为可选类型。
    //Student,Worker类都继承于People
    let stu1 = Student(name: "李彦宏", age: 35, school: "北京大学")
    let stu2 = Student(name: "马化腾", age: 45, school: "深圳大学")
    let stu3 = Student(name: "马云", age: 55, school: "杭州师范大学")
    let wk1  = Worker(name: "李开复", age: 56, factory: "微软")
    let wk2  = Worker(name: "张小龙", age: 46, factory: "腾讯")
    let people = [stu1,stu2,stu3,wk1,wk2]
    for p in people{
        if let stu = p as? Student{ //这里在是先转换为可选类型,并且在转换成功后进行了可选绑定(因为都有值就解包了)
            print("Student \(stu.name),年龄:\(stu.age),毕业于:\(stu.school)")
        }else if let wk = p as? Worker{
            print("Worker \(wk.name),年龄:\(wk.age),工作于:\(wk.factory)")
        }
    }
    let stu4 = people[0]as? Student //这里是直接赋值为可选类型
    print("--------")
    print(stu4 as Any)
    print(stu4?.name as Any) //可选类型安全访问其属性的写法
    print(stu4!.name)
    

    在这里插入图片描述

    • 使用AnyObjectAny类型:Swift提供了两种类型来表示不确定类型(任意类型),AnyObject表示任何类(class)的类型;Any则表示任何类型,包括Int、Double、Array、struct等基本数据类型,也包括AnyObject类型。也就是说AnyObjectAny的子集。
    • 在OC与Swift混合编程时,OC的id类型和Swift的AnyObject类型可以互换,但是两者有本质区别。id类型是泛性,可以代表任何指针类型,编译时编译器不检查id类型,是动态的。AnyObject是实实在在表示类的类型,编译时会检查AnyObject类型。
    • 原则上若能使用集体的数据类型,则尽量不要使用AnyObject类型,更要少考虑使用Any类型。从集合中取出这些实例时也要尽可能的将AnyObject类型和Any类型转换为特定类型,然后再进行接下来的操作。

    14.扩展(extension)

    • Swift中可以使用一种扩展机制,在原始类型(类、机构体、枚举)的基础上添加新功能。扩展是一种轻量级的继承机制。
    • Swift中扩展机制可以在原始类型中添加新功能包括:
      1. 实例计算属性和静态计算属性
      2. 实例方法和静态方法
      3. 构造函数(结构体中可以扩展构造函数;类中只能扩展便利构造函数,不能扩展指定构造函数和析构函数,指定构造函数和析构函数只能在原始类中提供)
      4. 下标
    展开全文
  • Swift进阶黄金之路(一) 上期遗留一个问题:为什么 rethrows 一般用在参数中含有可以 throws 的方法的高阶函数中。 我们可以结合Swift的官方文档对rethrows再做一遍回顾: A function or method can be declared ...

    Swift进阶黄金之路(一)

    上期遗留一个问题:为什么 rethrows 一般用在参数中含有可以 throws 的方法的高阶函数中。

    我们可以结合Swift的官方文档对rethrows再做一遍回顾:

    A function or method can be declared with the rethrows keyword to indicate that it throws an error only if one of its function parameters throws an error. These functions and methods are known as rethrowing functions and rethrowing methods. Rethrowing functions and methods must have at least one throwing function parameter.

    返回rethrows的函数要求至少有一个可抛出异常的函数式参数,而有以函数作为参数的函数就叫做高阶函数。

    这期分两方面介绍Swift:特性修饰词和一些重要的Swift概念。

    特性修饰词

    在Swift语法中有很多@符号,这些@符号在Swift4之前的版本大多是兼容OC的特性,Swift4及之后则出现越来越多搭配@符号的新特性。以@开头的修饰词,在官网中叫Attributes,在SwiftGG的翻译中叫特性,我并没有找到这一类被@修饰的符号的统称,就暂且叫他们特性修饰词吧,如果有清楚的小伙伴可以告知我。

    从Swift5的发布来看(@dynamicCallable,@State),之后将会有更多的特性修饰词出现,在他们出来之前,我们有必要先了解下现有的一些特性修饰词以及它们的作用。

    参考:Swift Attributes

    @available

    @available: 可用来标识计算属性、函数、类、协议、结构体、枚举等类型的生命周期。(依赖于特定的平台版本 或 Swift 版本)。它的后面一般跟至少两个参数,参数之间以逗号隔开。其中第一个参数是固定的,代表着平台和语言,可选值有以下这几个:

    • iOS
    • iOSApplicationExtension
    • macOS
    • macOSApplicationExtension
    • watchOS
    • watchOSApplicationExtension
    • tvOS
    • tvOSApplicationExtension
    • swift

    可以使用*指代支持所有这些平台。

    有一个我们常用的例子,当需要关闭ScrollView的自动调整inset功能时:

    // 指定该方法仅在iOS11及以上的系统设置
    if #available(iOS 11.0, *) {
      scrollView.contentInsetAdjustmentBehavior = .never
    } else {
      automaticallyAdjustsScrollViewInsets = false
    }
    复制代码

    还有一种用法是放在函数、结构体、枚举、类或者协议的前面,表示当前类型仅适用于某一平台:

    @available(iOS 12.0, *)
    func adjustDarkMode() {
      /* code */
    }
    @available(iOS 12.0, *)
    struct DarkModeConfig {
      /* code */
    }
    @available(iOS 12.0, *)
    protocol DarkModeTheme {
      /* code */
    }
    复制代码

    版本和平台的限定可以写多个:

    @available(OSX 10.15, iOS 13, tvOS 13, watchOS 6, *)
    public func applying(_ difference: CollectionDifference<Element>) -> ArraySlice<Element>?
    复制代码

    注意:作为条件语句的available前面是#,作为标记位时是@

    刚才说了,available后面参数至少要有两个,后面的可选参数这些:

    • deprecated:从指定平台标记为过期,可以指定版本号
    • obsoleted=版本号:从指定平台某个版本开始废弃(注意弃用的区别,deprecated是还可以继续使用,只不过是不推荐了,obsoleted是调用就会编译错误)该声明
    • message=信息内容:给出一些附加信息
    • unavailable:指定平台上是无效的
    • renamed=新名字:重命名声明

    我们看几个例子,这个是Array里flatMap的函数说明:

    @available(swift, deprecated: 4.1, renamed: "compactMap(_:)", message: "Please use compactMap(_:) for the case where closure returns an optional value")
    public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
    复制代码

    它的含义是针对swift语言,该方式在swift4.1版本之后标记为过期,对应该函数的新名字为compactMap(_:),如果我们在4.1之上的版本使用该函数会收到编译器的警告,即⚠️Please use compactMap(_:) for the case where closure returns an optional value

    在Realm库里,有一个销毁NotificationToken的方法,被标记为unavailable

    extension RLMNotificationToken {
        @available(*, unavailable, renamed: "invalidate()")
        @nonobjc public func stop() { fatalError() }
    }
    复制代码

    标记为unavailable就不会被编译器联想到。这个主要是为升级用户的迁移做准备,从可用stop()的版本升上了,会红色报错,提示该方法不可用。因为有renamed,编译器会推荐你用invalidate(),点击fix就直接切换了。所以这两个标记参数常一起出现。

    @discardableResult

    带返回的函数如果没有处理返回值会被编译器警告⚠️。但有时我们就是不需要返回值的,这个时候我们可以让编译器忽略警告,就是在方法名前用@discardableResult声明一下。可以参考Alamofire中request的写法:

    @discardableResult
    public func request(
        _ url: URLConvertible,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        encoding: ParameterEncoding = URLEncoding.default,
        headers: HTTPHeaders? = nil)
        -> DataRequest
    {
        return SessionManager.default.request(
            url,
            method: method,
            parameters: parameters,
            encoding: encoding,
            headers: headers
        )
    }
    复制代码

    @inlinable

    这个关键词是可内联的声明,它来源于C语言中的inline。C中一般用于函数前,做内联函数,它的目的是防止当某一函数多次调用造成函数栈溢出的情况。因为声明为内联函数,会在编译时将该段函数调用用具体实现代替,这么做可以省去函数调用的时间。

    内联函数常出现在系统库中,OC中的runtim中就有大量的inline使用:

    static inline id autorelease(id obj)
    {
        ASSERT(obj);
        ASSERT(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }
    复制代码

    Swift中的@inlinable和C中的inline基本相同,它在标准库的定义中也广泛出现,可用于方法,计算属性,下标,便利构造方法或者deinit方法中。

    例如Swift对Arraymap函数的定义:

    @inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
    复制代码

    其实Array中声明的大部分函数前面都加了@inlinable,当应用某一处调用该方法时,编译器会将调用处用具体实现代码替换。

    需要注意内联声明不能用于标记为private或者fileprivate的地方。

    这很好理解,对私有方法的内联是没有意义的。内联的好处是运行时更快,因为它省略了从标准库调用map实现的步骤。但这个快也是有代价的,因为是编译时做替换,这增加了编译的开销,会相应的延长编译时间。

    内联更多的是用于系统库的特性,目前我了解的Swift三方库中仅有CocoaLumberjack使用了@inlinable这个特性。

    @warn_unqualified_access

    通过命名我们可以推断出其大概含义:对“不合规”的访问进行警告。这是为了解决对于相同名称的函数,不同访问对象可能产生歧义的问题。

    比如说,Swift 标准库中ArraySequence均实现了min()方法,而系统库中也定义了min(::),对于可能存在的二义性问题,我们可以借助于@warn_unqualified_access

    extension Array where Self.Element : Comparable {
      @warn_unqualified_access
      @inlinable public func min() -> Element?
    }
    extension Sequence where Self.Element : Comparable {
      @warn_unqualified_access
      @inlinable public func min() -> Self.Element?
    }
    复制代码

    这个特性声明会由编译器在可能存在二义性的场景中对我们发出警告。这里有一个场景可以便于理解它的含义,我们自定义一个求Array中最小值的函数:

    extension Array where Element: Comparable {
        func minValue() -> Element? {
            return min()
        }
    }
    复制代码

    我们会收到编译器的警告:Use of 'min' treated as a reference to instance method in protocol 'Sequence', Use 'self.' to silence this warning。它告诉我们编译器推断我们当前使用的是Sequence中的min(),这与我们的想法是违背的。因为有这个@warn_unqualified_access限定,我们能及时的发现问题,并解决问题:self.min()

    @objc

    把这个特性用到任何可以在 Objective-C 中表示的声明上——例如,非内嵌类,协议,非泛型枚举(原始值类型只能是整数),类和协议的属性、方法(包括 setter 和 getter ),初始化器,反初始化器,下标。 objc 特性告诉编译器,这个声明在 Objective-C 代码中是可用的。

    用 objc 特性标记的类必须继承自一个 Objective-C 中定义的类。如果你把 objc 用到类或协议中,它会隐式地应用于该类或协议中 Objective-C 兼容的成员上。如果一个类继承自另一个带 objc 特性标记或 Objective-C 中定义的类,编译器也会隐式地给这个类添加 objc 特性。标记为 objc 特性的协议不能继承自非 objc 特性的协议。

    @objc还有一个用处是当你想在OC的代码中暴露一个不同的名字时,可以用这个特性,它可以用于类,函数,枚举,枚举成员,协议,getter,setter等。

    // 当在OC代码中访问enabled的getter方法时,是通过isEnabled
    class ExampleClass: NSObject {
        @objc var enabled: Bool {
            @objc(isEnabled) get {
                // Return the appropriate value
            }
        }
    }
    复制代码

    这一特性还可以用于解决潜在的命名冲突问题,因为Swift有命名空间,常常不带前缀声明,而OC没有命名空间是需要带的,当在OC代码中引用Swift库,为了防止潜在的命名冲突,可以选择一个带前缀的名字供OC代码使用。

    Charts作为一个在OC和Swift中都很常用的图标库,是需要较好的同时兼容两种语言的使用的,所以也可以看到里面有大量通过@objc标记对OC调用时的重命名代码:

    @objc(ChartAnimator)
    open class Animator: NSObject { }
    
    @objc(ChartComponentBase)
    open class ComponentBase: NSObject { }
    复制代码

    @objcMembers

    因为Swift中定义的方法默认是不能被OC调用的,除非我们手动添加@objc标识。但如果一个类的方法属性较多,这样会很麻烦,于是有了这样一个标识符@objcMembers,它可以让整个类的属性方法都隐式添加@objc,不光如此对于类的子类、扩展、子类的扩展都也隐式的添加@objc,当然对于OC不支持的类型,仍然无法被OC调用:

    @objcMembers
    class MyClass : NSObject {
      func foo() { }             // implicitly @objc
    
      func bar() -> (Int, Int)   // not @objc, because tuple returns
          // aren't representable in Objective-C
    }
    
    extension MyClass {
      func baz() { }   // implicitly @objc
    }
    
    class MySubClass : MyClass {
      func wibble() { }   // implicitly @objc
    }
    
    extension MySubClass {
      func wobble() { }   // implicitly @objc
    }
    复制代码

    参考:Swift3、4中的@objc、@objcMembers和dynamic

    @testable

    @testable是用于测试模块访问主target的一个关键词。

    因为测试模块和主工程是两个不同的target,在swift中,每个target代表着不同的module,不同module之间访问代码需要public和open级别的关键词支撑。但是主工程并不是对外模块,为了测试修改访问权限是不应该的,所以有了@testable关键词。使用如下:

    import XCTest
    @testable import Project
    
    class ProjectTests: XCTestCase {
      /* code */
    }
    复制代码

    这时测试模块就可以访问那些标记为internal或者public级别的类和成员了。

    @frozen 和@unknown default

    frozen意为冻结,是为Swift5的ABI稳定准备的一个字段,意味向编译器保证之后不会做出改变。为什么需要这么做以及这么做有什么好处,他们和ABI稳定是息息相关的,内容有点多就不放这里了,之后会单独出一篇文章介绍,这里只介绍这两个字段的含义。

    @frozen public enum ComparisonResult : Int {
        case orderedAscending = -1
        case orderedSame = 0
        case orderedDescending = 1
    }
    
    @frozen public struct String {}
    
    extension AVPlayerItem {
      public enum Status : Int {
        case unknown = 0
        case readyToPlay = 1
        case failed = 2
      }
    }
    复制代码

    ComparisonResult这个枚举值被标记为@frozen即使保证之后该枚举值不会再变。注意到String作为结构体也被标记为@frozen,意为String结构体的属性及属性顺序将不再变化。其实我们常用的类型像IntFloatArrayDictionarySet等都已被“冻结”。需要说明的是冻结仅针对structenum这种值类型,因为他们在编译器就确定好了内存布局。对于class类型,不存在是否冻结的概念,可以想下为什么。

    对于没有标记为frozen的枚举AVPlayerItem.Status,则认为该枚举值在之后的系统版本中可能变化。

    对于可能变化的枚举,我们在列出所有case的时候还需要加上对@unknown default的判断,这一步会有编译器检查:

    switch currentItem.status {
        case .readyToPlay:
            /* code */
        case .failed:
            /* code */
        case .unknown:
            /* code */
        @unknown default:
            fatalError("not supported")
    }
    复制代码

    @State、@Binding、@ObservedObject、@EnvironmentObject

    这几个是SwiftUI中出现的特性修饰词,因为我对SwiftUI的了解不多,这里就不做解释了。附一篇文章供大家了解。

    [译]理解 SwiftUI 里的属性装饰器@State, @Binding, @ObservedObject, @EnvironmentObject

    几个重要关键词

    lazy

    lazy是懒加载的关键词,当我们仅需要在使用时进行初始化操作就可以选用该关键词。举个例子:

    class Avatar {
      lazy var smallImage: UIImage = self.largeImage.resizedTo(Avatar.defaultSmallSize)
      var largeImage: UIImage
    
      init(largeImage: UIImage) {
        self.largeImage = largeImage
      }
    }
    复制代码

    对于smallImage,我们声明了lazy,如果我们不去调用它是不会走后面的图片缩放计算的。但是如果没有lazy,因为是初始化方法,它会直接计算出smallImage的值。所以lazy很好的避免的不必要的计算。

    另一个常用lazy的地方是对于UI属性的定义:

    lazy var dayLabel: UILabel = {
        let label = UILabel()
      	label.text = self.todayText()
        return label
    }()
    复制代码

    这里使用的是一个闭包,当调用该属性时,执行闭包里面的内容,返回具体的label,完成初始化。

    使用lazy你可能会发现它只能通过var初始而不能通过let,这是由 lazy 的具体实现细节决定的:它在没有值的情况下以某种方式被初始化,然后在被访问时改变自己的值,这就要求该属性是可变的。

    另外我们可以在Sequences中使用lazy,在讲解它之前我们先看一个例子:

    func increment(x: Int) -> Int {
      print("Computing next value of \(x)")
      return x+1
    }
    
    let array = Array(0..<1000)
    let incArray = array.map(increment)
    print("Result:")
    print(incArray[0], incArray[4])
    复制代码

    在执行print("Result:")之前,Computing next value of ...会被执行1000次,但实际上我们只需要0和4这两个index对应的值。

    上面说了序列也可以使用lazy,使用的方式是:

    let array = Array(0..<1000)
    let incArray = array.lazy.map(increment)
    print("Result:")
    print(incArray[0], incArray[4])
    
    // Result:
    // 1 5
    复制代码

    在执行print("Result:")之前,并不会打印任何东西,只打印了我们用到的1和5。就是说这里的lazy可以延迟到我们取值时才去计算map里的结果。

    我们看下这个lazy的定义:

    @inlinable public var lazy: LazySequence<Array<Element>> { get }
    复制代码

    它返回一个LazySequence的结构体,这个结构体里面包含了Array<Element>,而map的计算在LazySequence里又重新定义了一下:

    /// Returns a `LazyMapSequence` over this `Sequence`.  The elements of
    /// the result are computed lazily, each time they are read, by
    /// calling `transform` function on a base element.
    @inlinable public func map<U>(_ transform: @escaping (Base.Element) -> U) -> LazyMapSequence<Base, U>
    复制代码

    这里完成了lazy序列的实现。LazySequence类型的lazy只能被用于map、flatMap、compactMap这样的高阶函数中。

    参考“懒”点儿好

    纠错:参考文章中说:"这些类型(LazySequence)只能被用在 mapflatMapfilter这样的高阶函数中" 其实是没有filter的,因为filter是过滤函数,它需要完整遍历一遍序列才能完成过滤操作,是无法懒加载的,而且我查了LazySequence的定义,确实是没有filter函数的。

    unowned weak

    Swift开发过程中我们会经常跟闭包打交道,而用到闭包就不可避免的遇到循环引用问题。在Swift处理循环引用可以使用unownedweak这两个关键词。看下面两个例子:

    class Dog {
        var name: String
        init (name: String ) {
            self.name = name
        }
        deinit {
            print("\(name) is deinitialized")
        }
    }
    
    class Bone {
      	// weak 修饰词
        weak var owner: Dog?
        init(owner: Dog?) {
            self.owner = owner
        }
        deinit {
            print("bone is deinitialized" )
        }
    }
    
    var lucky: Dog? = Dog(name: "Lucky")
    var bone: Bone? = Bone(owner: lucky!)
    lucky =  nil
    // Lucky is deinitialized
    复制代码

    这里Dog和Bone是相互引用的关系,如果没有weak var owner: Dog?这里的weak声明,将不会打印Lucky is deinitialized。还有一种解决循环应用的方式是把weak替换为unowned关键词。

    • weak相当于oc里面的weak,弱引用,不会增加循环计数。主体对象释放时被weak修饰的属性也会被释放,所以weak修饰对象就是optional。
    • unowned相当于oc里面的unsafe_unretained,它不会增加引用计数,即使它的引用对象释放了,它仍然会保持对被已经释放了的对象的一个 "无效的" 引用,它不能是 Optional 值,也不会被指向 nil。如果此时为无效引用,再去尝试访问它就会crash。

    这两者还有一个更常用的地方是在闭包里面:

    lazy var someClosure: () -> Void = { [weak self] in
        // 被weak修饰后self为optional,这里是判断self非空的操作                                
        guard let self = self else { retrun }
        self.doSomethings()
    }
    复制代码

    这里如果是unowned修饰self的话,就不需要用guard做解包操作了。但是我们不能为了省略解包的操作就用unowned,也不能为了安全起见全部weak,弄清楚两者的适用场景非常重要。

    根据苹果的建议:

    Define a capture in a closure as an unowned reference when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time.

    当闭包和它捕获的实例总是相互引用,并且总是同时释放时,即相同的生命周期,我们应该用unowned,除此之外的场景就用weak。

    参考:内存管理,WEAK 和 UNOWNED

    Unowned 还是 Weak?生命周期和性能对比

    KeyPath

    KeyPath是键值路径,最开始是用于处理KVC和KVO问题,后来又做了更广泛的扩展。

    // KVC问题,支持struct、class
    struct User {
        let name: String
        var age: Int
    }
    
    var user1 = User()
    user1.name = "ferry"
    user1.age = 18
     
    //使用KVC取值
    let path: KeyPath = \User.name
    user1[keyPath: path] = "zhang"
    let name = user1[keyPath: path]
    print(name) //zhang
    
    // KVO的实现还是仅限于继承自NSObject的类型
    // playItem为AVPlayerItem对象
    playItem.observe(\.status, changeHandler: { (_, change) in
        /* code */    
    })
    复制代码

    这个KeyPath的定义是这样的:

    public class AnyKeyPath : Hashable, _AppendKeyPath {}
    
    /// A partially type-erased key path, from a concrete root type to any
    /// resulting value type.
    public class PartialKeyPath<Root> : AnyKeyPath {}
    
    /// A key path from a specific root type to a specific resulting value type.
    public class KeyPath<Root, Value> : PartialKeyPath<Root> {}
    复制代码

    定义一个KeyPath需要指定两个类型,根类型和对应的结果类型。对应上面示例中的path:

    let path: KeyPath<User, String> = \User.name
    复制代码

    根类型就是User,结果类型就是String。也可以不指定,因为编译器可以从\User.name推断出来。那为什么叫根类型的?可以注意到KeyPath遵循一个协议_AppendKeyPath,它里面定义了很多append的方法,KeyPath是多层可以追加的,就是如果属性是自定义的Address类型,形如:

    struct Address {
        var country: String = ""
    }
    let path: KeyPath<User, String> = \User.address.country
    复制代码

    这里根类型为User,次级类型是Address,结果类型是String。所以path的类型依然是KeyPath<User, String>

    明白了这些我们可以用KeyPath做一些扩展:

    extension Sequence {
        func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] {
            return sorted { a, b in
                return a[keyPath: keyPath] < b[keyPath: keyPath]
            }
        }
    }
    // users is Array<User>
    let newUsers = users.sorted(by: \.age)
    复制代码

    这个自定义sorted函数实现了通过传入keyPath进行升序排列功能。

    参考:The power of key paths in Swift

    some

    some是Swift5.1新增的特性。它的用法就是修饰在一个 protocol 前面,默认场景下 protocol 是没有具体类型信息的,但是用 some 修饰后,编译器会让 protocol 的实例类型对外透明。

    可以通过一个例子理解这段话的含义,当我们尝试定义一个遵循Equatable协议的value时:

    // Protocol 'Equatable' can only be used as a generic constraint because it has Self or associated type requirements
    var value: Equatable {
        return 1
    }
    
    var value: Int {
        return 1
    }
    复制代码

    编译器提示我们Equatable只能被用来做泛型的约束,它不是一个具体的类型,这里我们需要使用一个遵循Equatable的具体类型(Int)进行定义。但有时我们并不想指定具体的类型,这时就可以在协议名前加上some,让编译器自己去推断value的类型:

    var value: some Equatable {
        return 1
    }
    复制代码

    在SwiftUI里some随处可见:

    struct ContentView: View {
        var body: some View {
            Text("Hello World")
        }
    }
    复制代码

    这里使用some就是因为View是一个协议,而不是具体类型。

    当我们尝试欺骗编译器,每次随机返回不同的Equatable类型:

    var value: some Equatable {
        if Bool.random() {
            return 1
        } else {
            return "1"
        }
    }
    复制代码

    聪明的编译器是会发现的,并警告我们Function declares an opaque return type, but the return statements in its body do not have matching underlying types

    参考:SwiftUI 的一些初步探索 (一)

    展开全文
  • swift进阶4.0

    2020-07-29 14:20:14
    swift 进阶 4.0 花钱买的t喵神作品。王巍 (onevcat) 是来自中国的一线 iOS 开发者,毕业于清华大学。在校期间就开始进行 iOS 开发,拥有丰富的 Cocoa 和 Objective-C 开发经验,另外他也活跃于使用 C# 的 Unity3D ...
  • 2020年我们应该学习UIKit还是SwiftUI 去年在WWDC19上首次宣布时,SwiftUI感到非常年轻。 它显示了一个有希望的未来,但它在这里和那里都有着粗糙的边缘,很难预测一年后的情况。 一年过去了,这就是我对苹果公司新的...

    2020年我们应该学习UIKit还是SwiftUI

    去年在WWDC19上首次宣布时,SwiftUI感到非常年轻。 它显示了一个有希望的未来,但它在这里和那里都有着粗糙的边缘,很难预测一年后的情况。 一年过去了,这就是我对苹果公司新的声明式UI框架SwiftUI的看法。

    差距越来越近

    我们对SwiftUI的争论总是像是“您不能在SwiftUI中执行x(UIKit功能)”。 今年,苹果展示了SwiftUI开发的惊人速度。 去年缺少的UI元素已经具有SwiftUI副本,例如UIColelctionView和UITextField已经具有LazyH / VGrid和TextEditor的SwiftUI副本。

    头等公民

    看来SwiftUI不再只是UIKit的视图了。 现在,您可以使用纯SwiftUI编写整个应用程序。

    以下是有效的SwiftUI应用程序代码。

    import SwiftUI
    
    @main
    struct SwiftUIApp: App {
        var body: some Scene {
            WindowGroup {
                Text("Hello! SwiftUI")
            }
        }
    }
    

    上面的代码不仅适用于iOS,而且相同的代码可以构成iPad和Mac应用程序。 您可以完全使用SwiftUI编写多平台应用程序。 似乎SwiftUI的概念比我最初想象的要强大得多。

    独家交易

    WidgetKit是iOS 14中用于编写Widget的新框架,只能使用SwiftUI进行编写。 这意味着您无法逃避它,即使您喜欢UIKit,似乎也别无选择,还必须学习SwiftUI。

    WidgetKit

    UIKit会死吗?

    不,这还远远没有结束。我认为苹果没有计划在可预见的将来放弃UIKit。在UIKit上工作多年后,SwiftUI对我来说就像魔术。它可以使用单行代码(或者因为内置于SwiftUI中而无需编写代码)来复制UIKit函数。魔术的坏处是,当事情进展不理想时,很难找出问题所在,也可能无法修复。那是您需要返回UIKit的时候。 UIKit是iOS的基础,而Apple仍在继续向其中添加新功能(UICollectionView和UISplitViewController今年有很多很酷的功能,您应该检查一下)。

    我将UIKit视为所有SwiftUI魔术背后的秘密调味料。 UIKit和SwiftUI都有其优势,Apple选择了合适的工具来完成正确的工作(它们将SwiftUI用于WidgetKit,因为它适合Widget现在具有的约束)。我认为这两个将并存很长时间。

    苹果公司将多年的UIKit和工具经验用于SwiftUI。一起工作很愉快,并且结果令人惊讶。苹果之所以能够做到这一点,是因为他们建立了一种将SwiftUI桥接到UIKit的方法,因此他们知道,即使SwiftUI无法完成某些任务,总会有UIKit存在。

    结论#
    重要的问题到了。您应该学习UIKit还是SwiftUI?
    我的简短答案是SwiftUI。

    这是我的长答案。从我在本文中指出的所有事实来看,SwiftUI现已准备就绪。我认为最终您将学习两者。

    如果您了解UIKit,您将被迫学习SwiftUI,因为它是WidgetKit等新框架所独有的。即使不是出于这个原因,我想您可能也是最欣赏SwiftUI的人。 SwiftUI可以开箱即用地完成很多伟大的事情,这是我们一直希望在UIKit中做的事情,但是没有机会和时间去做。

    如果您了解SwiftUI,那么有时您需要额外的自定义或遇到一些障碍,UIKit将永远为您服务。

    原文地址

    https://sarunw.com/posts/should-i-learn-uikit-or-swiftui/

    推荐

    基础文章推荐

    经典教程推荐

    技术源码推荐

    推荐文章

    CoreData篇

    Combine篇

    TextField篇

    JSON文件篇


    一篇文章系列

    技术交流

    QQ:3365059189
    SwiftUI技术交流QQ群:518696470

    展开全文
  • iOS开发系列--Swift进阶

    千次阅读 2018-08-07 22:45:34
    概述 上一篇文章《iOS开发系列--Swift语言》中对Swift的语法特点以及它和C、ObjC等其他语言的用法区别进行了介绍。当然,这只是Swift的入门基础,但是仅仅了解这些对于使用Swift进行
    原文地址为:iOS开发系列--Swift进阶

    概述

    上一篇文章《iOS开发系列--Swift语言》中对Swift的语法特点以及它和C、ObjC等其他语言的用法区别进行了介绍。当然,这只是Swift的入门基础,但是仅仅了解这些对于使用Swift进行iOS开发还是不够的。在这篇文章中将继续介绍一些Swift开发中一些不常关注但是又必备的知识点,以便对Swift有进一步的了解。

    1. 访问控制
    2. Swift命名空间
    3. Swift和ObjC互相调用
      1. Swift和ObjC映射关系
      2. Swift调用ObjC
      3. ObjC调用Swift
      4. 扩展—Swift调用C
    4. 反射
      1. 扩展—KVO
    5. 内存管理
      1. 循环引用
      2. 指针与内存
      3. 扩展—Core Foundation

    访问控制

    和其他高级语言一样Swift中也增加了访问控制,在Swift中提供了private、internal、public三种访问级别,但是不同的是Swift中的访问级别是基于模块(module,或者target)和源文件(.swift文件)的,而不是基于类型、命名空间声明。

    • private:只能访问当前源文件中的实体(注意Swift中的private和其他语言不太一样,它是基于源文件的,作用范围是整个源文件,如果一个源文件中有两个类,那么一个类可以访问另外一个类的私有成员)。
    • internal:可以访问当前模块中的其他任何实体,但是在模块外无法访问,这是所有实体的默认访问级别(通常在一个单目标Application中不需要自行设置访问级别)。
    • public:可以访问当前模块及其他模块中的任何实体(通常用于Framework)。

    下面是关于Swift关于不同成员访问级别的约定规则:

    1. 如果一个类的访问级别是private那么该类的所有成员都是private(此时成员无法修改访问级别),如果一个类的访问级别是internal或者public那么它的所有成员都是internal(如果类的访问级别是public,成员默认internal,此时可以单独修改成员的访问级别),类成员的访问级别不能高于类的访问级别(注意:嵌套类型的访问级别也符合此条规则);
    2. 常量、变量、属性、下标脚本访问级别低于其所声明的类型级别,并且如果不是默认访问级别(internal)要明确声明访问级别(例如一个常量是一个private类型的类类型,那么此常量必须声明为private);
    3. 在不违反1、2两条规则的情况下,setter的访问级别可以低于getter的访问级别(例如一个属性访问级别是internal,那么可以添加private(set)修饰将setter权限设置为private,在当前模块中只有此源文件可以访问,对外部是只读的);
    4. 必要构造方法(required修饰)的访问级别必须和类访问级别相同,结构体的默认逐一构造函数的访问级别不高于其成员的访问级别(例如一个成员是private那么这个构造函数就是private,但是可以通过自定义来声明一个public的构造函数),其他方法(包括其他构造方法和普通方法)的访问级别遵循规则1;
    5. 子类的访问级别不高于父类的访问级别,但是在遵循三种访问级别作用范围的前提下子类可以将父类低访问级别的成员重写成更高的访问级别(例如父类A和子类B在同一个源文件,A的访问级别是public,B的访问级别是internal,其中A有一个private方法,那么A可以覆盖其private方法并重写为internal);
    6. 协议中所有必须实现的成员的访问级别和协议本身的访问级别相同,其子协议的访问级别不高于父协议;
    7. 如果一个类继承于另一个类的同时实现了某个协议那么这个类的访问级别为父类和协议的最低访问级别,并且此类中方法访问级别和所实现的协议中的方法相同;
    8. 扩展的成员访问级别遵循规则1,但是对于类、结构体、枚举的扩展可以明确声明访问级别并且可以更低(例如对于internal的类,你可以声明一个private的扩展),而协议的访问级别不可以明确声明;
    9. 元组的访问级别是元组中各个元素的最低访问级别,注意:元组的访问级别是自动推导的,无法直接使用以上三个关键字修饰其访问级别;
    10. 函数的访问级是函数的参数、返回值的最低级别,并且如果其访问级别和默认访问级别(internal)不符需要明确声明;
    11. 枚举成员的访问级别等同于枚举的访问级别(无法单独设置),同时枚举的原始值、关联值的访问级别不能低于枚举的访问级别;
    12. 泛型类型或泛型函数的访问级别是泛型类型、泛型函数、泛型类型参数三者中最低的一个;
    13. 类型别名的访问级别不能高于原类型的访问级别;

     上面这些规则看上去比较繁琐,但其实很多内容理解起来也是顺理成章的(如果你是一个语言设计者相信大部分规则也会这么设计),下面通过一个例子对于规则3做一解释,这一点和其他语言有所不同但是却更加实用。在使用ObjC开发时大家通常会有这样的经验:在一个类中希望某个属性对外界是只读的,但是自己又需要在类中对属性进行写操作,此时只能直接访问属性对应的成员变量,而不能直接访问属性进行设置。但是Swift为了让语法尽可能精简,并没有成员变量的概念,此时就可以通过访问控制来实现。

    Person.swift

    import Foundation

    public class Person {
    //设置setter私有,但是getter为public
    public private(set) var name:String

    public init(name:String){
    self.name = name
    }

    public func showMessage(){
    println("name=\(name)")
    }
    }

    main.swift

    import Foundation

    var p = Person(name:"Kenshin")
    //此时不能设置name属性,但是可读
    //p.name = "Kaoru"
    println("name=\(p.name)")
    p.showMessage() 

    Xcode中的每个构建目标(Target)可以当做是一个模块(Module),这个构建目标可以是一个Application,也可以是一个通用的Framework(更多的时候是一个Application)。

    Swift命名空间

    熟悉ObjC的朋友都知道ObjC没有命名空间,为了避免类名重复苹果官方推荐使用类名前缀,这种做法从一定程度上避免了大部分问题,但是当你在项目中引入一个第三方库而这个第三方库引用了一个和你当前项目中用到的同一个库时就会出现问题。因为静态库最终会编译到同一个域,最终导致编译出错。当然作为一个现代化语言Swift一定会解决这个问题,可是如果查看Swift的官方文档,里面关于Swift的命名空间并没有太多详细的说明。但是Swift的作者Chris Lattner在Twitter中回答了这个问题:

    Namespacing is implicit in swift, all classes (etc) are implicitly scoped by the module (Xcode target) they are in. no class prefixes needed

    Swift中是实现了命名空间功能的,只是这个命名空间不像C#的namespace或者Java中的package那样需要显式在文件中指定,而是采用模块(Module)的概念:在同一个模块中所有的Swift类处于同一个命名空间,它们之间不需要导入就可以相互访问。很明显Swift的这种做法是为了最大限度的简化Swift编程。其实一个module就可以看成是一个project中的一个target,在创建项目的时候默认就会创建一个target,这个target的默认模块名称就是这个项目的名称(可以在target的Build Settings—Product Module Name配置)。

    下面不妨看一个命名空间的例子,创建一个Single View Application应用“NameSpaceDemo”。默认情况下模块名称为“NameSpaceDemo”,这里修改为“Network”,并且添加”HttpRequest.swift"。然后添加一个Cocoa Touch Framework类型的target并命名为“IO”,添加“File.swift”。然后在ViewController.swift中调用HttpRequest发送请求,将请求结果利用File类来保存起来。

    File.swift

    import Foundation

    public class File {
    public var path:String!

    public init(path:String) {
    self.path = path
    }

    public func write(content:String){
    var error:NSError?
    content.writeToFile(path, atomically: true, encoding:NSUTF8StringEncoding, error: &error)
    if error != nil {
    println("write failure...")
    }
    }

    public func read() ->String?{
    var error:NSError?
    var content = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: &error)
    if error != nil {
    println("write failure...")
    }
    return content
    }
    }

    HttpRequest.swift

    import Foundation

    class HttpRequest {
    class func request(urlStr:String,complete:(responseText:String?)->()){
    var url = NSURL(string: urlStr)
    let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) -> Void in
    var str:String?
    if error == nil {
    str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String
    }
    complete(responseText: str)
    }
    task.resume()
    }
    }

     

    ViewController.swift

    import UIKit

    //导入模块
    import IO

    class ViewController: UIViewController {
    let url = "http://www.cnblogs.com/kenshincui"
    let filePath = "/Users/KenshinCui/Desktop/file.txt"

    override func viewDidLoad() {
    super.viewDidLoad()
    //加上命名空间Network调用,注意这里命名空间可以省略
    Network.HttpRequest.request(url, complete: { (responseText) -> () in
    if let txt = responseText {

    //调用模块中的类和方法
    var file = File(path: self.filePath)
    file.write(txt)
    // println(file.read()!)

    }else{
    println("error...")
    }
    })

    }
    }

    可以看到首先同一个Module中的HttpRequest类可以加上命名空间调用(当然这里可以省略),另外对于不同Modle下的File类通过导入IO模块可以直接使用File类,但是这里需要注意访问控制,可以看到File类及其成员均声明为了public访问级别。 用模块进行命名空间划分的方式好处就是可以不用显式指定命名空间,然而这种方式无法在同一个模块中再进行划分,不过这个问题可以使用Swift中的嵌套类型来解决。在下面的例子中仍然使用前面的两个类HttpRequest和File类来演示,不同的是两个类分别嵌套在两个结构体Network和IO之中。

    Network.HttpRequest.swift

    import Foundation

    struct Network {
    class HttpRequest {
    class func request(urlStr:String,complete:(responseText:String?)->()){
    var url = NSURL(string: urlStr)
    let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) -> Void in
    var str:String?
    if error == nil {
    str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String
    }
    complete(responseText: str)
    }
    task.resume()
    }
    }
    }

    IO.File.swift

    import Foundation

    struct IO {
    class File {
    var path:String!

    init(path:String) {
    self.path = path
    }

    func write(content:String){
    var error:NSError?
    content.writeToFile(path, atomically: true, encoding:NSUTF8StringEncoding, error: &error)
    if error != nil {
    println("write failure...")
    }
    }

    func read() ->String?{
    var error:NSError?
    var content = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: &error)
    if error != nil {
    println("write failure...")
    }
    return content
    }
    }
    }

    main.swift

    import Foundation

    let url = "http://www.cnblogs.com/kenshincui"
    let filePath = "/Users/KenshinCui/Desktop/file.txt"

    Network.HttpRequest.request(url, complete: { (responseText) -> () in
    if let txt = responseText {

    var file = IO.File(path: filePath)
    file.write(txt)
    //println(file.read()!)

    }else{
    println("error...")
    }
    })

    sleep(30) //延迟30s避免命令行程序运行完进程结束,等待网络请求

    Swift和ObjC互相调用

    Swift的设计的初衷就是摆脱ObjC沉重的历史包袱,毕竟ObjC的历史太过悠久,相比于很多现代化语言它缺少一些很酷的语法特性,而且ObjC的语法和其他语言相比差别很大。但是Apple同时也不能忽视ObjC的地位,毕竟ObjC经过二十多年的历史积累了大量的资源(开发者、框架、类库等),因此在Swift推出的初期必须考虑兼容ObjC。但同时Swift和ObjC是基于两种不同的方式来实现的(例如ObjC可以在运行时决定对象类型,但是Swift为了提高效率要求在编译时就必须确定对象类型),所以要无缝兼容需要做大量的工作。而作为开发人员我们有必要了解两种语言之间的转化关系才能对Swift有更深刻的理解。

    Swift和ObjC映射关系

    其实从前面的例子中大家不难发现Swift和ObjC必然存在着一定的映射关系,例如对于文件的操作使用了字符串的writeToFile方法,在网络请求时使用的NSURLSession,虽然调用方式不同但是其参数完全和做ObjC开发时调用方式一致。原因就是Swift编译器自动做了映射,下面列举了部分Swift和ObjC的映射关系帮助大家理解:

    Swift ObjC 备注
    AnyObject id(ObjC中的对象任意类型) 由于ObjC中的对象可能为nil,所以Swift中如果用到ObjC中类型的参数会标记为对应的可选类型
    Array、Dictionary、Set NSArray、NSDictionary、NSSet 注意:ObjC中的数组和字典不能存储基本数据类型,只能存储对象类型,这样一来对于Swift中的Int、UInt、Float、Double、Bool转化时会自动桥接成NSNumber
    Int NSInteger、NSUInteger 其他基本类型情况类似,不再一一列举
    NSObjectProtocol NSObject协议(注意不是NSObject类) 由于Swift在继承或者实现时没有类的命名空间的概念,而ObjC中既有NSObject类又有NSObject协议,所以在Swift中将NSObject协议对应成了NSObjectProtocol
    CGContext CGContextRef

    Core Foundation中其他情况均是如此,由于Swift本身就是引用类型,在Swift不需要再加上“Ref”

    ErrorType NSError  
    “ab:" @selector(ab:)

    Swift可以自动将字符串转化成成selector

    @NSCopying copy属性  
    init(x:X,y:Y) initWithX:(X)x y:(Y)y 构造方法映射,Swift会去掉“With”并且第一个字母小写作为其第一个参数,同时也不需要调用alloc方法,但是需要注意ObjC中的便利工厂方法(构建对象的静态方法)对应成了Swift的便利构造方法
    func xY(a:A,b:B) void xY:(A)a b:(B)b  
    extension(扩展) category(分类) 注意:不能为ObjC中存在的方法进行extension
    Closure(闭包) block(块) 注意:Swift中的闭包可以直接修改外部变量,但是block中要修改外部变量必须声明为__block

    Swift兼容大部分ObjC(通过类似上面的对应关系),多数ObjC的功能在Swift中都能使用。当然,还是有个别地方Swift并没有考虑兼容ObjC,例如:Swift中无法使用预处理指令(例如:宏定义,事实上在Swift中推举使用常量定义);Swift中也无法使用performSelector来执行一个方法,因为Swift认为这么做是不安全的。

    相反,如果在ObjC中使用Swift也同样是可行的(除了个别Swift新增的高级功能)。Swift中如果一个类继承于NSObject,那么他会自动和ObjC兼容,这样ObjC就可以按照上面的对应关系调用Swift的方法、属性等。但是如果Swift中的类没有继承于NSObject呢?此时就需要使用一个关键字“@objc”进行标注,ObjC就可以像使用正常的ObjC编码一样调用Swift了(事实上继承于NSObject的类之所以在ObjC中能够直接调用也是因为编译器会自动给类和非private成员添加上@objc,类似的@IBoutlet、@IBAction、@NSManaged修饰的方法属性Swift编译器也会自动添加@objc标记)。

    Swift调用ObjC 

    当前ObjC已经积累了大量的第三方库,相信在Swift发展的前期调用已经存在的ObjC是比较常见的。在Swift和ObjC的兼容性允许你在一个项目中使用两种语言混合编程(称为“mix and match”),而不管这个项目原本是基于Swift的还是ObjC的。无论是Swift中调用ObjC还是ObjC中调用Swift都是通过头文件暴漏对应接口的,下图说明了这种交互方式:

    SwiftInteractObjC

    不难发现,要在Swift中调用ObjC必须借助于一个桥接头文件,在这个头文件中将ObjC接口暴漏给Swift。例如你可以创建一个“xx.h”头文件,然后使用“#import”导入需要在Swift中使用的ObjC类,同时在Build Settings的“Objective-C Bridging Header”中配置桥接文件“xx.h”。但是好在这个过程Xcode可以帮助你完成,你只需要在Swift项目中添加ObjC文件,Xcode就会询问你是否创建桥接文件,你只需要点击“Yes”就可以帮你完成上面的操作:

    CreateObjCBridgingHeaderTip

    为了演示Swift中调用ObjC的简洁性, 下面创建一个基于Swift的Single View Application类型的项目,现在有一个基于ObjC的“KCLoadingView”类,它可以在网络忙时显示一个加载动画。整个类的实现很简单,就是通过一个基础动画实现一个图片的旋转。

    KCLoadingView.h

    #import <UIKit/UIKit.h>

    /**
    * 加载视图,显示加载效果
    */
    @interface KCLoadingView : UIImageView

    /**
    * 启动,开始旋转
    */
    - (void)start;

    /**
    * 停止
    */
    - (void)stop;

    @end

    KCLoadingView.m

    #import "KCLoadingView.h"

    static NSString *const kAnimationKey = @"rotationAnimation";
    @interface KCLoadingView ()
    @property(strong, nonatomic) CABasicAnimation *rotationAnimation;
    @end

    @implementation KCLoadingView
    #pragma mark - 生命周期及其基类方法
    - (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
    [self setup];
    }
    return self;
    }

    #pragma mark - 公共方法
    - (void)start {
    [self.layer addAnimation:self.rotationAnimation forKey:kAnimationKey];
    }

    - (void)stop {
    [self.layer removeAnimationForKey:kAnimationKey];
    }

    #pragma mark - 私有方法
    - (void)setup {
    self.image = [UIImage imageNamed:@"loading"];

    CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    rotationAnimation.toValue = [NSNumber numberWithFloat:M_PI * 2.0];
    rotationAnimation.duration = 0.7;
    rotationAnimation.cumulative = YES;
    rotationAnimation.repeatCount = HUGE_VALF;
    self.rotationAnimation = rotationAnimation;
    [self.layer addAnimation:rotationAnimation forKey:kAnimationKey];
    }

    @end

    当将这个类加入到项目时就会提示你是否创建一个桥接文件,在这个文件中导入上面的“KCLoadingView”类。现在这个文件只有一行代码

    ObjCBridge-Bridging-Header.h

    #import "KCLoadingView.h" 

    接下来就可以调用这个类完成一个加载动画,调用关系完全顺其自然,开发者根本感觉不到这是在调用一个ObjC类。

    ViewController.swfit

    import UIKit

    class ViewController: UIViewController {
    lazy var loadingView:KCLoadingView = {
    var size=UIScreen.mainScreen().bounds.size
    var lv = KCLoadingView()
    lv.frame.size=CGSizeMake(37.0, 37.0)
    lv.center=CGPointMake(size.width*0.5, size.height*0.5)
    return lv
    }()

    lazy private var converView:UIView = {
    var cv = UIView(frame: UIScreen.mainScreen().bounds)
    cv.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5)
    return cv
    }()

    override func loadView() {
    //设置背景
    var image = UIImage(named: "iOS9")
    var background = UIImageView(frame: UIScreen.mainScreen().bounds)
    background.userInteractionEnabled=true
    background.image=image
    self.view = background
    }

    override func viewDidLoad() {
    super.viewDidLoad()

    //设置蒙层
    self.view.addSubview(self.converView)

    //添加加载控件
    self.view.addSubview(self.loadingView)

    loadingView.start()
    }

    override func touchesBegan(touches: Set, withEvent event: UIEvent) {
    loadingView.stop()
    }
    }

    运行效果

    SwiftUseObjC 

    ObjC调用Swift

    从前面的Swift和ObjC之间的交互图示可以看到ObjC调用Swift是通过Swift生成的一个头文件实现的,好在这个头文件是由编译器自动完成的,开发者不需要关注,只需要记得他的格式即可“项目名称-Swift.h”。如果在ObjC项目中使用了Swift,只要在ObjC的“.m”文件中导入这个头文件就可以直接调用Swift,注意这个生成的文件并不在项目中,它在项目构建的一个文件夹中(可以按住Command点击头文件查看)。同样通过前面的例子演示如何在ObjC中调用Swift,新建一个基于ObjC的项目(项目名称“UseSwiftInObjC”),并且这次加载动画控件使用Swift编写。

    LoadingView.swift

    import UIKit

    public class LoadingView:UIImageView {
    let basicAnimationKey = "rotationAnimation"
    lazy var rotationAnimation:CABasicAnimation = {
    var animation = CABasicAnimation(keyPath: "transform.rotation.z")
    animation.toValue = 2*M_PI
    animation.duration = 0.7
    animation.cumulative = true
    animation.repeatCount = .infinity
    return animation
    }()

    convenience init(){
    self.init(frame: CGRectZero)
    }

    override init(frame: CGRect) {
    super.init(frame: frame)
    self.image = UIImage(named: "loading")
    }

    required public init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.image = UIImage(named: "loading")
    }

    public func start() {
    self.layer.addAnimation(self.rotationAnimation, forKey: basicAnimationKey)
    }

    public func stop() {
    self.layer.removeAnimationForKey(basicAnimationKey)
    }
    }

     然后可以直接在ObjC代码中导入自动生成的文件“UseSwiftInObjC-Swift.h”并调用。

    ViewController.m

    #import "ViewController.h"
    #import "UseSwiftInObjC-Swift.h"

    @interface ViewController ()
    @property (strong,nonatomic) UIView *converView;
    @property (strong,nonatomic) LoadingView *loadingView;
    @end

    @implementation ViewController
    -(void)loadView{
    UIImage *image = [UIImage imageNamed:@"iOS9"];
    UIImageView *background = [[UIImageView alloc]initWithImage:image];
    background.userInteractionEnabled = YES;
    self.view = background;
    }

    - (void)viewDidLoad {
    [super viewDidLoad];

    [self.view addSubview:self.converView];
    [self.view addSubview:self.loadingView];
    [self.loadingView start];
    }

    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [self.loadingView stop];
    }

    #pragma mark - 属性
    /**
    * 遮罩层
    */
    -(UIView *)converView{
    if (!_converView) {
    _converView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    _converView.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5];
    }
    return _converView;
    }
    /**
    * 加载指示器
    */
    -(LoadingView *)loadingView{
    if(!_loadingView){
    CGSize screenSize = [UIScreen mainScreen].bounds.size;
    CGFloat loadingViewWidth = 37.0;
    _loadingView=[[LoadingView alloc]init];
    _loadingView.frame=CGRectMake((screenSize.width-loadingViewWidth)*0.5, (screenSize.height - loadingViewWidth)*0.5, loadingViewWidth, loadingViewWidth);
    }
    return _loadingView;
    }

    @end

    虽然生成的头文件并不会直接放到项目中,但是可以直接按着Command键查看生成的文件内容,当然这个文件比较长,里面使用了很多宏定义判断,这里只关心最主要部分。

    UseSwiftInObjC-Swift.h

    SWIFT_CLASS("_TtC14UseSwiftInObjC11LoadingView")
    @interface LoadingView : UIImageView
    - (SWIFT_NULLABILITY(nonnull) instancetype)initWithCoder:(NSCoder * __nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER;
    - (void)start;
    - (void)stop;
    @end

    可以清晰的看到Swift确实进行了桥接,通过头文件将接口暴漏给了ObjC。但是注意前面说过的访问控制,如果类和方法在Swift中不声明为public,那么在ViewController.m中是无法调用的。事实上,如果方法不是public在UseSwiftInObjC-Swift.h中根本不会生成对应的方法声明。

    扩展—Swift调用C

    由于ObjC是C的超集,使得在ObjC可以无缝访问C语言。但是Swift的产生就是ObjC without C,因此在Swift中不可能像在ObjC中混编入C一样简单。但是考虑到C语言的强大以及历时那么多年留下了丰富的类库,有时候又不得不使用它,Swift中还是保留了与一定数量的C语言类型和特性的兼容。前面介绍过关于如何在Swift中使用ObjC的知识,事实上在Swift中使用C也是类似的(因为ObjC是C的超集,ObjC既然可以桥接,C自然也可以),你需要一个桥接文件,不同的是ObjC中的很多内容在桥接到Swift时都是类似,很容易上手。例如ObjC中使用的NSObject,在Swift中仍然对应NSObject,很多时候开发人员感觉不到这种转化,只是编程语言发生了变化。但是C导入Swift就需要必须要了解具体的对应关系:

    C类型 Swift类型 说明
    基本类型    
    char,signed char CChar 类似的unsigned char对应CUnsignedChar
    int CInt 类似的unsigned int对应CUnsignedInt
    short CShort 类似的unsigned short对应CUnsignedShort
    long CLong 类似的unsigned long对应CUnsignedLong
    long long CLongLong 类似的unsigned long long 对应 CUnsignedLongLong
    float CFloat  
    double CDouble  
    构造体类型   注意:结构体实现
    枚举typedef NS_ENUM(NSInteger,A){AB,AC} enum A:Int{case B,C} 去掉对应的前缀 ,注意C中的NS_Options会对应成Swift中实现OptionSetType的结构体
    结构体 对应Swift中的结构体   
    联合   Swift中不能完全支持联合,建议使用枚举关联值代替
    指针类型   C语言中的指针类型映射成了Swift中的泛型
    Type * UnsafeMutablePointer<Type> 作为返回类型、变量、参数类型时
    const Type * UnsafePointer<Type> 作为返回类型、变量、参数类型时
    Type *const * UnsafePointer<Type> 对于类类型
    Type *__strong * UnsafeMutablePointer<Type> 对于类类型
    Type * * AutoreleasingUnsafePointer<Type> 对于类类型
    函数指针 闭包  

     对于其他类型的映射关系都很容易理解,这里主要说一下指针的内容。通过上表可以看到在C中定义的一些指针类型当在Swift中使用时会有对应的类型,但是如果一个参数为某种指针类型,实际调用时应该使用何种Swift数据类型的数据作为参数调用呢?例如参数为UnsafePointer<Type>,是否只能传入UnsafePointer<Type>呢,其实也可以传入nil,并且最终调用时将会转化为null指针来调用。下表列出了这种参数调用对应关系:

    可用类型 最终转化类型
     UnsafePointer<Type>   注意:如果Type为Void则可以代表任何类型
     nil  null
     UnsafePointer<Type>、UnsafeMutablePointer<Type>、AutoreleasingUnsafeMutablePointer<Type>  UnsafePointer<Type>
     String  如果Type为Int、或者Int8将最终转化为UTF8字符串
     &typeValue  元素地址
     Type类型的数组([typeValue1,typeValue2])  数组首地址
     UnsafeMutablePointer<Type>  注意:如果Type为Void则可以代表任何类型
     nil null 
     UnsafeMutablePointer<Type>  UnsafeMutablePointer<Type> 
     &typeValue  元素地址
     Type类型的数组的地址(&[typeValue1,typeValue2])  数组地址
     AutoreleasingUnsafeMutablePointer<Type>  
     nil null 
     AutoreleasingUnsafeMutablePointer<Type>  AutoreleasingUnsafeMutablePointer<Type>
     &typeValue  元素地址

    下面不妨看一下如何在Swift中使用C语言,假设现在有一个用于字符串拼接的C库函数“stringAppend(char*,const char *)”,将其对应的文件导入到一个Swift项目中按照提示添加桥接头文件并在桥接头文件中引入对应的C文件。

    string.h

    #ifndef __UseCInSwift__Common__
    #define __UseCInSwift__Common__void stringAppend(char *source, char *toAppend)

    #include

    void stringAppend(char *source,const char *toAppend);

    #endif

    string.c

    #include "string.h"

    void stringAppend(char *source,const char *toAppend) {
    unsigned long sourceLen = strlen(source);
    char *pSource = source + sourceLen;
    const char *pAppend = toAppend;
    while (*pAppend != '\0') {
    *pSource++ = *pAppend++;
    }
    }

    UseCInSwift-Bridging-Header.h

    #import "string.h"

    然后在Swift中调用上面的C函数

    import Foundation

    var sourceStr:String = "Hello"
    var appendStr:String = ",World!"

    var sourceCStr = (sourceStr as NSString).UTF8String
    var sourceMutablePointer:UnsafeMutablePointer = UnsafeMutablePointer(sourceCStr)

    stringAppend(sourceMutablePointer,appendStr)

    println(String.fromCString(sourceMutablePointer)!) //结果:Hello,World!

     可以看到“char *”参数转化成了Swift中的UnsafeMutablePointer<Int8>,而将”const char *”转化成了UnsafePointer<Int8>。根据上面表格中的调用关系,如果参数为UnsafeMutablePointer<Type>可以传入nil、UnsafeMutablePointer<Type>或者元素地址,很明显这里需要使用UnsafeMutablePointer<Int8>;而如果参数为UnsafePointer<Type>并且Type为Int8或者Int则可以直接传入String类型的参数,因此也就有了上面的调用关系。

    当然,上面这种方式适合所有在Swift中引入C语言的情况,但是为了方便调用,在Swift中默认已经module了常用的C语言类库Darwin,这个类库就作为了标准的Swift类库不需要再进行桥接,可以直接导入模块(例如import Darwin,但是事实上Foundation模块已经默认导入了Darwin,而UIKit又导入了Foundation模块,因此通常不需要手动导入Darwin)。那么对于没有模块化的C语言类库(包括第三方类库和自己定义的C语言文件等)能不能不使用桥接文件呢?答案就是使用隐藏符号“@asmname”,通过@asmname可以将C语言的函数不经过桥接文件直接映射为Swift函数。例如可以移除上面的桥接头文件,修改main.swift函数,通过@asmname加stringAppend映射成为Swift函数(注意重新映射的Swift函数名称不一定和C语言函数相同):

     main.swift

    import Foundation

    //通过asmname将C函数stringAppend()映射到Swift函数,事实上这里的Swift函数名可以任意命名
    @asmname("stringAppend") func stringAppend(var sourceStr:UnsafeMutablePointer,var apendStr:UnsafePointer ) -> Void

    var sourceStr:String = "Hello"
    var appendStr:String = ",World!"

    var sourceCStr = (sourceStr as NSString).UTF8String
    var sourceMutablePointer:UnsafeMutablePointer = UnsafeMutablePointer(sourceCStr)

    stringAppend(sourceMutablePointer,appendStr)

    println(String.fromCString(sourceMutablePointer)!) //结果:Hello,World!

    更多Swift标准类库信息可以查看:https://github.com/andelf/Defines-Swift 

    反射

    熟悉C#、Java的朋友不难理解反射的概念,所谓反射就是可以动态获取类型、成员信息,在运行时可以调用方法、属性等行为的特性。 在使用ObjC开发时很少强调其反射概念,因为ObjC的Runtime要比其他语言中的反射强大的多。在ObjC中可以很简单的实现字符串和类型的转换(NSClassFromString()),实现动态方法调用(performSelector: withObject:),动态赋值(KVC)等等,这些功能大家已经习以为常,但是在其他语言中要实现这些功能却要跨过较高的门槛,而且有些根本就是无法实现的。不过在Swift中并不提倡使用Runtime,而是像其他语言一样使用反射(Reflect),即使目前Swift中的反射还没有其他语言中的反射功能强大(Swift还在发展当中,相信后续版本会加入更加强大的反射功能)。

    在Swift中反射信息通过MirrorType协议来描述,而Swift中所有的类型都能通过reflect函数取得MirrorType信息。先看一下MirrorType协议的定义(为了方便大家理解,添加了相关注释说明):

    protocol MirrorType {

    /// 被反射的成员,类似于一个实例做了as Any操作
    var value: Any { get }

    /// 被反射成员的类型
    var valueType: Any.Type { get }

    /// 被反射成员的唯一标识
    var objectIdentifier: ObjectIdentifier? { get }

    /// 被反射成员的子成员数(例如结构体的成员个数,数组的元素个数等)
    var count: Int { get }

    // 取得被反射成员的字成员,返回值对应字成员的名称和值信息
    subscript (i: Int) -> (String, MirrorType) { get }

    /// 对于反射成员的描述
    var summary: String { get }

    /// 显示在Playground中的“值”信息
    var quickLookObject: QuickLookObject? { get }

    /// 被反射成员的类型的种类(例如:基本类型、结构体、枚举、类等)
    var disposition: MirrorDisposition { get }
    }

    获取到一个变量(或常量)的MirrorType之后就可以访问其类型、值、类型种类等元数据信息。在下面的示例中将编写一个函数简单实现一个类似于ObjC中“valueForKey:”的函数。

    import UIKit

    struct Person {
    var name:String
    var age:Int = 0

    func showMessage(){
    print("name=\(name),age=\(age)")
    }
    }


    //定义一个方法获取实例信息
    func valueForKey(key:String,obj:Any) -> Any?{
    //获取元数据信息
    var objInfo:MirrorType = reflect(obj)
    //遍历子成员
    for index in 0..<objInfo.count {
    //如果子成员名称等于key则获取对应值
    let (name,mirror) = objInfo[index]
    if name == key {
    return mirror.value
    }
    }
    return nil;
    }

    var p = Person(name: "Kenshin", age: 29)
    //先查看一下对象描述信息,然后对照结果是否正确
    dump(p)
    /*结果:
    __lldb_expr_103.Person
    - name: Kenshin
    - age: 29
    */

    var name = valueForKey("name", p)
    print("p.name=\(name)") //结果:p.name=Optional("Kenshin")

    可以看到,通过反射可以获取到变量(或常量)的信息,并且能够读取其成员的值,但是Swift目前原生并不支持给某个成员动态设置值(MirrorType的value属性是只读的)。如果想要进行动态设置,可以利用前面介绍的Swift和ObjC兼容的知识来实现,Swift目前已经导入了Foundation,只要这个类是继承于NSObject就会有对应的setValue:forKey:方法来使用KVC。当然,这仅限于类,对应结构体无能为力。

    扩展--KVO

    和KVC一样,在Swift中使用KVO也仅限于NSObject及其子类,因为KVO本身就是基于KVC进行动态派发的,这些都属于运行时的范畴。Swift要实现这些动态特性需要在类型或者成员前面加上@objc(继承于NSObject的子类及非私有成员会自动添加),但并不是说加了@objc就可以动态派发,因为Swift为了性能考虑会优化为静态调用。如果确实需要使用这些特性Swift提供了dynamic关键字来修饰,例如这里要想使用KVO除了继承于NSObject之外就必须给监控的属性加上dynamic关键字修饰。下面的演示中说明了这一点:

    import Foundation

    class Acount:NSObject {
    dynamic var balance:Double = 0.0
    }

    class Person:NSObject {
    var name:String
    var account:Acount?{
    didSet{
    if account != nil {
    account!.addObserver(self, forKeyPath: "balance", options: .Old, context: nil);
    }
    }
    }

    init(name:String){
    self.name = name
    super.init()
    }

    override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer) {
    if keyPath == "balance" {
    var oldValue = change[NSKeyValueChangeOldKey] as! Double
    var newValue = (account?.balance)!
    print("oldValue=\(oldValue),newValue=\(newValue)")
    }
    }
    }

    var p = Person(name: "Kenshin Cui")
    var account = Acount()
    account.balance = 10000000.0
    p.account = account
    p.account!.balance = 999999999.9 //结果:oldValue=10000000.0,newValue=999999999.9

    注意:对于系统类(或一些第三方框架)由于无法修改其源代码如果要进行KVO监听,可以先继承此类然后进行使用dynamic重写;此外,并非只有KVO需要加上dynamic关键字,对于很多动态特性都是如此,例如要在Swift中实现Swizzle方法替换,方法前仍然要加上dynamic,因为方法的替换也需要动态派发。

    内存管理

    循环引用

    Swift使用ARC来自动管理内存,大多数情况下开发人员不需要手动管理内存,但在使用ObjC开发时,大家都会遇到循环引用的问题,在Swift中也不可避免。 举例来说,人员有一个身份证(Person有idCard属性),而身份证就有一个拥有者(IDCard有owner属性),那么对于一个Person对象一旦建立了这种关系之后就会和IDCard对象相互引用而无法被正确的释放。

    例如下面的代码在执行完test之后p和idCard两个对象均不会被释放:

    import Foundation

    class Person {
    var name:String
    var idCard:IDCard

    init(name:String,idCard:IDCard){
    self.name = name
    self.idCard = idCard
    idCard.owner = self
    }

    deinit{
    println("Person deinit...")
    }
    }

    class IDCard {
    var no:String
    var owner:Person?

    init(no:String){
    self.no = no
    }

    deinit{
    println("IDCard deinit...")
    }
    }

    func test(){
    var idCard = IDCard(no:"100188888888888888")
    var p = Person(name: "Kenshin Cui",idCard:idCard)
    }

    //注意test执行完之后p和idCard均不会被释放(无法执行deinit方法)
    test()

    println("wait...")

    两个对象之间的引用关系如下图:

    CircularRefrence

    为了避免这个问题Swift采用了和ObjC中同样的概念:弱引用,通常将被动的一方的引用设置为弱引用来解决循环引用问题。例如这里可以将IDCard中的owner设置为弱引用。因为IDCard对于Person的引用变成了弱引用,而Person持有IDCard的强引用,这样一来Person作为主动方,只要它被释放后IDCard也会跟着释放。如要声明弱引用可以使用weak和unowned关键字,前者用于可选类型后者用于非可选类型,相当于ObjC中的__weak和__unsafe_unretained(因为weak声明的对象释放后会设置为nil,因此它用来修饰可选类型)。

    import Foundation

    class Person {
    var name:String
    var idCard:IDCard

    init(name:String,idCard:IDCard){
    self.name = name
    self.idCard = idCard
    idCard.owner = self
    }

    deinit{
    println("Person deinit...")
    }
    }

    class IDCard {
    var no:String
    //声明为弱引用
    weak var owner:Person?

    init(no:String){
    self.no = no
    }

    deinit{
    println("IDCard deinit...")
    }
    }

    func test(){
    var idCard = IDCard(no:"100188888888888888")
    var p = Person(name: "Kenshin Cui",idCard:idCard)
    }

    //注意test执行完之后p会被释放,其后idCard跟着被释放
    test()

    println("wait...")

    现在两个对象之间的引用关系如下图:

    WeakRefrence

    当然类似于上面的引用关系实际遇到的并不多,更多的还是存在于闭包之中(ObjC中多出现于Block中),因为闭包会持有其内部引用的元素。下面简单修改一下上面的例子,给Person添加一个闭包属性,并且在其中访问self,这样闭包自身就和Person类之间形成循环引用。

    import Foundation

    class Person {
    let name:String

    //下面的默认闭包实现中使用了self,会引起循环引用
    lazy var description:()->NSString = {
    return "name = \(self.name)"
    }

    init(name:String){
    self.name = name
    }

    deinit{
    println("Person deinit...")
    }
    }

    func test(){
    var p = Person(name: "Kenshin Cui")
    println(p.description())
    }

    test()

    println("wait...")
    /**打印结果
    name = Kenshin Cui
    wait...
    */

    Swift中使用闭包捕获列表来解决闭包中的循环引用问题,这种方式有点类似于ObjC中的weakSelf方式,当时语法更加优雅, 具体实现如下:

    import Foundation

    class Person {
    let name:String

    //使用闭包捕获列表解决循环引用
    lazy var description:()->NSString = {
    [unowned self] in
    return "name = \(self.name)"
    }

    init(name:String){
    self.name = name
    }

    deinit{
    println("Person deinit...")
    }
    }

    func test(){
    var p = Person(name: "Kenshin Cui")
    println(p.description())
    }

    test()

    println("wait...")
    /**打印结果
    name = Kenshin Cui
    Person deinit...
    wait...
    */

    指针与内存

    除了循环引用问题,Swift之所以将指针类型标识为“unsafe”是因为指针没办法像其他类型一样进行自动内存管理,因此有必要了解一下指针和内存的关系。在Swift中初始化一个指针必须通过alloc和initialize两步,而回收一个指针需要调用destroy和dealloc(通常dealloc之后还会将指针设置为nil)。

    import Foundation

    class Person {
    var name:String

    init(name:String){
    self.name = name
    }

    deinit{
    println("Person\(name) deinit...")
    }
    }

    func test(){
    var p = Person(name: "Kenshin Cui")

    //虽然可以使用&p作为参数进行inout参数传递,但是无法直接获取其地址,下面的做法是错误的
    //var address = &p

    /*创建一个指向Person的指针pointer*/
    //申请内存(alloc参数代表申请n个Person类型的内存)
    var pointer:UnsafeMutablePointer = UnsafeMutablePointer.alloc(1)
    //初始化
    pointer.initialize(p)

    //获取指针指向的对象
    var p2 = pointer.memory
    println(p===p2) //结果:true,因为p和p2指向同一个对象
    //修改对象的值
    p2.name = "Kaoru"
    println(p.name) //结果:Kaoru


    //销毁指针
    pointer.destroy()
    //释放内存
    pointer.dealloc(1)
    //指向空地址
    pointer = nil
    }

    test()

    println("waiting...")
    /**打印结果

    Kaoru
    PersonKaoru deinit...
    waiting...

    */

    运行程序可以看到p对象在函数执行结束之后被销毁,但是如果仅仅将pointer设置为nil是无法销毁Person对象的,这很类似于之前的MRC内存管理,在Swift中使用指针需要注意:谁创建(alloc,malloc,calloc)谁释放。 当然上面演示中显然对于指针的操作略显麻烦,如果需要对一个变量进行指针操作可以借助于Swift中提供的一个方法withUnsafePointer。例如想要利用指针修改Person的name就可以采用下面的方式:

    var p = Person(name: "Kenshin Cui")

    var p2 = withUnsafeMutablePointer(&p, {
    (pointer:UnsafeMutablePointer) -> Person in
    pointer.memory.name = "Kaoru"
    return pointer.memory
    })

    println(p.name) //结果:Kaoru

    在前面的C语言系列文章中有一部分内容用于介绍如何利用指针遍历一个数组,当然在Swift中仍然可以采用这种方式,但是在Swift中如果想要使用指针操作数组中每个元素的话通常借助于另一个类型UnsafeMutableBufferPointer。这个类表示一段连续内存,通常用于表示数组或字典的指针类型。

    import Foundation

    var array:[String] = ["Kenshin","Kaorsu","Tom"]

    //UnsafeBufferPointer和UnsafeMutableBufferPointer用于表示一段连续内存的指针,例如:数组或字典
    //下面创建一个指向数组的指针
    var pointer = UnsafeMutableBufferPointer(start: &array, count: 3)

    //baseAddress属性表示内存首地址
    var baseAddress = pointer.baseAddress as UnsafeMutablePointer
    println(baseAddress.memory) //结果:Kenshin

    //利用指针遍历数组
    for index in 1...pointer.count {
    println(baseAddress.memory)
    //向后移动指针,向前移动使用baseAddress.predecessor()
    baseAddress = baseAddress.successor()
    }
    /**打印结果
    Kenshin
    Kaorsu
    Tom
    */

     扩展—Core Foundation

    Core Foundation作为iOS开发中最重要的框架之一,在iOS开发中有着重要的地位,但是它是一组C语言接口,在使用时需要开发人员自己管理内存。在Swift中使用Core Foundation框架(包括其他Core开头的框架)需要区分这个API返回的对象是否进行了标注:

    1.如果已经标注则在使用时完全不用考虑内存管理(它可以自动管理内存)。

    2.如果没有标注则编译器不会进行内存管理托管,此时需要将这个非托管对象转化为托管对象(当然你也可以使用retain()、release()或者autorelease()手动管理内存,但是不推荐这么做)。当然,苹果开发工具组会尽可能的标注这些API以实现C代码和Swift的自动桥接,但是在此之前未标注的API会返回Unmanaged<Type>结构,可以调用takeUnretainedValue()和takeRetainedValue()方法将其转化为可以自动进行内存管理的托管对象(具体是调用前者还是后者,需要根据是否需要开发者自己进行内存管理而定,其本质是使用takeRetainedValue()方法,在对象使用完之后会调用一次release()方法。按照Core Foundation的命名标准,通常如果函数名中含“Create”、“Copy”、“Retain”关键字需要调用takeRetainedValue()方法来转化成托管对象)。

    当然,上述两种方式均是针对系统框架而言,如果是开发者编写的类或者第三方类库,应该尽可能按照Cocoa规范命名并且在合适的地方使用CF_RETURNS_RETAINED和CF_RETURNS_NOT_RETAINED来进行标注以便可以进行自动内存管理。

    备注:

    1.在Swift中内存的申请除了使用alloc其实也可以使用malloc或者calloc,此时释放时使用free函数;

    2.关于更多内存管理的内容可以参见前面的文章:iOS开发系列http://www.cnblogs.com/kenshincui/p/3870325.html


    转载请注明本文地址:iOS开发系列--Swift进阶
    展开全文
  • objccn-Swift 进阶

    2020-07-14 23:30:46
    objccn 出版的swift 进阶。 讲述了swift 的高级用法。
  • Swift进阶黄金之路(二) 这篇是对一文鉴定是Swift的王者,还是青铜文章中问题的解答。这些问题仅仅是表层概念,属于知识点,在我看来即使都很清楚也并不能代表上了王者,如果非要用段位类比的话,黄金还是合理的???...
  • 集合是极大简化我们工作效率的一种类型,我们可以通过它存储同类型的多条数据(Array);可以存储同类型且唯一的多条数据(Set);也可以存储多条键值对(Dictionary)。学会高效的使用集合,不仅可以提高我们代码的性能,...
  • 期间swift进阶这本书看了三遍。 一开始的全书通读到现在的每小节精读。 想分享一些关于这本书我的一些学习笔记和看法。 目前已经写了七章的笔记了。明天开始会一篇一篇的分享出来。 如果你有更好的想法和观点欢迎...
  • 如果你想要把 你的 Swift 相关知识技能提升到和你原来已经熟知的 Objective-C 或者其他语言的同一水平线 上的话,这本书会非常适合你。本书也适合那些已经开始学习 Swift,对这⻔语言基础有一定了 解,并且渴望再上...
  • Swift进阶之内存模型和方法调度

    千次阅读 2016-11-13 16:07:51
    前言Apple今年推出了Swift3.0,较2.3来说,3.0是一次重大的升级。关于这次更新,在这里都可以找到,最主要的还是提高了Swift的性能,优化了Swift API的设计(命名)规范。前段时间对之前写的一个项目...
  • Swift 编程语言入门课程已经有许多了,但是进阶的还没课程,因为这个原因,录制了该课程。本套视频主要是针对有移动开发基础的,比如对OC语言有所了解,或者对OC项目开发有一定的基础的伙伴们。虽然苹果公司并没有...
  • Swift 进阶”。 写时复制 (昂贵方式) 要实现写时复制,我们首先将 _data 标记为结构体的私有属性。我们不再直接变更 _data,而是通过一个计算属性 _dataForWriting 来访问它。这个计算属性总是会复制 _...
  • swift进阶

    2018-03-03 11:39:14
    1.扩展2.泛型3.guard4.计算属性5.枚举6.空合运算符7.函数式编程8.闭包
  • Swift 进阶

    2019-07-29 23:44:19
    iOS开发系列--Swift进阶 2015-09-21 00:01 by KenshinCui,3072阅读,12评论,收藏,编辑 概述 上一篇文章《iOS开发系列--Swift语言》中对Swift的语法特点以及它和C、ObjC等其他语言的用法区别进行了介绍。当然,...
  • A002 - WWDC 2014 Swift 进阶 WWDC 2014 官方视频 中文翻译 A002.01 可能值 / 可能值绑定 / 可能值链 A002.02 内存管理 / 自动引用计数 / 弱引用 / 非持有引用 A002.03 初始化原则 / 便捷初始化 / 初始化...
  • swift4.0进阶

    2020-07-18 23:30:35
    onevcat喵神作品。学习swift4.0进阶必备。中文版原版。
  • 目前最新的是 swift4.2, 从2014年至今经历4年时间,感觉swift沉淀的也差不多了吧。准备学习并分享一下, 文章会持续更新。有错误的地方请多多指教,... swift 进阶 swift UIKit swift 设计模式 swift 高级(实战)
1 2 3 4 5 ... 20
收藏数 4,445
精华内容 1,778
关键字:

swift进阶