swift4.0_swift4.0 本地缓存 - CSDN
  • 001--swift简史小视频 002--Playground体验 003--常量&变量 一、swift简史 1、介绍   swift是苹果公司于2014年推出用于撰写OS和iOS应用程序的语言。它由苹果开发者工具部门总监“克里斯.拉特纳”在...

    小视频

    001--swift简史小视频

    002--Playground体验

    003--常量&变量

    一、swift简史

    1、介绍

     

    swift是苹果公司于2014年推出用于撰写OS和iOS应用程序的语言。它由苹果开发者工具部门总监“克里斯.拉特纳”在2010年开始着手设计,历时一年完成基本的架构。到后来苹果公司大力投入swift语言的研发,于2014年发布这一语言的第一版本。swift2.0之后的语法则趋于稳定,2017年发布的swift4.0虽有改动,但也只是增添了一些新特性。这些新特性需要在Xcode9上运行才能显示出效果。值得一提的是它支持unicode9,也就是说,可以用某些图片图标来充当变量。

    例如:

    0.png

    2、特点

    • swift取消了预编译指令,宏也被包括在内。某些开发者为了让Objective-C和swift代码兼容,会尽少在Objective-C中定义宏。

    • 取消了Objective-C中的指针等其他不安全访问的使用

    • 使用点语法来调用属性或者函数

    • 去除了NS前缀

    3、为什么要学习swift

    • swift作为面向协议语言,不仅能写移动端,也可以做到搭建服务器端。

    • 纵观国内外iOS开发界,已经有许多公司直接或间接采用swift开发,使用swift语言开发已成为未来iOS开发的趋势。

    • swift以简洁、优雅等优点迅速俘获广大开发者的青睐。

    二、用playground体验swift开发

    打开Xcode,选择创建一个playground项目

    创建一个普通的UIView对象

    正如上图所示,playgound文件的左边是代码区,右边则是显示结果的区域。当点击用于眼睛时会实时显示出界面效果。

    swift与objective-C的重大区别

    • 在swift中是没有.h和.m文件之分的。所有的代码全部都存储在一个文件里面。

    • 在swift中所有的代码都被封装在{}里面

    • OC使用alloc init进行初始化,而swift使用()

    • OC中使用[]来调用方法,而swift中采用点语法。比如UIColor.red

    • swift中不需要用分号分割语句

    三、常量和变量

    1、数据类型

    在swift中也有各种数据类型来存储不同的信息。下表列举的是常见的数据类型变量。

    但其实,在swift中,是不存在基本的数据类型的,所谓的数据类型,其实都只是结构体。这也是swift中的一个特点。

    2、变量和常量

    声明

    swift中用let声明常量,用var声明变量。

    var x = 10;
    let y = 20;
    
    let z   //错误示范,let z 在声明的时候并没有赋值常量是不可改变的,只能在声明时赋值

    在开发中,通常会优先选择使用let,因为不可变会更安全一点。所以建议在写代码之时,先选择let,等到需要变化的时候再改成var。

    自动推导

    创建一个UIView,不指定类型。可以看到控制台上会打印出UIView的信息。这个现象被称为swift的自动推导。事实上,在代码左侧定义的类型只是程序员希望的类型,而右侧才是程序真实的类型。

    let z = UIView()
    print(z)

    也就是说,变量或常量的类型会根据右侧代码执行的结果,推导出对应的类型。

    可以使用热键option点击查看类型。

    swift对类型的严格要求

    在swift中,任何不同类型的数据之间是不允许直接运算的。比如下面这段代码就会报错。

    //错误示范
    let a = 10
    let b = 12.5
    print(x + y)

    如果非要让不同类型数据之间能够运算,可以将其中一个类型进行转换。

    let a = 10
    let b = 12.5
    print(a + Int(b))

    此时得到的结果就是22。在swift中,做类型转换时是将数据括起来,相当于swift结构体中的构造函数。

    当然也可以将前面的整数转换成Double型。此时就能打印出小数来。

    print(Double(a)+b)

    四、String类型和Bool类型

    1、String类型

    声明

    直接用双引号将数据引起来

    let str = "小仙女"
    let str1:String = "hahh"

    拼接

    字符串的连接有两种方法,一种是通过加号来连接,另一种则是通过反斜杆进行插入。

    let str = "小仙女"
    let mesg1 = "one"+str //用加号的方式
    let mesg2 = "two,\(str)" //反斜杠的方式
    print(mesg1,mesg2)

    在做字符串拼接时要注意加号和反斜杠后面都不能出现空格,不然会报错。

    拼接字符串时格式的变化

    假设在某些特定的地方需要输出特定位数的字符,比如或时间的输出,就需要使用占位符来调整字符串的格式。使用String的构造函数,调用format方法,%0后面加上数字就表示需要占多少位数。

    let min = 2
    let second = 10
    
    String(format: "d:d", min,second)

    遍历

    调用字符串的characters属性,采用for...in...的方式来遍历字符串。

    for c in str{
        print(c)      //swift4中的遍历
    }
    print(str.count)  //打印字符串长度
    
    for char in myString.characters {
        print(char)   // swift3的遍历
     } 
    print(str..characters.count)  //swift3打印字符串长度

    字符串的截取

    最方便的方式就是将String类型转换成OC的NSString类型,再来截取。

    let urlStr = "www.baidu.com"
    let header = (urlStr as NSString).substring(to: 3)  //截取前三位
    
    let middle = (urlStr as NSString).substring(with: NSMakeRange(4, 5))//去除前四个字符截取,范围之后五位字符
    
    let footer = (urlStr as NSString).substring(from: 10)   //从第十个字符开始截取

    2、Bool类型

    与其他语言一样,Bool类型表示的就是真假,但是不同于Objective-C,swift中用true和false来表示真假。

    五、可选类型

    在Objective-C开发中,如果一个变量暂时不会使用到,可以将它赋值为0或者赋值为空,而在swift中,nil是一个特殊的类型,如果它和真实类型不匹配是不能进行赋值的。但是开发中将变量赋值为空是在所难免的事情,因此就推出了可选类型。

    可选类型是swift的一大特色,在定义变量时,如果指定这个变量是可选的话,就是说这个变量可以有一个指定类型的值或者为nil。

    1、定义一个optional的变量

    let x:Optional = 10
    print(x)

    点击进去查看,可以发现Option其实是一个枚举类型。这个枚举有两个值,一个是none,表示没有值,而另一个是some,表示某一类值。

    在输出的时候,可以看见控制台上的内容Optional(10),它的作用就是提示这是一个可选值。

    而在实际开发中,一般不用上述方式创建可选值,而是指定一个类型,再在其后添一个问号。

    let x:Optional = 10  //第一种写法
    
    let x:Int? = 20     //第二种写法
    print(x)

    上述代码问号的意思就是定义一个可选的Int类型,可能没有值,也可能有一个整数。

    2、 解包

    试试将上面案例x和y相加,这个时候还能输出结果么?

    此时可以看到编译器已经报错。在前面的教程中提到过,不同类型的值是不能直接运算的。而可选项有两种值的产生,若它的值为nil则不能参加计算。

    因此引入解包的概念,“!”代表强制解包。它的意思是从可选值中强行获取对应的非空值。

    print(x!+y!)

    3、解包常见错误

    //错误示范1
    let y : Int?
    print(y)

    使用let定义的是常量,在初始化时必须要给出值。

    //错误示范2:
    let y : Int? = nil
    print(y)

    强制解包是危险操作,如果可选值为nil,强制解包系统会奔溃。

    4、let和var的可选项默认值

    //默认值测试
    let x: Int?
    print(x)
    var y :Int?
    print(y)

    用let做测试时会直接报错,说明let的可选值是没有默认值的,而用var做测试时,报错信息就变成了警告,运行的结果为nil。可以由此推测出var的可选项默认值为nil。

    swift中有规定,对象中的任何属性在创建对象时,都必须有明确的初始化值。

    5、可选绑定

    用if let/var表示。它将变量赋值给一个临时变量,在这个操作中会做两步操作:首先判断变量是否有值,如果没有值,则直接不执行大括号里面的内容;如果有值,系统会自动将变量进行解包,并且将解包后的结果,赋值给临时变量。

    比如下面这个例子:

    通过一个字符串创建NSURL对象

    let url: URL? = URL(string: "https://www.baidu.com")

    接着创建NSURLRequest对象。强制解包非常危险,当url有中文的时候可能会变成nil。所以要判断url是否为空再对其进行解包。

    if let url = url {
        let request = URLRequest(url: url)
    }

    六、swift中的分支

    1、if语句

    在swift中,if语句是不用带小括号的,但是后面跟的语句必须有花括号,哪怕只有一行代码。许多公司的代码规范也是规定必须使用这一格式。

    注意:在swift中没有非0即真的说法,所以不能写成if(num)这样的格式。

    let x = 9
    if x > 5 {
        print("小仙女")
    }else{
        print("妖精哪里跑")
    }

    2、三目运算符

    三目运算符的写法是表达式后跟一个问号,用冒号来隔开条件是否成立的值。

    let x = 10
    x > 5 ? print("小仙女"):print("妖精")

    非常有意思的是,如果开发者只想处理条件成立的部分,此时可以在冒号后面用一个小括号来代替条件不成立的部分。

    x > 5 ? print("你都写了我两次啦"):()

    3、 三目运算符的简单模式

    三目运算符的简单模式通常是用于处理可选项的。“??”的意思是说,如果表达式有值,就使用那个值,如果没有,就使用“??”后面的值来代替。

    let x:Int? = nil
    let y:Int? = 9
    print((x ?? 0) + (y ?? 0))

    运行之后的结果为9。

    之后再来说说运算符的优先级。举个简单的栗子

    let name:String? = "安琪拉"
    print((name ?? "") + "火烧屁屁咯")
    print(name ?? "" + "火烧屁屁咯")

    从运行的结果可以看到,“??”的优先级是最低的。如果没有小括号的约束,它会将后面的语句都当成是一个表达式。

    4、 guard的用法

    分支若是写得过多,就会导致代码可读性较差的问题。为了降低代码的层次,swift推出了guard。guard后面跟判断表达式,else后面写表达式不成立的代码。

    需要注意的是guard必须写在函数内部,在最末尾出必须要跟关键字return/continue/break/throw中的一种。

    import UIKit
    let age = 20
    func online(age : Int){
        guard age >= 18 else {
            print("还未成年呢")
            return
        }
        print("一起来开黑吖")
    }

    这样或许看不到guard的特别之处,但若是像下面这样的代码出现呢?

    let age = 20
    let money = true
    let idcard  = true
    func online2(age : Int,money:Bool,idcard:Bool){
        if age >= 18 {
            if money {
                if idcard {
                    print("一起来开黑吖")
                }else{
                    print("回去带身份证吧")
                }
            }else{
                 print("回去拿钱")
            }
        }else {
            print("还未成年呢")
        }
    }
    //调用
    online2(age: age, money: money, idcard: idcard)

    如果用普通的分支方法,就会显得可读性太差。我们可以试着将它改成guard的写法。

    func online1(age : Int){
        //判断年龄
        guard age >= 18 else {
            print("还未成年呢")
            return
        }
        //判断是否有钱
        guard money else {
            print("回去拿钱")
            return
        }
        //判断是否带了身份证
        guard idcard else {
             print("回去带身份证吧")
            return
        }
        print("一起来开黑吖")
    }

    执行完所有的判断语句之后才执行代码库,阅读性也比if……else分支强。

    5、 switch

    最基本的用法

    switch后面的小括号可以省略。用case关键字来表示不同的情形,case语句结束后,break也可以省略。

    let sex = 0
    switch sex {
    case 0:
        print("男")
    case 1:
        print("女")
    default:
        print("其他")
    }

    基础语法的补充

    如果系统某一个case中产生case穿透,可以在case结束后跟上fallthrough

    case  0:
        print("男")
        fallthrough

    case后面可以判断多个条件,这些条件以逗号分开

    let sex = 0
    switch sex {
    case  0,1:
        print("正常人")
        
    default:
        print("其他")
    }

    switch可以判断浮点型、字符串类型和Bool类型

    switch 3.14 {
    case  0:
        print("正常人")
        
    default:
        print("其他")
    }
    let opration = "+"
    switch opration {
    case  "+":
        print("加法")
    case "-":
        print("减法")
    default:
        print("其他")
    }

    七、swift的for循环和表示区间

    1、变化

    在swift3开始,就已经弃用了var i = 0; i < 10; i++的这种写法。并且++这种写法也被取消掉了,改为+=代替。

    2、表示区间

    swift常见区间有两种,开区间用..<表示,闭区间用...表示。要注意的是数字和省略号之间是不能加空格的。

    func demo1() {
        for i in 0..<5 {
            print(i)
        }
        print("^^^^^^^")
        
        for i in 0...5 {
            print(i)
        }
    }
    demo1()

    3、逆序操作

    如果想要做逆序操作,只要在in后面的表达式后添加reversed()即可。

    func demo1() {
        for i in (0..<5).reversed() {
            print(i)
        } 
    }
    demo1()

    八、swift中的数组

    Swift语言提供了Arrays、Sets和Dictionaries三种基本的集合类型用来存储集合数据。数组是有序数据的集,集合是无序无重复数据的集,而字典则是无序的键值对的集。

    数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。

    1、定义数组

    用let定义出来的数组就是不可变的

    //定义不可变数组
    let array = ["爱丽丝","小红帽","白雪公主"]

    使用var来定义可变数组。正确的写法是Array这样的形式。其中Element是这个数组中唯一允许存在的数据类型。但是为了简便,推荐使用[Element]()的写法。

    //定义可变数组
    var arrayM = [String]()
    var arrayM1:[String]
    var arrayM2 = Array()

    2、创建带有默认值的数组

    swift中的array类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。开发者可以在里面指定它的数量和类型。

    var threeDouble = Array(repeating: 0.0, count: 3)
    print(threeDouble[1])

    3、对可变数组的基本操作

    使用append给数组添加元素

    arrayM.append("1")
    arrayM.append("2")
    arrayM.append("3")
    arrayM.append("4")
    arrayM.append("5")

    使用insert方法将值添加到具体索引值之前

    arrayM.insert("10", at: 2)

    使用remove系列方法可以对数组做删除操作

    arrayM.remove(at: 0)
    arrayM.removeSubrange(1..<3)
    arrayM.removeAll()
    arrayM.removeLast() //可以去除最后一项,避免捕获数组count属性

    通过取下标的方式对数组进行修改和查找

    arrayM[0] = "小红帽"
    print(arrayM[2])

    利用区间对具体范围内的值替换

    //替换第2项和第3项的值
    arrayM[2...4] = ["22","33"]
    print(arrayM[3])

    4、数组的遍历

    1.png

    若同时需要每个数据项的值和索引,可以使用数组的emumerated()方法来进行数组遍历。

    for(index,value) in arrayM.enumerated(){
        print(String(index+1)+":"+value)
    }

    5、数组的合并

    只有相同类型的数组才能进行合并。

    let resultArray = arrayM + array

    九、swift中的集合

    集合(Set)用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。

    集合中的元素必须有确定的hashvalue,或者是实现了hashable协议。而swift提供的Int,String等类型其实都是实现了hashable协议的。hashable是equable的子协议,如果要判断两个元素是否相等,就要看他们的hashvalue是否相等。

    1、定义集合

    使用set定义。

    Element表示集合中允许存储的类型,和数组不同的是,集合没有等价的简化形式。

    //创建空集合
    var letters = Set()
    //使用字面量创建集合
    var favorite:Set = ["绮罗生","意琦行"]

    要注意的是一个Set类型是不能直接后面跟的字面量被单独推断出来的,因此这个Set是必须要显示声明的。但是由于swift的自动推断功能,可以不用写出Set的具体类型。比如说上面那个例子,省去String,也能推断出Set的正确类型。

    var favorite:Set = ["绮罗生","意琦行"]

    2、访问和修改集合

    通过.count属性知道集合的长度,通过isEmpty判断集合是否为空。

    3、添加元素

    favorite.insert("寒烟翠")
    print(favorite.count)

    4、删除元素

    通过remove的方法删除元素,若这个值真的存在就会删除改值,并且返回被删除的元素。若集合中不包含这个值,就会返回nil。

    if let removeBack = favorite.remove("意琦行"){
        print(removeBack)
    }else{
        print("没有找到值")
    }

    5、集合操作

    swift提供了许多数学方法来操作集合。

    print(oddD.union(evenD).sorted()) //并集
    print(oddD.intersection(evenD).sorted())//交集
    print(oddD.subtracting(siggleDPrime).sorted())//取差值
    print(oddD.symmetricDifference(siggleDPrime).sorted())//去掉相同值

    6、遍历集合

    for item in favorite {
        print(item)
    }
    //按照首字母的顺序输出
    for item1 in favorite.sorted() {
        print(item1)
    }

    7、集合的成员关系

    用 ==来判断两个集合是否包含全部相同的值

    用 isSubset(of:)来判断一个集合中的值是否也被包含在另外一个集合中

    用 isSuperset(of:)来判断一个集合中包含另一个集合所有的值

    用isStrictSubset(of:)或者isStrictSuperset(of:)方法来判断一个集合是否是另外一个集合的子集合或父集合并且两个集合不相等

    十、字典

    字典是一种存储多个相同类型的值的容器。每个值value都关联这唯一的键key。键就是这个字典的标识符。而且字典中的数据项并没有具体顺序。键集合不能有重复元素,而值集合是可以重复的。

    1、定义字典

    使用let定义不可变的字典,使用var定义可变字典。用字面量赋值时,系统会自动判断[]中存放的是键值对还是要一个个的元素。

    let dict = [1:"one",2:"two",3:"three"]  //定义不可变字典
    
    var dictM = Dictionary()  //定义可变字典
    var dictM1 = [String:NSObject]()
    
    //AnyObject一般用于指定类型,NSObject一般用于创建对象

    2、对可变字典做基本操作

    添加、删除和获取元素

    dictM1["name"] = "小仙女" as NSObject
    dictM["age"] = 17 as NSObject
    dictM.removeValue(forKey:"name")
    //获取:swift中只保留了最简单的写法,OC中有objectforkey的方法在swift中也被删除掉了。
    dictM["name"]

    3、修改元素

    若字典中已经有对应的key,操作的结果是直接修改原来的key中保存的value。若字典中没有对应的key,则会添加新的键值对。

    dictM["name"] = "llx"

    4、遍历字典

    可以通过范围for遍历所有的key和value。也可以遍历所有的键值对。

    for (key,value) in dictM {
        print(key)
        print(value)
    }

    5、合并字典

    合并字典时通过遍历的方式将第二个字典的内容添加到第一个字典中。绝对不能用相加的方式对字典进行合并。

    var dict1 = ["name":"llx","age":"17"]
    var dict2 = ["num":"007"]
    
    for (key,value) in dict2 {
        dict1[key] = value
    }
    dict

    十一、元组

    元组是swift中特有的一种数据结构,用于定义一组数据,元组在数学中的应用十分广泛。

    1、定义元组

    使用()包含信息,组成元组类型的数据可以被称为“元素”。

    //使用元组来描述个人信息
    let info1 = ("1001","张三",30)

    2、起别名

    可以给元素加上名称,之后可以通过元素名称访问元素

    //给元素加上名称,之后可以通过元素名称访问元素
    let info2 = (id:"1001",name:"张三",age:30)
    info2.name

    元组一般用于作为方法的返回值。元组中元素的别名,就是元组的名称

    let (name,age) = ("张三",18)
    name

    十二、函数

    函数相当于Objective-C中的方法,是一段完成特定任务的独立代码片段。可以通过给函数命名来标志某个函数的功能。而这个名字可以用来在需要的时候“调用”该函数完成其任务。格式如下:

    func 函数名(参数列表)-> 返回值类型 {
        代码块
        return 返回值
    }

    func表示关键字,多个参数列表之间用逗号隔开,也可以没有参数。使用->指向返回值类型。如果没有返回值,可以用Void代替,也可以省略。

    1、定义无参无返回的函数

    func phone()->Void {
        print("小米")
    }
    phone()

    2、定义有参无返回的函数

    func phoneNum() -> String {
        return "123456"
    }
     print(phoneNum())

    3、定义有参无返回的函数

    func callPhone(phoneNum:String){
        print("打电话给\(phoneNum)")
    }
    callPhone(phoneNum: "123456")

    4、定义有参有返回的函数

    func sum(num1 : Int,num2 : Int) -> Int{
        return num1 + num2
    }
    sum(num1: 30, num2: 30)

    在swift4之后,调用函数的时候,能直观的看到参数。而在之前调用之时,只能看见第二个参数之后的名称,表达起来并不直观。如何解决这个问题呢?

    可以采用给参数起别名的方式,在参数前面添加一个别名。

    func sum(number1 num1: Int,number2 num2 : Int) -> Int{
        return num1 + num2
    }
    sum(number1: 2, number2: 4)

    5、默认参数

    在swift中可以给方法的参数设置默认值。比如说买甜筒的时候,商店默认会给顾客准备原味冰淇淋。但是用户也可以选择指定口味。

    func makeIceCream(flavor:String = "原味") -> String {
        return "制作一个\(flavor)冰淇淋"
    }
    makeIceCream()
    makeIceCream(flavor: "抹茶")

    6、可变参数

    有些时候,在创建方法的时候,并不确定参数的个数,于是swift推出了可变参数。参数的类型之后使用...表示多个参数。

    func sum(num:Int...) -> Int {
        var result = 0
        for i in num {
            result += i
        }
        return result
    }
    sum(num: 18,29,3)

    7、引用传递

    如果现在有这样一个需求:要交换两个数的值,不能使用系统提供的方法。要如何来完成呢?

    如果按照上面的写法就会报错,可以按住option键查看,参数默认是不可变的。

    而且就算可行,做到的也是值传递。为了解决这一问题,swift提供了关键字inout来声明数据地址传递,也被称之为引用传值。在swift3.0的时候,inout的位置发生了改变,被放置在标签位置。但是作用与之前相同。

    func swapNum1( m : inout Int, n : inout Int) {
        let tempNum = m
        m = n
        n = tempNum
    }
    swapNum1(m: &m, n: &n)
    print("m:\(m),n:\(n)")

    十三、类

    swift用关键字class来定义类。通常情况下,定义类时,让它继承自NSObject,若没有指定父类,那么该类就是rootClass。类的格式如下:

    class 类名:SuperClass {
        //定义属性和方法
    }

    1、定义存储属性和创建类对象

    对象的属性必须要赋值,用解包的方式赋值为nil。

    class Person : NSObject {
        //定义存储属性
        var age : Int = 0
        var name : String? //对象的属性必须赋值,不赋值会报错的哦
    }
    let p = Person()

    2、给类的属性赋值

    可以直接赋值,也可以通过KVC进行赋值

    p.age = 10
    p.name = "llx"
    if let name = p.name {
        print(name)
    }

    3、定义方法

    在swift中,如果使用当前某一对象的属性或者方法,可以直接使用,不需要加self

    // 定义方法,返回平均成绩
    func getAverage() -> Double {
            return (mathScore + EnglishScore)*0.5
        }

     

    let average = p.getAverage()

    4、定义计算属性

    通过别的方式计算到结果的属性,称之为计算属性。

    var averageS : Double {
            return (mathScore + EnglishScore) * 0.5
        }

    5、定义类属性

    类属性是和整个类相关的属性,用static修饰,作用域是整个类。通过类名进行访问。

        static var courseCount : Int = 0

    在类外通过类名访问类属性

    Person.courseCount = 2

    6、类的构造函数

    构造函数类似于OC中的init方法。默认情况下创建一个类时,必定会调用一个构造函数。如果一个类继承自NSObjct,可以对父类的构造函数进行重写。

    在构造函数中,如果没有明确super.init()。那么系统会默认调用super.init()。

    class Person : NSObject {
        var name : String?
        var age : Int = 0
        
        override init() {
            print("hello world")
        }
    }
    let p = Person()

    7、自定义构造函数

    自定义构造函数可以传入参数,做赋值操作时采用self调用属性以示区分。

    class Person : NSObject {
        var name : String?
        var age : Int = 0
        
        // 自定义构造函数
        init(name:String,age:Int){
            self.name = name
            self.age = age
        }
    }
    // 调用自定义的构造函数
    let p1 = Person(name: "kaka", age: 12)
    print(p1.age)

    可以定义字典类型的构造函数。用KVC的方式将字典的值取出来,要调用系统的setValue方法就必须先调用系统的构造函数创建出对象。为了防止取出的对象没有属性而导致程序奔溃,需要重写系统的setValue方法。

    如果用KVC的方式一定要先调用父类的构造函数。因为系统默认调用是放在方法最后面调用的。

    class Person : NSObject {
       @objc var  name : String?
       @objc var age : Int = 0
            
        init(dict:[String : Any]) {
            super.init()
       // 要调用系统的`setValue`方法就必须先调用系统的构造函数创建出对象
            setValuesForKeys(dict)
        }
        // 防止奔溃
        override func setValue(_ value: Any?, forUndefinedKey key: String) {
        }
    }
    
    let p2 = Person(dict:["name":"lala","age":18,"score":33])
    p2.name
    p2.age

    由于swift与objective-c的编译方式不同,用KVC字典转模型构造函数时,需要在属性前面加上@objc。

    8、类的属性监听器

    在object-c中,我们可以重写set方法来监听属性的改变,而在swift中也可以通过属性观察者来监听和响应属性值的变化。通常用于监听存储属性和类属性的改变。对于计算属性则不需要定义属性观察者,因为我们可以在计算属性的setter中直接观察并响应这种值的变化。

    可以通过设置以下观察方法并响应这种值的变化。

    willSet:在属性值被存储之前设置,此时新属性值作为一个常量参数被传入。该参数名默认为newValue,开发者可以自己定义该参数名。

    didSet:在新属性值被存储后立即调用,与willSet不同的是,此时传入的是属性的旧值,默认参数名为oldValue。

    上面两个方法都只有在属性第一次被设置时才会调用,在初始化时,不会去调用这些监听方法。

    class Person : NSObject {
        //属性监听器
        var name:String? {
            willSet {
                print(name as Any)
                //如果想要查看接下来的新值,可以使用newValue
                print(newValue as Any)
            }
            didSet {
               print(name as Any)
            }
        }
    }
    
    let p = Person()
    p.name = "llx"

    十四、闭包

    闭包是swift中非常重要的一个知识点。类似于objective-c中的block,其实函数就相当于一个特殊的闭包。闭包需要提前写好,在适当的时候再执行。

    1、定义闭包

    闭包的格式是(参数列表)->(返回值类型) in 实现代码

    举一个最简单的栗子 

    用常量记录一个代码块,按住option键就能看到,b1是一个闭包。再到适合的地方去调用它。

    let b1 = {
      print("干掉他们")
    }
    b1()

    再来看一个带参数的闭包。在闭包中,参数、返回值和实现代码都是写在花括号里面的。in是用来定义分割和实现的。

    let b2 = {
        (x:String)->() in print(x)
    }
    
    b2("string")

    2、闭包案例

    这个案例要模拟封装一个网络请求的类。利用闭包将jsonData类型的数据传递给展示页面。

    创建一个新的项目,选择swift语言

    封装一个网络请求的类HttpTool.swift继承自NSObject

    用异步线程模拟网络数据请求,再回到主线程中回调闭包

    class HttpTool: NSObject {
        //闭包类型:(参数列表)->(返回值类型)
     
        func loadData(callback:@escaping(_ jsonData : String)->()) {
            DispatchQueue.global().async {
                print("发生网络请求:\(Thread.current)")
            }
           
            DispatchQueue.main.async {
                ()->Void in
                print("获取到数据,并且回调:\(Thread.current)")
               
                callback("jsonData数据")
            }
        }
    }

    到需要接收数据的界面定义Httptool类的属性,设置一个初始化值,将初始值赋值给变量

    在swift中是不需要引入头文件的,文件之间可共享

    import UIKit
    
    class ViewController: UIViewController {
    
        var tools : HttpTool = HttpTool()
       
        override func touchesBegan(_ touches: Set, with event: UIEvent?) {
            //用闭包将json数据拿到
            tools.loadData { (jsonData) ->() in
                print("在viewcontroller中拿到数据\(jsonData)" )
            }
        }
    }

    3、尾随闭包

    尾随闭包用于需要将一个很长的闭包表达式作为最后一个参数传递给函数。也就是说如果按时的最后一个参数是闭包,那么在调用它的时候就可以把这个闭包写在括号外面,并紧跟括号,函数的其他参数则仍然写在括号之中。

    //这个函数接受一个String和一个闭包
    //函数体内调用闭包,并且将String作为参数传递给闭包
    func myFunc(strP:String,closeP:(String)->Void) {
        closeP(strP)
    }
    
    //普通调用
    myFunc(strP: "hello", closeP: {(string) in print(string)})
    //尾随闭包
    myFunc(strP: "hello") {
        (string) in print(string)
    }

    4、逃逸闭包

    当一个闭包作为参数传到一个函数中,但是该闭包要在函数返回之后才被执行,于是就称这样的闭包为逃逸闭包。也就是说闭包逃离了函数的作用域。写法是在这个闭包参数前加一个@escaping用来指明这个闭包是允许逃逸出该函数的。

    声明一个方法,这个方法是一个逃逸闭包

    该方法要做的事情,就是将闭包添加到数组中去

     //定义数组,里面的元素都是闭包类型的
    var callBackArray : [()->Void] = []
    
    //定义一个接收闭包的函数
    func testEscapingClosure(callBack:@escaping ()-> Void) {
        callBackArray.append(callBack)
    }

    当改变数组的时候,取第0个元素调用。此时就改变了变量x的值

    class SomeClass {
        var x = 10
        
        func doSomething(){
            testEscapingClosure {
                self.x = 100
            }
        }
    }
    
    let instance = SomeClass()
    instance.doSomething()
    print(instance.x)
    callBackArray.first?()
    print(instance.x)

    因为逃逸闭包是函数执行之后才会执行,所以可以这样理解:创建一个类的对象instance;在对象中初始化一个x=10;利用对象执行了函数doSomething;函数内部调用全局函数testEscapingClosure,期望修改instance对象的x值为100,但是此时并没有执行这个包含了赋值语句的闭包。

    查找全局数组callBackArray,找到里面第一个元素,显然找到的是在testEscapingClosure函数中添加的闭包{self.x = 100},此时才通过全局数组的查询找出闭包并执行,于是x此时才被赋值为100。这就是在函数执行完毕后才执行闭包。刚好符合逃逸闭包的定义。

    结论: 逃逸闭包将在函数执行之后执行,于是这段代码最后输出为100是因为闭包最后才被执行……

    解决循环引用的三种方式

    1、可以使用weak关键字将对象之间的联系变为弱引用

    weak var weakself = self

    2、第一种方式的简化

    [weak self]

    3、使用unowned解决

    [unowned self]

    但是该方法十分危险,要确保数据一定有值。否则会发生奔溃。

    __weak 与__unretained有何区别?

    • __weak修饰的弱引用,如果指向的对象被销毁,那么指针会立马指向nil

    • __unretained修饰的弱引用,如果指向的对象被销毁,它的指针依然会指向之前的内存地址,很容易产生野指针(僵尸对象)

    十五、tableView的用法

    1、 懒加载

    swift中也有懒加载的方式,并且在swift中有专门的关键字lazy来实现某一个属性实现懒加载。

    格式:lazy var 变量:类型 = {创建变量代码}()

    懒加载的本质在第一次使用的时候执行闭包,将闭包的返回值赋值给属性,并且只会赋值一次。

    //懒加载只能用于结构体或者类的成员变量中
    class Person:NSObject {
        lazy var array : [String] = {
            ()->[String] in
            return ["llx","lll"]
        }()
    }

    2、tableView的使用

    使用步骤如下:

    创建tableView对象

    使用懒加载的方式,到需要用到的时候再创建tableView。将tableView添加到控制器上的View。

    class ViewController: UIViewController {
       
        lazy var tableView:UITableView = UITableView()
        override func viewDidLoad() {
            super.viewDidLoad()
            view.addSubview(tableView)
           
            }
    }

    设置tableView的frame

    tableView.frame = view.bounds

    设置数据源和代理

    实现UITableView的协议,并为tableView设置数据源

    class ViewController: UIViewController ,UITableViewDataSource,UITableViewDelegate{
       
        lazy var tableView:UITableView = UITableView()
        override func viewDidLoad() {
            super.viewDidLoad()
          
            view.addSubview(tableView)
            tableView.frame = view.bounds
            //设置数据源
            tableView.dataSource = self
            tableView.delegate = self
        }
    }

    实现代理方法

     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 20
        }

    创建cell。因为cell是个可选类型,有可能有值,也可能为nil。所以要进行判断。给cell设置数据的时候,选择textLabel点击option会发现textLabel也是可选类型。

    在最后返回cell的时候,对cell进行强制解包。因为之前已经做过判断,所以不会出现程序奔溃的问题。

     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let CellID = "CellID"
            var cell = tableView.dequeueReusableCell(withIdentifier: CellID)
            if cell == nil {
                cell = UITableViewCell(style: .default, reuseIdentifier: CellID)
            }
            cell?.textLabel?.text = "测试数据:\(indexPath.row)"
            return cell!
        }

    实现点击的代理方法

     func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            print("点击了:\(indexPath.row)")
        }

    十六、swift中的注释

    在swift中,类似于paramg --mark的写法是不可行的。

    它是如下两种形式

    //MARK:- 要写的内容 用于分组

    class ViewController: UIViewController ,UITableViewDataSource,UITableViewDelegate{
        // MARK:- 懒加载
        lazy var tableView:UITableView = UITableView()
        // MARK:- 系统回调函数
        override func viewDidLoad() {
            super.viewDidLoad()
            }
        }

    这样写的话,就可以在菜单栏看到分组的信息

    /// 提示信息 用于提示

    若在tableView系列的某个方法上面写上///提示,到其他地方调用该方法时,会出现前面写的注释信息。

    十七、枚举

    1、定义

    在swift中,枚举使用的是由enum关键字来创建的枚举,枚举的所有成员都放在一对大括号里面。它为一组相关的值定义一个共同的类型。使用case关键字来定义一个新的枚举成员值。

    enum SomeEnum {
        // 在这里定义枚举
        case north
        case south
        case east
        case west
    }

    上面这个枚举定义的东南西北四个值就是这个枚举的成员值。与C语言和objective-c不同的是,swift的枚举成员值在创建的时候并不会被赋予一个默认的整形值。这些值的类型就是刚刚定义好的枚举的名字SomeEnum。

    如果希望多个成员值要写在同一行中,可以使用逗号将他们分割开。

    enum Plant {
        case mercury,earth,mars
    }

    每个枚举都定义了一个新的类型,就像swift中的其他类型一样。此时可以把它赋值给一个变量,而且可以用点语法这种形式调用。

    var directionT = SomeEnumeration.west
    
    directionT = .east

    注意:在switch中使用枚举值的时候,一定要穷举出所有的情况,如果忽略其中的一个,代码都无法编译通过。因为它没有考虑到枚举类的全部成员。如果说不需要匹配所有的枚举成员,可以提供一个default分支来涵盖其他未明确处理的枚举成员。

    class Person:NSObject{
        var directionT = SomeEnum.west
     
        func direc()  {
            switch directionT {
            case .north:
                print("north")
            case .east:
                print("east")
            default:
                print("没有方向")
            }
        }
    }

    2、关联值

    可以定义swift的枚举类存储任意类型的关联值,而且每个枚举成员的关联值类型都可以不相同。比如说,来创建一个条形码类型。类似于库存,可以有不同类型的条形码去识别商品,比如说通过数字,或者根据产品代码来识别。

    enum BarCode {
        case upc(Int,Int,Int,Int)
        case qrCode(String)
    }

    上面代码可以理解为定义一个名为BarCode的枚举类型。它的一个成员值是一个具有(Int,Int,Int,Int)类型关联值的upc,另一个成员值是具有String类型的qrCode

    之后可以使用任意的条形码类型去创建新的条形码

    class Person:NSObject {
        // 创建一个名为pBar变量,并将Barcode.upc赋值给它。
        func function() {
            var pBar = BarCode.upc(9, 0, 3, 3)
            pBar = .qrCode("ABCD")
        } 
    }

    这个时候原来的barcode.upc和其整数关联值被新的Barcode.qrCode和其字符串关联值所替代了。

    3、枚举的原始值

    枚举的原始值就是枚举的默认值,这些原始值的类型必须相同。在定义枚举的时候必须给出类型。

    enum ASCIICHAR : Character {
        case tab = "\t"
        case lineFeed = "\n"
        case carriageReturn = "\r"
    }

    在使用原始值为整数或者字符串类型的枚举时,不需要显式的为每一个枚举成员设置原始值,swift将会自动未它们赋值。

    enum Planet : Int {
        case mercury = 1, venus,earth,mars
    }

    上面这个例子,Planet.mercury原始值是1,那么后面的venus就是2,之后以此类推。

    可以通过rawValue属性来访问枚举变量的原始值.

    let earthsOrder = Planet.earth.rawValue

    4、枚举递归

    枚举成员的关联值为当前枚举类型时称为递归枚举。那我们可以通过使用indirect修饰枚举变量。indirect修饰整个枚举时,所有成员均可递归(也可不递归)

    indirect enum Ari {
        case number(Int)
        case addition(Ari,Ari)
        case multi(Ari,Ari)
    }

    上面定义的枚举类型可以存储三种算术表达式:纯数字、两个表达式相加、两个表达式相乘。

    let five = Ari.number(5)
    let four  = Ari.number(4)
    let sum = Ari.addition(five, four)
    let product = Ari.multi(sum, Ari.number(2))

    通过枚举递归,就成功的创建了一个(5+4)*2的式子。

    十八、结构体

    结构体通过struct去声明。在swift中,用到了大量的结构体,比如说基本的数据类型都是结构体而不是类。这意味着它们被赋值给新的常量或者变量,或者被传入函数或方法中时,值会被拷贝。

    struct teacher {
        var name : String = ""
        var age : Int  = 30
    }

    十九、扩展

    扩展 (Extension)可以做到无需修改原本的代码就直接把想要的功能实现。

      extension 某个现有的class {
        //添加新功能
      }

    限制:

    • 不能添加任何已存在的 法或是属性

    • 添加的属性不能是存储属性,只能是计算属性

    1、扩展在方法中的应用

    extension String {
        func sayHello() {
            print("Hello from extension")
        }
    }

    上面这段代码是对String做了一个扩展。之后声明一个变量调用扩展方法。

    var hello = "hi"
    hello.sayHello()

    此后,任何String类型都可以调用该扩展方法。

    2、用扩展进行计算

    extension Int {
        var squared : Int {
            return (self * self)
        }
    }

    上面这段代码对Int扩展了一个属性,让它计算一个数字的平方值。

    var newInt = 30
    newInt.squared
    999.squared

    3、扩展类或结构体

    创建一个普通类

    class Lisa {
        var lisa = "半边天使"
    }

    对类扩展,新增一个方法,使其能做自我介绍

    extension Lisa {
        func describe() -> String {
            return "我可是会傲娇的"
        } 
    }

    创建对象调用方法

    二十、泛型

    泛型可以让开发者写出灵活可重复使用的方法跟结构。

    先看一个栗子!!

     var stringArray = ["Hi", "Hello", "Bye"]
     var intArray = [1,2,3]
     var doubleArray = [1.1,2.2,3.3]

    上面创建了三个不同类型的数组,若是要求打印所有数组中的元素,通常会怎么做呢?

    func printStringFromArray(a: [String]) {
          for s in a {
    print(s) }
    }
      func printIntFromArray(a: [Int]){
          for i in a {
    print(i) }
    }
      func printdoubleFromArray(a:[Double]) {
          for d in a {
    print(d) }
    }
      printStringFromArray(a: stringArray)
      printIntFromArray(a: intArray)
      printdoubleFromArray(a: doubleArray)

    上面这段冗长的代码实在让人不忍直视。而泛型的出现正好可以解决这一问题。

    func printEelementFormArray(a:[T]){
        for element in a {
                  print(element)
              }
          }

    这段代码中的T代表了任意的元素。无论上面类型的数据都能放入其中。之后只要调用者一个方法,传入不同的数组就能将不同类型的元素打印出来。

    二十一、协议

    1、对面向对象语言的吐槽

    • 使用子类时,协议继承父类的属性和方法。其中某些方法或属性并不是开发者所需要的。这会让代码变得异常的臃肿。

    • 若一个类拥有很多父类,会让开发者很难找到每个类中的问题并进行修改。

    • 对象引用到内存的同一地方,若是发生改变,可能会造成代码混乱的现象。

    而swift是一种面向协议的语言。协议其实就像篮球教练,会告诉选手如何去训练,但是教练本身并不会出现在球场。Swift中的protocol不仅能定义方法还能定义属性,配合extension扩展的使用还能提供一些方法的默认实现,而且不仅类可以遵循协议,现在的枚举和结构体也能遵循协议了。

    2、一个简单的协议案例

    创建一个简单的协议,并让一个结构体去遵循

    遵循协议的方法与继承类似。

    protocol People {    
    }
    
    struct Lisa: People {    
    }

    完善协议

    给协议添加一些属性和方法,用get set 设定协议的状态。遵循协议时要了解变量是否能读取或赋值。

    protocol People {
        var name: String {get set}
        var race: String {get set}
        func sayHi()
    }

    在结构体中实现协议的方法和变量

    struct Lisa: People {
        var name: String = "Lisa"
        var race: String = "Asian"
        func sayHi() {
            print("Hi~, I'm \(name)")
        }
    }

    3、协议的继承

    创建一个协议,让该协议继承自之前创建的People协议

    protocol superman {
          var canFly: Bool {get set}
          func punch()
    }
    
    protocol superman: People {
          var canFly: Bool {get set}
          func punch()
    }

    调用

    struct AngleLisa: superman {
    var name: String = "Lisa"
    var race: String = "Asian"
    func sayHi() {
        print("Hi, I'm \(name)")
    }
    var canFly: Bool = true
    func punch() {
        print("punch  Vergil")
    }
    }

    由此可知,一旦协议进行了继承,不但要实现本协议中所声明的方法和属性,连协议父类的方法和属性也不能落下。

    二十二、swift4新特性

    以下内容来自 最全的 Swift 4 新特性解析

    感谢大佬提供学习资源!!!

    1、语法改进

    在扩展extension中可以访问private的属性

    举一个简单的栗子!

    struct Date: Equatable, Comparable {
        private let secondsSinceReferenceDate: Double
        static func ==(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
        }
        static func  Bool {
            return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
        }
    }

    上面代码定义了一个 Date 结构体,并实现 Equatable 和 Comparable 协议。为了让代码更清晰,可读性更好,一般会把对协议的实现放在单独的 extension 中,这也是一种非常符合 Swift 风格的写法,如下:

    struct Date {
        private let secondsSinceReferenceDate: Double
    }
    extension Date: Equatable {
        static func ==(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
        }
    }
    extension Date: Comparable {
        static func  Bool {
            return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
        }
    }

    但是在 Swift 3 中,这样写会导致编译报错,extension 中无法获取到 secondsSinceReferenceDate 属性,因为它是 private 的。于是在 Swift 3 中,必须把 private 改为 fileprivate。

    struct Date {
        fileprivate let secondsSinceReferenceDate: Double
    }
    ...

    但是如果用 fileprivate,属性的作用域就会比我们需要的更大,可能会不小心造成属性的滥用。

    在 Swift 4 中,private 的属性的作用域扩大到了 extension 中,并且被限定在了 struct 和 extension 内部,这样struct的属性就不需要再用 fileprivate修饰了,这是最好的结果。

    类型和协议的组合类型

    protocol Shakeable {
        func shake()
    }
    
    extension UIButton: Shakeable { func shake() {/* */ } }
    extension UISlider: Shakeable { func shake() {/* */ } }
    
    func shakeEm(controls: [???]) {
        for control in controls where control.state.isEnabled {
        }
        control.shake()
    }

    仔细思考上面的代码,如果是swift3中,func shakeEm(controls: [???])中的???应该写上面类型呢?如果写UIControl,那么调用control.shake()时就会报错。如果写Shakeable类型,那么control.state.isEnabled这条语句就会报错。

    swift4为了解决类似问题,实现了把类型和协议用&组合在一起作为一个类型使用的写法。把它声明为UIControl & Shakeable类型。

    func shakeEm(controls: [UIControl & Shakeable]) {
        for control in controls where control.isEnabled {
            control.shake()
        }
    }

    Associated Type 可以追加 Where 约束语句

    在 Swift 4 中可以在 associatedtype后面声明的类型后追加 where 语句。

    protocol Sequence {
        associatedtype Element where Self.Element == Self.Iterator.Element
        // ...
    }

    它限定了 Sequence 中 Element 这个类型必须和 Iterator.Element 的类型一致。

    通过 where 语句可以对类型添加更多的约束,使其更严谨,避免在使用这个类型时做多余的类型判断。

    新的 Key Paths 语法

    先来看看 Swift 3 中 Key Paths 的写法:

    @objcMembers class Kid: NSObject {
        dynamic var nickname: String = ""
        dynamic var age: Double = 0.0
        dynamic var friends: [Kid] = []
    }
    
    var ben = Kid(nickname: "Benji", age: 5.5)
    
    let kidsNameKeyPath = #keyPath(Kid.nickname)
    
    let name = ben.valueForKeyPath(kidsNameKeyPath)
    ben.setValue("Ben", forKeyPath: kidsNameKeyPath)

    Swift 4 中创建一个 KeyPath 用 \作为开头:

    \Kid.nickname

    当编译器可以推导出类型时,可以省略基础类型部分:

    \.nickname

    上面的代码在 Swift 4 中就可以这样写:

    struct Kid {
        var nickname: String = ""
        var age: Double = 0.0
        var friends: [Kid] = []
    }
    
    var ben = Kid(nickname: "Benji", age: 8, friends: [])
    
    let name = ben[keyPath: \Kid.nickname]
    ben[keyPath: \Kid.nickname] = "BigBen"

    相比 Swift 3,Swift 4 的 Key Paths 具有以下优势:

    类型可以定义为 class、struct

    定义类型时无需加上 @objcMembers、dynamic 等关键字

    性能更好

    类型安全和类型推断,例如 ben.valueForKeyPath(kidsNameKeyPath) 返回的类型是 Any,ben[keyPath: \Kid.nickname] 直接返回 String 类型

    可以在所有值类型上使用

    下标支持泛型

    Swift 支持通过下标来读写容器中的数据,但是如果容器类中的数据类型定义为泛型,以前的下标语法就只能返回 Any,在取出值后需要用 as? 来转换类型。Swift 4 定义下标也可以使用泛型了。但是并不需要做转型操作。

    struct GenericDictionary {
        private var data: [Key: Value]
        
        init(data: [Key: Value]) {
            self.data = data
        }
        
        subscript(key: Key) -> T? {
            return data[key] as? T
        }
    }
    
    let dictionary = GenericDictionary(data: ["Name": "Xiaoming"])
    
    let name: String? = dictionary["Name"] // 不需要再写 as? String

    2、字符串

    Unicode 字符串在计算 count 时的正确性改善

    在 Unicode 中,有些字符是由几个其它字符组成的,比如 é 这个字符,它可以用 \u{E9} 来表示,也可以用 e 字符和上面一撇字符组合在一起表示 \u{65}\u{301}。

    2.png

    这个 family 是一个由多个字符组合成的字符,打印出来的结果为3.png。上面的代码在 Swift 3 中打印的 count 数是 4,在 Swift 4 中打印出的 count 是 1。

    更快的处理速度

    Swift 4 的字符串优化了底层实现,对于英语、法语、德语、西班牙语的处理速度提高了 3.5 倍。对于简体中文、日语的处理速度提高了 2.5 倍。

    去掉了characters

    One-sided Slicing

    Swift 4 新增了一个语法糖 ... 可以对字符串进行单侧边界取子串。

    4.png

    String 当做 Collection 来用

    Swift 4 中 String 可以当做 Collection 来用,并不是因为 String 实现了 Collection 协议,而是 String 本身增加了很多 Collection 协议中的方法,使得 String 在使用时看上去就是个 Collection。例如:

    翻转字符串:

    let abc: String = "abc"
    print(String(abc.reversed()))
    // cba

    遍历字符:

    let abc: String = "abc"
    for c in abc {
        print(c)
    }
    /*
    a
    b
    c
    */

    Map、Filter、Reduce:

    // map
    let abc: String = "abc"
    _ = abc.map {
        print($0.description)
    }
    
    // filter
    let filtered = abc.filter { $0 == "b" }
    
    // reduce
    let result = abc.reduce("1") { (result, c) -> String in
        print(result)
        print(c)
        return result + String(c)
    }
    print(result)

    Substring

    在 Swift 中,String 的背后有个 Owner Object 来跟踪和管理这个 String,String 对象在内存中的存储由内存其实地址、字符数、指向 Owner Object 指针组成。Owner Object 指针指向 Owner Object 对象,Owner Object 对象持有 String Buffer。当对 String 做取子字符串操作时,子字符串的 Owner Object 指针会和原字符串指向同一个对象,因此子字符串的 Owner Object 会持有原 String 的 Buffer。当原字符串销毁时,由于原字符串的 Buffer 被子字符串的 Owner Object 持有了,原字符串 Buffer 并不会释放,造成极大的内存浪费。

    在 Swift 4 中,做取子串操作的结果是一个 Substring 类型,它无法直接赋值给需要 String 类型的地方。必须用 String() 包一层,系统会通过复制创建出一个新的字符串对象,这样原字符串在销毁时,原字符串的 Buffer 就可以完全释放了。

    let big = downloadHugeString()
    let small = extractTinyString(from: big)
    
    mainView.titleLabel.text = small // Swift 4 编译报错
    
    mainView.titleLabel.text = String(small) // 编译通过

    多行字符串字面量

    Swift 3 中写很长的字符串只能写在一行。

    func tellJoke(name: String, character: Character) {
        let punchline = name.filter { $0 != character }
        let n = name.count - punchline.count
        let joke = "Q: Why does \(name) have \(n) \(character)'s in their name?\nA: I don't know, why does \(name) have \(n) \(character)'s in their name?\nQ: Because otherwise they'd be called \(punchline)."
        print(joke)
    }
    tellJoke(name: "Edward Woodward", character: "d")

    字符串中间有换行只能通过添加 \n 字符来代表换行。

    Swift 4 可以把字符串写在一对 """ 中,这样字符串就可以写成多行。

    func tellJoke(name: String, character: Character) {
        let punchline = name.filter { $0 != character }
        let n = name.count - punchline.count
        let joke = """
            Q: Why does \(name) have \(n) \(character)'s in their name?
            A: I don't know, why does \(name) have \(n) \(character)'s in their name?
            Q: Because otherwise they'd be called \(punchline).
            """
        print(joke)
    }
    tellJoke(name: "Edward Woodward", character: "d")

    3、Swift 标准库

    Encoding and Decoding

    当需要将一个对象持久化时,需要把这个对象序列化,往常的做法是实现 NSCoding 协议,写过的人应该都知道实现 NSCoding 协议的代码写起来很痛苦,尤其是当属性非常多的时候。几年前有一个工具能自动生成 Objective-C 的实现 NSCoding 协议代码,当时用着还不错,但后来这个工具已经没有人维护很久了,而且不支持 Swift。

    Swift 4 中引入了 Codable 帮我们解决了这个问题。

    struct Language: Codable {
        var name: String
        var version: Int
    }

    我们想将这个 Language 对象的实例持久化,只需要让 Language 符合 Codable 协议即可,Language 中不用写别的代码。符合了 Codable 协议以后,可以选择把对象 encode 成 JSON 或者 PropertyList。

    Encode 操作如下:

    let swift = Language(name: "Swift", version: 4)
    if let encoded = try? JSONEncoder().encode(swift) {
        // 把 encoded 保存起来
    }

    Decode 操作如下:

    if let decoded = try? JSONDecoder().decode(Language.self, from: encoded) {
        print(decoded.name)
    }

    Sequence 改进

    Swift 3:
    
    protocol Sequence {
        associatedtype Iterator: IteratorProtocol
        func makeIterator() -> Iterator
    }
    Swift 4:
    
    protocol Sequence {
        associatedtype Element
        associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
        func makeIterator() -> Iterator
    }

    由于 Swift 4 中的 associatedtype 支持追加 where 语句,所以 Sequence 做了这样的改进。

    Swift 4 中获取 Sequence的元素类型可以不用 Iterator.Element,而是直接取 Element。

    SubSequence 也做了修改:

    protocol Sequence {
        associatedtype SubSequence: Sequence 
            where SubSequence.SubSequence == SubSequence,
                  SubSequence.Element == Element
    }

    通过 where 语句的限定,保证了类型正确,避免在使用 Sequence 时做一些不必要的类型判断。

    Collection 也有一些类似的修改。

    Protocol-oriented integers

    整数类型符合的协议有修改,新增了 FixedWidthInteger 等协议,具体的协议继承关系如下:

    +-------------+   +-------------+
            +------>+   Numeric   |   | Comparable  |
            |       |   (+,-,*)   |   | (==,,...)|
            |       +------------++   +---+---------+
            |                     ^       ^
    +-------+------------+        |       |
    |    SignedNumeric   |      +-+-------+-----------+
    |     (unary -)      |      |    BinaryInteger    |
    +------+-------------+      |(words,%,bitwise,...)|
           ^                    ++---+-----+----------+
           |         +-----------^   ^     ^---------------+
           |         |               |                     |
    +------+---------++    +---------+---------------+  +--+----------------+
    |  SignedInteger  |    |  FixedWidthInteger      |  |  UnsignedInteger  |
    |                 |    |(endianness,overflow,...)|  |                   |
    +---------------+-+    +-+--------------------+--+  +-+-----------------+
                    ^        ^                    ^       ^
                    |        |                    |       |
                    |        |                    |       |
                   ++--------+-+                +-+-------+-+
                   |Int family |-+              |UInt family|-+
                   +-----------+ |              +-----------+ |
                     +-----------+                +-----------+

    Dictionary and Set enhancements

    这里简单列一下 Dictionary 和 Set 增强了哪些功能:

    • 通过 Sequence 来初始化

    • 可以包含重复的 Key

    • Filter 的结果的类型和原类型一致

    • Dictionary 的 mapValues 方法

    • Dictionary 的默认值

    • Dictionary 可以分组

    • Dictionary 可以翻转

    NSNumber bridging and Numeric types

    let n = NSNumber(value: 999)
    let v = n as? UInt8 // Swift 4: nil, Swift 3: 231

    在 Swift 4 中,把一个值为 999 的 NSNumber 转换为 UInt8 后,能正确的返回 nil,而在 Swift 3 中会不可预料的返回 231。

    MutableCollection.swapAt(::)

    MutableCollection 现在有了一个新方法 swapAt(::) 用来交换两个位置的值,例如:

    var mutableArray = [1, 2, 3, 4]
    mutableArray.swapAt(1, 2)
    print(mutableArray)
    // 打印结果:[1, 3, 2, 4]

    4、构建过程改进

    New Build System

    Xcode 9 引入了 New Build System,可在 Xcode 9 的 File -> Project Settings... 中选择开启。

    预编译 Bridging Headers 文件

    对于 Swift 和 Objective-C 混合的项目,Swift 调用 Objective-C 时,需要建立一个 Bridging Headers 文件,然后把 Swift 要调用的 Objective-C 类的头文件都写在里面,编译器会读取 Bridging Headers 中的头文件,然后生成一个庞大的 Swift 文件,文件内容是这些头文件内的 API 的 Swift 版本。然后编译器会在编译每一个 Swift 文件时,都要编译一遍这个庞大的 Swift 文件的内容。

    有了预编译 Bridging Headers 以后,编译器会在预编译阶段把 Bridging Headers 编译一次,然后插入到每个 Swift 文件中,这样就大大提高了编译速度。

    苹果宣称 Xcode 9 和 Swift 4 对于 Swift 和 Objective-C 混合编译的速度提高了 40%

    Indexing 可以在编译的同时进行

    用 Swift 开发项目时,近几个版本的 Xcode 进行 Indexing 的速度慢的令人发指。Xcode 9 和 Swift 4 在这方面做了优化,可以在编译的同时进行 Indexing,一般编译结束后 Indexing 也会同时完成。

    COW Existential Containers

    Swift 中有个东西叫 Existential Containers,它用来保存未知类型的值,它的内部是一个 Inline value buffer,如果 Inline value buffer 中的值占用空间很大时,这个值会被分配在堆上,然而在堆上分配内存是一个性能比较慢的操作。

    Swift 4 中为了优化性能引入了 COW Existential Containers,这里的 COW 就代表 "Copy-On-Write",当存在多个相同的值时,他们会共用 buffer 上的空间,直到某个值被修改时,这个被修改的值才会被拷贝一份并分配内存空间

    移除未调用的协议实现

    struct Date {
        private let secondsSinceReferenceDate: Double
    }
    
    extension Date: Equatable {
        static func ==(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
        }
    }
    
    extension Date: Comparable {
        static func  Bool {
            return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
        }
    }

    看上面例子,Date 实现了 Equatable 和 Comparable 协议。编译时如果编译器发现没有任何地方调用了对 Date 进行大小比较的方法,编译器会移除 Comparable 协议的实现,来达到减小包大小的目的。

    减少隐式 @objc 自动推断

    在项目中想把 Swift 写的 API 暴露给 Objective-C 调用,需要增加 @objc。在 Swift 3 中,编译器会在很多地方为我们隐式的加上 @objc,例如当一个类继承于 NSObject,那么这个类的所有方法都会被隐式的加上 @objc。

    class MyClass: NSObject {
        func print() { ... } // 包含隐式的 @objc
        func show() { ... } // 包含隐式的 @objc
    }

    这样很多并不需要暴露给 Objective-C 也被加上了 @objc。大量 @objc 会导致二进制文件大小的增加。

    在 Swift 4 中,隐式 @objc 自动推断只会发生在很少的当必须要使用 @objc 的情况,比如:

    复写父类的 Objective-C 方法

    符合一个 Objective-C 的协议

    其它大多数地方必须手工显示的加上 @objc。

    减少了隐式 @objc 自动推断后,Apple Music app 的包大小减少了 5.7%。

    5、 Exclusive Access to Memory

    在遍历一个 Collection 的时候可以去修改每一个元素的值,但是在遍历时如果去添加或删除一个元素就可能会引起 Crash。

    例如为 MutableCollection 扩展一个 modifyEach 方法来修改每个元素的值,代码如下:

    extension MutableCollection {
        mutating func modifyEach(_ body: (inout Element) -> ()) {
            for index in self.indices {
                body(&self[index])
            }
        }
    }

    假如在调用 modifyEach 时去删除元素:

    var numbers = [1, 2, 3]
    numbers.modifyEach { element in
        element *= 2
        numbers.removeAll()
    }

    就会在运行时 Crash。Swift 4 中引入了 Exclusive Access to Memory,使得这个错误可以在编译时被检查出来。

    作者:阡陌有客

    链接:https://www.jianshu.com/p/06b9ddf748f7

    展开全文
  • swift 4.0 语法基础

    2018-07-21 11:53:14
    语句间可以没有分号 基本数据类型  UInt :无符号整数 字面量  字面量(literal)是用于表达源代码中一个固定值的表示法(notation) 1.整型: 二进制数,前缀是0b ...八进制数,前缀是0o ...例如 let binaryInteger...
    • 语句间可以没有分号
    • 基本数据类型 

    UInt :无符号整数

    • 字面量 

    字面量(literal)是用于表达源代码中一个固定值的表示法(notation)

    1.整型:

    二进制数,前缀是0b

    八进制数,前缀是0o

    十六进制数,前缀是0x

    例如 let binaryInteger = 0b10001       // 二进制的17

     

    2.浮点型:

    浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是0x)

    指数在十进制浮点数中e来指定,在十六进制浮点数中p(相当于基数和2^exp的乘)来指定

    例如 1.25e-2 表示 1.25 × 10^-2,等于 0.0125

             0xFp2 表示 15 × 2^2,等于 60.0

     

    3.整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量

    例如 let justOverOneMillion = 1_000_000.000_000_1

     

    • 常量、变量、运算符 

    常量用let (常量是不可改变的值)

    变量用var (变量定义时要赋初值)

     变量名 第一个字母小写 后面的单词每个首字母大写 lowerComeClose

    可以用中文

    • 类型

    常量或变量的类型是在变量名后

    :类型名=值

    类型可有可无

    例如 let 循环次数:Int = 50

    • 类型强制转化

    例子 1,常量 twoThousand 的类型是UInt16 ,而常量 one 的类型是 UInt8 。他们不能直接被相加在一起,因为他们的类型不同。所以,这里让 UInt16 (one ) 创建一个新的 UInt16 类型并用 one 的值初始化

    2,用浮点数初始化一个新的整数类型的时候,数值会被截断。也就是说 4.75 会变成 4 , -3.9 会变为 -3

    • 类型别名 

    用 typealias 关键字定义类型别名

    例 typealias AudioSample = UInt16

         var maxAmplitudeFound = AudioSample.min

         // maxAmplitudeFound is now 0

    • 元组(tuple):用于函数返回多个值

    1,元组把多个值合并成单一的复合型的值。

    例 

    let http404Error = (404, "Not Found")

    // http404Error is of type (Int, String), and equals (404, "Not Found")

    2,可以使用下标访问元组元素

    print(“code is /(http404Error.0)”)

     

    3, 可以在定义元组的时候给其中的单个元素命名:

     

    1

    let http200Status = (statusCode: 200, description: "OK")

    在命名之后,你就可以通过访问名字来获取元素的值了:

     

    1

    2

    3

    4

    print("The status code is \(http200Status.statusCode)")

    // prints "The status code is 200"

    • 运算符:包含c语言

    1,Swift 同时也提供两个等价运算符( ===  和 !== ),可以使用它们来判断两个对象的引用是否相同。

    2,范围运算符

    a…b ab范围内包括ab

    a..<b 含a 不含b

    3,取余%可对负数

    4,逻辑运算符的操作数必须是Bool类型

    5,nil表示值不存在

    6,??合并运算符 作用于两个操作数

      左边为可选量,

    • 数组类

    定义Array<Type>或 [Type]

    例 var allStudents:[String]=[“john”,”Kenny”,”Wendy”,”Kim”]

    • 字典类

    定义Dictionary<keyType,valueType>或 [keyType:valueType]

    展开全文
  • Swift 4.0 新特性

    2017-08-15 19:37:46
    WWDC 2017 带来了很多惊喜,在这次大会上,Swift 4 也伴随着 Xcode 9 测试版来到了我们的面前,虽然正式版要8月底9月初才会公布,但很多强大的新特性正吸引我们去学习它。根据大会上已经开放的新特性,先一睹为快。...

    WWDC 2017 带来了很多惊喜,在这次大会上,Swift 4 也伴随着 Xcode 9 测试版来到了我们的面前,虽然正式版要8月底9月初才会公布,但很多强大的新特性正吸引我们去学习它。根据大会上已经开放的新特性,先一睹为快。

    体验

    Swift 4包含在Xcode 9中,您可以从Apple的开发者门户下载最新版本的Xcode 9(您必须拥有一个活跃的开发者帐户)。 每个Xcode测试版将在发布时捆绑最新的Swift 4快照。在阅读时,您会注意到[SE-xxxx]格式的链接。 这些链接将带您到相关的Swift Evolution提案。 如果您想了解有关任何主题的更多信息,请务必查看。

    版本迁移

    由于Swift 4新增了很多的新的语法特性,这些语法和思想完全区别于Swift 3及以下版本。因此,使用Swift迁移工具将为您处理大部分更改,在Xcode中,您可以导航到编辑/转换/到当前Swift语法…以启动转换工具。

    语法改进

    extension 中可以访问 private 的属性

    例如有如下代码:

    struct Date: Equatable, Comparable {
        private let secondsSinceReferenceDate: Double
        static func ==(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
        }
        static func <(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
        }
    }

    上面代码定义了一个 Date 结构体,并实现 Equatable 和 Comparable 协议。为了让代码更清晰,可读性更好,一般会把对协议的实现放在单独的 extension 中,这也是一种非常符合 Swift 风格的写法。

    struct Date {
        private let secondsSinceReferenceDate: Double
    }
    extension Date: Equatable {
        static func ==(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
        }
    }
    extension Date: Comparable {
        static func <(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
        }
    }
    

    但是在 Swift 3 中,编译就报错了,因为 extension 中无法获取到 secondsSinceReferenceDate 属性,因为它是 private 的。所以,在 Swift 3 中必须把 private 改为 fileprivate。但是如果用 fileprivate,属性的作用域就会更大,可能会不小心造成属性的滥用。

    struct Date {
        fileprivate let secondsSinceReferenceDate: Double
    }
    ...

    而在 Swift 4 中,private 的属性的作用域扩大到了 extension 中,并且被限定在了 struct 和 extension 内部,这样就不需要再改成 fileprivate 了。

    类型和协议的组合类型

    考虑以下如下代码:

    protocol Shakeable {
        func shake()
    }
    
    extension UIButton: Shakeable { /* ... */ }
    extension UISlider: Shakeable { /* ... */ }
    
    func shakeEm(controls: [???]) {
        for control in controls where control.state.isEnabled {
        }
        control.shake()
    }
    

    ???处怎么写呢?在Swift 3中可以这么写。

    func shakeEm(controls: [UIControl]) {
        for control in controls where control.isEnabled {
            if control is Shakeable {
                (control as! Shakeable).shake()
            }
        }
    }

    在Swift 4中,如果将类型和协议用 & 组合在一起使用,代码就可以这么写了。

    protocol Shakeable {
        func shake()
    }
    
    extension UIButton: Shakeable { /* ... */ }
    extension UISlider: Shakeable { /* ... */ }
    
    func shakeEm(controls: [UIControl & Shakeable]) {
        for control in controls where control.state.isEnabled {
            control.shake()
        }// Objective-C API
    @interface NSCandidateListTouchBarItem<CandidateType> : NSTouchBarItem
    @property (nullable, weak) NSView <NSTextInputClient> *client;
    @end
    }

    Associated Type 追加Where 约束语句

    在 Swift 4 中可以在 associated type 后面声明的类型后追加 where 语句,其语法格式如下:

    associatedtype Element where <xxx>

    下面是 Swift 4 标准库中 Sequence 中 Element 的声明:

    protocol Sequence {
        associatedtype Element where Self.Element == Self.Iterator.Element
        // ...
    }

    它限定了 Sequence 中 Element 这个类型必须和 Iterator.Element 的类型一致。通过 where 语句可以对类型添加更多的约束,使其更严谨,避免在使用这个类型时做多余的类型判断。

    Key Paths 语法

    先来看看Swift 3的Key Paths语法:

    @objcMembers class Kid: NSObject {
        dynamic var nickname: String = ""
        dynamic var age: Double = 0.0
        dynamic var friends: [Kid] = []
    }
    
    var ben = Kid(nickname: "Benji", age: 5.5)
    
    let kidsNameKeyPath = #keyPath(Kid.nickname)
    
    let name = ben.valueForKeyPath(kidsNameKeyPath)
    ben.setValue("Ben", forKeyPath: kidsNameKeyPath)
    
    

    在Swift 4中上面的代码可以这样写:

    struct Kid {
        var nickname: String = ""
        var age: Double = 0.0
        var friends: [Kid] = []
    }
    
    var ben = Kid(nickname: "Benji", age: 8, friends: [])
    
    let name = ben[keyPath: \Kid.nickname]
    ben[keyPath: \Kid.nickname] = "BigBen"
    
    

    相比 Swift 3,Swift 4 的 Key Paths 具有以下优势:

    1. 类型可以定义为 class、struct;
    2. 定义类型时无需加上 @objcMembers、dynamic 等关键字;
    3. 性能更好;
    4. 类型安全和类型推断,例如 ben.valueForKeyPath(kidsNameKeyPath) 返回的类型是
      Any,ben[keyPath: \Kid.nickname] 直接返回 String 类型;
    5. 可以在所有值类型上使用;

    下标支持泛型

    Swift 支持通过下标来读写容器中的数据,但是如果容器类中的数据类型定义为泛型,以前的下标语法就只能返回 Any,在取出值后需要用 as? 来转换类型。在Swift 4中,下标也可以使用泛型。

    struct GenericDictionary<Key: Hashable, Value> {
        private var data: [Key: Value]
    
        init(data: [Key: Value]) {
            self.data = data
        }
    
        subscript<T>(key: Key) -> T? {
            return data[key] as? T
        }
    }
    
    let dictionary = GenericDictionary(data: ["Name": "Xiaoming"])
    
    let name: String? = dictionary["Name"] // 不需要再写 as? String
    
    

    字符串

    Unicode 字符串

    在 Unicode 中,有些字符是由几个其它字符组成的,比如 é 这个字符,它可以用 \u{E9} 来表示,也可以用 e 字符和上面一撇字符组合在一起表示 \u{65}\u{301}。例如:

    这里写图片描述
    这个 family 是一个由多个字符组合成的字符,打印出来的结果为 一个家庭。上面的代码在 Swift 3 中打印的 count 数是 4,在 Swift 4 中打印出的 count 是 1。

    更快的字符处理速度

    Swift 4 的字符串优化了底层实现,对于英语、法语、德语、西班牙语的处理速度提高了 3.5 倍。对于简体中文、日语的处理速度提高了 2.5 倍。

    去掉了 characters

    Swift 3 中的 String 需要通过 characters 去调用的属性方法,在 Swift 4 中可以通过 String 对象本身直接调用,例如:

    let values = "one,two,three..."
    var i = values.characters.startIndex
    
    while let comma = values.characters[i...<values.characters.endIndex].index(of: ",") {
        if values.characters[i..<comma] == "two" {
            print("found it!")
        }
        i = values.characters.index(after: comma)
    }
    
    

    在Swift 4 可以把上面代码中的所有的 characters 都去掉:

    let values = "one,two,three..."
    var i = values.startIndex
    
    while let comma = values[i...<values.endIndex].index(of: ",") {
        if values[i..<comma] == "two" {
            print("found it!")
        }
        i = values.index(after: comma)
    }
    

    One-sided Slicing

    Swift 4 新增了一个语法糖 … 可以对字符串进行单侧边界取子串。例如:

    let values = "abcdefg"
    let startSlicingIndex = values.index(values.startIndex, offsetBy: 3)
    let subvalues = values[startSlicingIndex...] // One-sided Slicing
    // defg
    
    

    将String 当做 Collection 来用

    Swift 4 中 String 可以当做 Collection 来用,并不是因为 String 实现了 Collection 协议,而是 String 本身增加了很多 Collection 协议中的方法,使得 String 在使用时看上去就是个 Collection。例如:
    翻转字符串

    let abc: String = "abc"
    print(String(abc.reversed()))
    // cba

    遍历字符

    let abc: String = "abc"
    for c in abc {
        print(c)
    }
    /*
    a
    b
    c
    */

    Map、Filter、Reduce

    // map
    let abc: String = "abc"
    _ = abc.map {
        print($0.description)
    }
    
    // filter
    let filtered = abc.filter { $0 == "b" }
    
    // reduce
    let result = abc.reduce("1") { (result, c) -> String in
        print(result)
        print(c)
        return result + String(c)
    }
    print(result)
    
    

    Substring

    这里写图片描述

    在 Swift 中,String 的背后有个 Owner Object 来跟踪和管理这个 String,String 对象在内存中的存储由内存其实地址、字符数、指向 Owner Object 指针组成。Owner Object 指针指向 Owner Object 对象,Owner Object 对象持有 String Buffer。当对 String 做取子字符串操作时,子字符串的 Owner Object 指针会和原字符串指向同一个对象,因此子字符串的 Owner Object 会持有原 String 的 Buffer。当原字符串销毁时,由于原字符串的 Buffer 被子字符串的 Owner Object 持有了,原字符串 Buffer 并不会释放,造成极大的内存浪费。
    在 Swift 4 中,做取子串操作的结果是一个 Substring 类型,它无法直接赋值给需要 String 类型的地方。必须用 String() 包一层,系统会通过复制创建出一个新的字符串对象,这样原字符串在销毁时,原字符串的 Buffer 就可以完全释放了。例如:

    let big = downloadHugeString()
    let small = extractTinyString(from: big)
    
    mainView.titleLabel.text = small // Swift 4 编译报错
    
    mainView.titleLabel.text = String(small) // 编译通过
    
    

    多行字符串字面量

    Swift 3 中写很长的字符串只能写在一行。

    func tellJoke(name: String, character: Character) {
        let punchline = name.filter { $0 != character }
        let n = name.count - punchline.count
        let joke = "Q: Why does \(name) have \(n) \(character)'s in their name?\nA: I don't know, why does \(name) have \(n) \(character)'s in their name?\nQ: Because otherwise they'd be called \(punchline)."
        print(joke)
    }
    tellJoke(name: "Edward Woodward", character: "d")
    

    字符串中间有换行只能通过添加 \n 字符来代表换行。Swift 4 可以把字符串写在一对 “”” 中,这样字符串就可以写成多行。

    func tellJoke(name: String, character: Character) {
        let punchline = name.filter { $0 != character }
        let n = name.count - punchline.count
        let joke = """
            Q: Why does \(name) have \(n) \(character)'s in their name?
            A: I don't know, why does \(name) have \(n) \(character)'s in their name?
            Q: Because otherwise they'd be called \(punchline).
            """
        print(joke)
    }
    tellJoke(name: "Edward Woodward", character: "d")
    
    

    Swift 标准库

    Encoding and Decoding

    当需要将一个对象持久化时,需要把这个对象序列化,往常的做法是实现 NSCoding 协议,写过的人应该都知道实现 NSCoding 协议的代码写起来很痛苦,尤其是当属性非常多的时候。Swift 4 中引入了 Codable 帮我们解决了这个问题,这和Java等面向对象语言有异曲同工之妙。例如:

    struct Language: Codable {
        var name: String
        var version: Int
    }

    想让这个 Language 对象的实例持久化,只需要让 Language 符合 Codable 协议即可,Language 中不用写别的代码。符合了 Codable 协议以后,可以选择把对象 encode 成 JSON 或者 PropertyList。

    Encode操作

    let swift = Language(name: "Swift", version: 4)
    if let encoded = try? JSONEncoder().encode(swift) {
        // 把 encoded 保存起来
    }

    Decode操作

    if let decoded = try? JSONDecoder().decode(Language.self, from: encoded) {
        print(decoded.name)
    }

    Sequence

    在Swift 3中,

    protocol Sequence {
        associatedtype Iterator: IteratorProtocol
        func makeIterator() -> Iterator
    }

    由于 Swift 4 中的 associatedtype 支持追加 where 语句,所以 Sequence 做了这样的改进。

    protocol Sequence {
        associatedtype Element
        associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
        func makeIterator() -> Iterator
    }
    
    

    Swift 4 中获取 Sequence 的元素类型可以不用 Iterator.Element,而是直接取 Element。例如:

    protocol Sequence {
        associatedtype SubSequence: Sequence 
            where SubSequence.SubSequence == SubSequence,
                  SubSequence.Element == Element
    }
    
    

    通过 where 语句的限定,保证了类型正确,避免在使用 Sequence 时做一些不必要的类型判断。

    Protocol-oriented integers

    整数类型的协议也做了修改,新增了 FixedWidthInteger 等协议,具体的协议继承关系如下:
    这里写图片描述

    Dictionary and Set enhancements

    这里简单的罗列了Dictionary 和 Set 增强的功能点:

    • 通过 Sequence 来初始化;
    • 可以包含重复的 Key
    • Filter 的结果的类型和原类型一致
    • Dictionary 的 mapValues 方法
    • Dictionary 的默认值
    • Dictionary 可以分组
    • Dictionary 可以翻转

    NSNumber

    在 Swift 4 中,把一个值为 999 的 NSNumber 转换为 UInt8 后,能正确的返回 nil,而在 Swift 3 中会不可预料的返回 231。

    let n = NSNumber(value: 999)
    let v = n as? UInt8 // Swift 4: nil, Swift 3: 231

    MutableCollection.swapAt(::)

    MutableCollection 现在有了一个新方法 swapAt(::) 用来交换两个位置的值,例如:

    var mutableArray = [1, 2, 3, 4]
    mutableArray.swapAt(1, 2)
    print(mutableArray)
    // 打印结果:[1, 3, 2, 4]

    Xcode改进

    New Build System

    Xcode 9 引入了 New Build System,可在 Xcode 9 的 File -> Project Settings… 中选择开启。
    这里写图片描述

    预编译 Bridging Headers 文件

    对于 Swift 和 Objective-C 混合的项目,Swift 调用 Objective-C 时,需要建立一个 Bridging Headers 文件,然后把 Swift 要调用的 Objective-C 类的头文件都写在里面,编译器会读取 Bridging Headers 中的头文件,然后生成一个庞大的 Swift 文件,文件内容是这些头文件内的 API 的 Swift 版本。然后编译器会在编译每一个 Swift 文件时,都要编译一遍这个庞大的 Swift 文件的内容。

    有了预编译 Bridging Headers 以后,编译器会在预编译阶段把 Bridging Headers 编译一次,然后插入到每个 Swift 文件中,这样就大大提高了编译速度(苹果宣称 Xcode 9 和 Swift 4 对于 Swift 和 Objective-C 混合编译的速度提高了 40%)。

    COW Existential Containers

    Swift 中有个东西叫 Existential Containers,它用来保存未知类型的值,它的内部是一个 Inline value buffer,如果 Inline value buffer 中的值占用空间很大时,这个值会被分配在堆上,然而在堆上分配内存是一个性能比较慢的操作。

    Swift 4 中为了优化性能引入了 COW Existential Containers,这里的 COW 就代表 “Copy-On-Write”,当存在多个相同的值时,他们会共用 buffer 上的空间,直到某个值被修改时,这个被修改的值才会被拷贝一份并分配内存空间。

    移除未调用的协议实现

    struct Date {
        private let secondsSinceReferenceDate: Double
    }
    
    extension Date: Equatable {
        static func ==(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
        }
    }
    
    extension Date: Comparable {
        static func <(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
        }
    }
    
    

    例如,上面的代码中,Date 实现了 Equatable 和 Comparable 协议。编译时如果编译器发现没有任何地方调用了对 Date 进行大小比较的方法,编译器会移除 Comparable 协议的实现,来达到减小包大小的目的。

    减少隐式 @objc 自动推断

    在项目中想把 Swift 写的 API 暴露给 Objective-C 调用,需要增加 @objc。在 Swift 3 中,编译器会在很多地方为我们隐式的加上 @objc,例如当一个类继承于 NSObject,那么这个类的所有方法都会被隐式的加上 @objc。这样很多并不需要暴露给 Objective-C 也被加上了 @objc。大量 @objc 会导致二进制文件大小的增加。

    class MyClass: NSObject {
        func print() { ... } // 包含隐式的 @objc
        func show() { ... } // 包含隐式的 @objc
    }

    在 Swift 4 中,隐式 @objc 自动推断只会发生在很少的当必须要使用 @objc 的情况,比如:

    1. 复写父类的 Objective-C 方法
    2. 符合一个 Objective-C 的协议

    其它大多数地方必须手工显示的加上 @objc。减少了隐式 @objc 自动推断后,Apple Music app 的包大小减少了 5.7%。

    兼容

    Xcode 9 中同时集成了 Swift 3.2 和 Swift 4。

    1. Swift 3.2 完全兼容 Swift 3.1,并会在过时的语法或函数上报告警告。
    2. Swift 3.2 具有 Swift 4 的一些写法,但是性能不如 Swift 4。
    3. Swift 3.2 和 Swift 4 可以混合编译,可以指定一部分模块用 Swift 3.2 编译,一部分用 Swift 4 编译。
    4. 迁移到 Swift 4 后能获得 Swift 4 所有的新特性,并且性能比 Swift 3.2 好。

    当 Xcode 正式版发布后,现有的 Swift 代码可以直接升级到 Swift 3.2 而不用做任何改动,后续可以再迁移到 Swift 4。或者直接迁移到 Swift 4 也可以,Swift 4 相比 Swift 3 的 API 变化还是不大的,很多第三方库都可以直接用 Swift 4 编译。Swift 1 到 2 和 Swift 2 到 3 的迁移的痛苦在 3 到 4 的迁移上已经大大改善了。

    参考资料:

    展开全文
  • Swift 4是苹果计划于2017年秋季推出的最新版本,其主要重点是提供与Swift 3代码的源兼容性,并努力实现ABI稳定性。 本文重点介绍对Swift的更改将对您的代码产生最大的影响。 而且,让我们开始吧!

    本文由陈云峰翻译,转载请注明。

    注意:本教程将使用Swift 4版本捆绑在Xcode 9 beta 1中。

    Swift 4

    Swift 4是苹果计划于2017年秋季推出的最新版本,其主要重点是提供与Swift 3代码的源兼容性,并努力实现ABI稳定性。

    本文重点介绍对Swift的更改将对您的代码产生最大的影响。 而且,让我们开始吧!

    入门

    Swift 4包含在Xcode 9中。您可以从Apple的开发者门户下载最新版本的Xcode 9(您必须拥有一个活跃的开发者帐户)。 每个Xcode测试版将在发布时捆绑最新的Swift 4快照。

    在阅读时,您会注意到[SE-xxxx]格式的链接。 这些链接将带您到相关的Swift Evolution提案。 如果您想了解有关任何主题的更多信息,请务必查看。

    我建议您在操场上尝试每个Swift 4功能或更新。 这将有助于巩固您的头脑中的知识,并使您有能力深入了解每个主题。 试图扩大/打破他们的例子来玩弄这些例子。 玩得开心!

    注意:本文将针对每个Xcode测试版进行更新。 如果您使用不同的Swift快照,这里的代码不能保证工作。

    迁移到Swift 4

    从Swift 3迁移到4将比从2.2到3更麻烦。一般来说, 大多数变化是相加的,不应该需要大量的个人感觉。 因此,Swift迁移工具将为您处理大部分更改。

    Xcode 9同时支持Swift 4以及Swift 3.2中的Swift 3中间版本。 您的项目中的每个目标可以是Swift 3.2或Swift 4,如果需要,您可以逐个迁移。 然而,转换为Swift 3.2并不是完全免费的 – 您可能需要更新代码部分才能与新SDK兼容,并且由于Swift尚未ABI稳定,因此您将需要使用Xcode 9重新编译依赖项。

    当您准备迁移到Swift 4时,Xcode再次提供了一个迁移工具来帮助您。 在Xcode中,您可以导航到编辑/转换/到当前Swift语法…以启动转换工具。

    选择要转换的目标后,Xcode将提示您对Objective-C推理的偏好。 选择推荐的选项通过限制引用来减少二进制大小(有关此主题的更多信息,请查看下面的限制@objc推断 )

    为了更好地了解您的代码中期望的更改,我们将首先介绍Swift 4中的API更改。

    API更改

    在跳转到Swift 4中介绍的补充之前,我们先来看看现有API所做的更改/改进。

    字符串

    String在Swift 4中获得了很多很好的爱。这个提案包含很多变化,所以让我们分解最大的。 [SE-0163] :

    如果你感觉怀旧,字符串再次收藏,就像他们是Swift 2.0之前一样。 此更改消除了对String上的String数组的需求。 您现在可以直接在String对象上进行迭代:

    let galaxy = "Milky Way "
    for char in galaxy {
      print(char)
    }

    是!

    您不仅可以通过String逻辑迭代,还可以从SequenceCollection获取所有的响铃和口哨:

    galaxy.count       // 11
    galaxy.isEmpty     // false
    galaxy.dropFirst() // "ilky Way "
    String(galaxy.reversed()) // " yaW ykliM"
    
    // Filter out any none ASCII characters
    galaxy.filter { char in
      let isASCII = char.unicodeScalars.reduce(true, { $0 && $1.isASCII })
      return isASCII
    } // "Milky Way "

    上面的ASCII示例显示了对Character 。 您现在可以直接从Character访问Character 。 以前,您需要实例化一个新的String [SE-0178] 。

    另外还有一个是StringProtocol 。 它声明了以前在String上声明的大部分功能。 这种变化的原因是改善切片如何工作。 Swift 4添加了Substring类型,用于引用String上的子序列。

    StringSubstring实现了StringProtocol使它们具有几乎相同的功能:

    // Grab a subsequence of String
    let endIndex = galaxy.index(galaxy.startIndex, offsetBy: 3)
    var milkSubstring = galaxy[galaxy.startIndex...endIndex]   // "Milk"
    type(of: milkSubstring)   // Substring.Type
    
    // Concatenate a String onto a Substring
    milkSubstring += ""     // "Milk"
    
    // Create a String from a Substring
    let milkString = String(milkSubstring) // "Milk"

    另一个很大的改进是String如何解释图形集合。 此解决方案来自于Unicode 9的改编。以前,由多个代码点组成的Unicode字符会导致count大于1.常见的情况是具有所选肤色的表情符号。 以下是几个示例,显示前后行为:

    "‍".count // Now: 1, Before: 2
    "".count // Now: 1, Before: 2
    "‍❤️‍‍".count // Now: 1, Before, 4

    这只是“ 字符串宣言”中提到的更改的一个子集。 您可以阅读有关将来希望看到的原始动机和提出的解决方案。

    词典和集合

    至于Collection类型, SetDictionary并不总是最直观的。 幸运的是,斯威夫特队给了他们一些非常需要的爱[SE-0165] 。

    基于序列的初始化
    列表首先是从一系列键值对(元组)创建一个字典的能力:

    let nearestStarNames = ["Proxima Centauri", "Alpha Centauri A", "Alpha Centauri B", "Barnard's Star", "Wolf 359"]
    let nearestStarDistances = [4.24, 4.37, 4.37, 5.96, 7.78]
    
    // Dictionary from sequence of keys-values
    let starDistanceDict = Dictionary(uniqueKeysWithValues: zip(nearestStarNames, nearestStarDistances)) 
    // ["Wolf 359": 7.78, "Alpha Centauri B": 4.37, "Proxima Centauri": 4.24, "Alpha Centauri A": 4.37, "Barnard's Star": 5.96]

    重复键处理
    您现在可以使用重复的键来处理初始化字典的任何方式。 这有助于避免覆盖键值对,而不会有任何问题:

    // Random vote of people's favorite stars
    let favoriteStarVotes = ["Alpha Centauri A", "Wolf 359", "Alpha Centauri A", "Barnard's Star"]
    
    // Merging keys with closure for conflicts
    let mergedKeysAndValues = Dictionary(zip(favoriteStarVotes, repeatElement(1, count: favoriteStarVotes.count)), uniquingKeysWith: +) // ["Barnard's Star": 1, "Alpha Centauri A": 2, "Wolf 359": 1]

    上面的代码使用zip和速记+来通过添加两个冲突的值来解析重复的键。

    注意:如果您不熟悉zip ,您可以在Apple的Swift文档中快速了解它

    过滤
    DictionarySet现在都可以将结果过滤到原始类型的新对象中:

    // Filtering results into dictionary rather than array of tuples
    let closeStars = starDistanceDict.filter { $0.value < 5.0 }
    closeStars // Dictionary: ["Proxima Centauri": 4.24, "Alpha Centauri A": 4.37, "Alpha Centauri B": 4.37]

    字典映射
    Dictionary获得了一个非常有用的方法来直接映射其值:

    // Mapping values directly resulting in a dictionary
    let mappedCloseStars = closeStars.mapValues { "\($0)" }
    mappedCloseStars // ["Proxima Centauri": "4.24", "Alpha Centauri A": "4.37", "Alpha Centauri B": "4.37"]

    字典默认值
    在Dictionary上访问某个值时,常见的做法是使用nil coalescing运算符给出默认值,以防数值为nil 。 在Swift 4中,这变得更加清洁,并允许您在线突变中做一些真棒:

    // Subscript with a default value
    let siriusDistance = mappedCloseStars["Wolf 359", default: "unknown"] // "unknown"
    
    // Subscript with a default value used for mutating
    var starWordsCount: [String: Int] = [:]
    for starName in nearestStarNames {
      let numWords = starName.split(separator: " ").count
      starWordsCount[starName, default: 0] += numWords // Amazing 
    }
    starWordsCount // ["Wolf 359": 2, "Alpha Centauri B": 3, "Proxima Centauri": 2, "Alpha Centauri A": 3, "Barnard's Star": 2]

    以前,这种类型的突变将需要在一个blo肿的if-let语句中包装。 在Swift 4中,可能是一条线!

    字典分组
    另一个令人惊讶的有用的补充是从Sequence Dictionary并将它们分组到桶中的能力:

    // Grouping sequences by computed key
    let starsByFirstLetter = Dictionary(grouping: nearestStarNames) { $0.first! }
    
    // ["B": ["Barnard's Star"], "A": ["Alpha Centauri A", "Alpha Centauri B"], "W": ["Wolf 359"], "P": ["Proxima Centauri"]]

    当通过特定模式对数据进行分组时,这很方便。

    预留容量
    SequenceDictionary现在都具有明确保留容量的能力。

    // Improved Set/Dictionary capacity reservation
    starWordsCount.capacity  // 6
    starWordsCount.reserveCapacity(20) // reserves at _least_ 20 elements of capacity
    starWordsCount.capacity // 24
    

    这些类型的重新分配可能是一项昂贵的任务。 使用reserveCapacity(_:)是一个简单的方法来提高性能,当您了解需要存储多少数据时。

    这是一大堆信息,所以绝对检查这两种类型,并寻找使用这些添加剂来调整代码的方法。

    私有访问修饰符

    Swift 3的一个元素,一些不太喜欢的是添加fileprivate 。 从理论上讲,这是非常好的,但实际上它的使用往往会令人困惑。 目标是在成员本身中使用private的,并且在您想要在同一文件中的成员共享访问的情况下很少使用fileprivate 。

    问题是Swift鼓励使用扩展将代码分解成逻辑组。 扩展被认为在原始成员声明范围之外,这导致对fileprivate的广泛需求。

    Swift 4通过在类型和所述类型的任何扩展之间共享相同的访问控制范围来实现原始意图。 这只适用于相同的源文件[SE-0169] :

    struct SpaceCraft {
      private let warpCode: String
    
      init(warpCode: String) {
        self.warpCode = warpCode
      }
    }
    
    extension SpaceCraft {
      func goToWarpSpeed(warpCode: String) {
        if warpCode == self.warpCode { // Error in Swift 3 unless warpCode is fileprivate
          print("Do it Scotty!")
        }
      }
    }
    
    let enterprise = SpaceCraft(warpCode: "KirkIsCool")
    //enterprise.warpCode  // error: 'warpCode' is inaccessible due to 'private' protection level
    enterprise.goToWarpSpeed(warpCode: "KirkIsCool") // "Do it Scotty!"

    这允许您使用fileprivate作为其预期目的,而不是作为带状编码组织。

    新增API

    现在让我们来看看Swift 4的新功能。这些更改不应该打破你现有的代码,因为它们是简单的加法。

    归档和序列化

    谷物人

    到目前为止,在Swift中,为了序列化和归档您的自定义类型,您必须跳过一些环。对于class类型,您需要对NSObject进行子类化并实现NSCoding协议。

    structenum这样的值类型需要许多hacks,例如创建一个可以扩展NSObjectNSCoding的子对象。

    Swift 4通过将序列化到所有三种Swift类型[SE-0166]来解决这个问题:

    struct CuriosityLog: Codable {
      enum Discovery: String, Codable {
        case rock, water, martian
      }
    
      var sol: Int
      var discoveries: [Discovery]
    }
    
    // Create a log entry for Mars sol 42
    let logSol42 = CuriosityLog(sol: 42, discoveries: [.rock, .rock, .rock, .rock])

    在这个例子中,您可以看到,使Swift类型可Encodable和可Decodable所需的唯一Decodable是实现可编Codable协议。 如果所有属性都是Codable ,则协议实现由编译器自动生成。

    本文由陈云峰翻译,转载请注明。

    要实际编码对象,您需要将其传递给编码器。 Swift编码器正在Swift 4中积极实施。每个编码器根据不同的方案对您的对象进行编码[SE-0167] ( 注意:此提案的一部分仍在开发中):

    let jsonEncoder = JSONEncoder() // One currently available encoder
    
    // Encode the data
    let jsonData = try jsonEncoder.encode(logSol42)
    // Create a String from the data
    let jsonString = String(data: jsonData, encoding: .utf8) // "{"sol":42,"discoveries":["rock","rock","rock","rock"]}"

    这采取了一个对象,并自动将其编码为JSON对象。 确保查看JSONEncoder暴露的属性来自定义其输出。

    该过程的最后一部分是将数据解码为具体对象:

    let jsonDecoder = JSONDecoder() // Pair decoder to JSONEncoder
    
    // Attempt to decode the data to a CuriosityLog object
    let decodedLog = try jsonDecoder.decode(CuriosityLog.self, from: jsonData)
    decodedLog.sol         // 42
    decodedLog.discoveries // [rock, rock, rock, rock]

    使用Swift 4编码/解码,您可以在Swift中获得预期的类型安全性,而不依赖于@objc协议的开销和限制。

    键值编码

    到目前为止,您可以参考函数而不调用它们,因为函数是Swift中的闭包。 你不能做的是保持对属性的引用,而不实际访问属性保存的底层数据。

    对Swift 4来说,令人兴奋的补充是能够引用类型的关键路径来获取/设置实例的基础值[SE-0161] :

    struct Lightsaber {
      enum Color {
        case blue, green, red
      }
      let color: Color
    }
    
    class ForceUser {
      var name: String
      var lightsaber: Lightsaber
      var master: ForceUser?
    
      init(name: String, lightsaber: Lightsaber, master: ForceUser? = nil) {
        self.name = name
        self.lightsaber = lightsaber
        self.master = master
      }
    }
    
    let sidious = ForceUser(name: "Darth Sidious", lightsaber: Lightsaber(color: .red))
    let obiwan = ForceUser(name: "Obi-Wan Kenobi", lightsaber: Lightsaber(color: .blue))
    let anakin = ForceUser(name: "Anakin Skywalker", lightsaber: Lightsaber(color: .blue), master: obiwan)

    在这里,您将通过设置他们的名字,光剑和主人来创建强制用户的几个实例。 要创建一个关键路径,您只需使用一个反斜杠后跟您感兴趣的属性:

    // Create reference to the ForceUser.name key path
    let nameKeyPath = \ForceUser.name
    
    // Access the value from key path on instance
    let obiwanName = obiwan[keyPath: nameKeyPath]  // "Obi-Wan Kenobi"

    在这种情况下,您正在为ForceUsername属性创建一个关键路径。 然后,通过将其传递给新的下标keyPath来使用此键路径。 默认情况下,此下标现在可用于每种类型。

    以下是使用关键路径深入到子对象,设置属性和构建关键路径引用的更多示例:

    // Use keypath directly inline and to drill down to sub objects
    let anakinSaberColor = anakin[keyPath: \ForceUser.lightsaber.color]  // blue
    
    // Access a property on the object returned by key path
    let masterKeyPath = \ForceUser.master
    let anakinMasterName = anakin[keyPath: masterKeyPath]?.name  // "Obi-Wan Kenobi"
    
    // Change Anakin to the dark side using key path as a setter
    anakin[keyPath: masterKeyPath] = sidious
    anakin.master?.name // Darth Sidious
    
    // Note: not currently working, but works in some situations
    // Append a key path to an existing path
    //let masterNameKeyPath = masterKeyPath.appending(path: \ForceUser.name)
    //anakin[keyPath: masterKeyPath] // "Darth Sidious"

    Swift的关键路径的美丽在于它们是强类型的! 没有更多的Objective-C字符串风格混乱!

    多行字符串文字

    许多编程语言的一个非常常见的特征是能够创建多行字符串文字。 Swift 4通过在三个引号[SE-0168]中包装文本来添加这个简单而有用的语法:

    let star = "⭐️"
    let introString = """
      A long time ago in a galaxy far,
      far away....
    
      You could write multi-lined strings
      without "escaping" single quotes.
    
      The indentation of the closing quotes
           below deside where the text line
      begins.
    
      You can even dynamically add values
      from properties: \(star)
      """
    print(introString) // prints the string exactly as written above with the value of star

    这在构建XML / JSON消息或构建长格式的文本以在UI中显示时非常有用。

    单面范围

    为了减少冗长度并提高可读性,标准库现在可以使用单面范围[SE-0172]来推断起始和终点索引。

    派上用场的一种方法是创建一个从索引到集合的开始或结束索引的范围:

    / Collection Subscript
    var planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
    let outsideAsteroidBelt = planets[4...] // Before: planets[4..<planets.endIndex]
    let firstThree = planets[..<4]          // Before: planets[planets.startIndex..<4]

    如您所见,单面范围减少了明确指定开始索引或结束索引的需要。

    无限序列
    当起始索引为可数类型时,它们还允许您定义无限Sequence :

    // Infinite range: 1...infinity
    var numberedPlanets = Array(zip(1..., planets))
    print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (8, "Neptune")]
    
    planets.append("Pluto")
    numberedPlanets = Array(zip(1..., planets))
    print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (9, "Pluto")]

    模式匹配
    单面范围的另一个很好的用途是模式匹配:

    // Pattern matching
    
    func temperature(planetNumber: Int) {
      switch planetNumber {
      case ...2: // anything less than or equal to 2
        print("Too hot")
      case 4...: // anything greater than or equal to 4
        print("Too cold")
      default:
        print("Justtttt right")
      }
    }
    
    temperature(planetNumber: 3) // Earth

    通用下标

    下标是使数据类型以简洁方式可访问的重要组成部分。 为了提高其有用性,下标现在可以是通用的[SE-0148] :

    struct GenericDictionary<Key: Hashable, Value> {
      private var data: [Key: Value]
    
      init(data: [Key: Value]) {
        self.data = data
      }
    
      subscript<T>(key: Key) -> T? {
        return data[key] as? T
      }
    }

    在这个例子中,返回类型是通用的。 你可以使用这个通用的下标,如下所示:

    // Dictionary of type: [String: Any]
    var earthData = GenericDictionary(data: ["name": "Earth", "population": 7500000000, "moons": 1])
    
    // Automatically infers return type without "as? String"
    let name: String? = earthData["name"]
    
    // Automatically infers return type without "as? Int"
    let population: Int? = earthData["population"]

    返回类型不仅可以是通用的,而且实际的下标类型也可以是通用的:

    extension GenericDictionary {
      subscript<Keys: Sequence>(keys: Keys) -> [Value] where Keys.Iterator.Element == Key {
        var values: [Value] = []
        for key in keys {
          if let value = data[key] {
            values.append(value)
          }
        }
        return values
      }
    }
    
    // Array subscript value
    let nameAndMoons = earthData[["moons", "name"]]        // [1, "Earth"]
    // Set subscript value
    let nameAndMoons2 = earthData[Set(["moons", "name"])]  // [1, "Earth"]

    在这个例子中,你可以看到,传递两个不同的Sequence类型( ArraySet )会导致一个数组的各自的值。

    更多更新

    它处理了Swift 4中最大的变化。现在让我们通过一些较小的位和块来更快速地进行一些。

    MutableCollection.swapAt(_:_ )

    MutableCollection现在具有mutate方法swapAt(_:_:) ,就像它的声音一样; 交换给定索引值[SE-0173] :

    // Very basic bubble sort with an in-place swap
    func bubbleSort<T: Comparable>(_ array: [T]) -> [T] {
      var sortedArray = array
      for i in 0..<sortedArray.count - 1 {
        for j in 1..<sortedArray.count {
          if sortedArray[j-1] > sortedArray[j] {
            sortedArray.swapAt(j-1, j) // New MutableCollection method
          }
        }
      }
      return sortedArray
    }
    
    bubbleSort([4, 3, 2, 1, 0]) // [0, 1, 2, 3, 4]

    相关类型限制

    您现在可以使用where子句来限制关联类型[SE-0142] :

    protocol MyProtocol {
      associatedtype Element
      associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element
    }

    使用协议约束,许多associatedtype声明可以直接约束其值,而不必跳过环。

    类和协议存在

    最终将其从Objective-C转换为Swift的功能是定义符合类和一组协议的类型的能力[SE-0156] :

    protocol MyProtocol { }
    class View { }
    class ViewSubclass: View, MyProtocol { }
    
    class MyClass {
      var delegate: (View & MyProtocol)?
    }
    
    let myClass = MyClass()
    //myClass.delegate = View() // error: cannot assign value of type 'View' to type '(View & MyProtocol)?'
    myClass.delegate = ViewSubclass()

    限制@objc推论

    要将Objective-C或Swift API公开,请使用@objc编译器属性。 在许多情况下,Swift编译器为您推断出这一点。 质量推理的三个主要问题是:

      1. 潜在的二进制大小显着增加
      2. 知道何时@objc

    被推断不明显

    1. 不经意间创建Objective-C选择器碰撞的机会增加。

    Swift 4通过限制@objc [SE-0160]的推论来解决这个问题。 这意味着在需要Objective-C的完整动态调度功能的情况下,您需要使用@objc 。

    您需要进行这些更改的几个示例包括private方法, dynamic声明和NSObject子类的任何方法。

    NSNumber桥接

    NSNumber和Swift数字之间已经有很多时髦的行为,这些行为一直困扰着语言太久。 幸运的是,Swift 4压缩了这些错误[SE-0170] 。

    以下是一个示例演示示例:

    let n = NSNumber(value: 999)
    let v = n as? UInt8 // Swift 4: nil, Swift 3: 231

    Swift 3中的奇怪行为表明,如果数字溢出,则从0开始。在此示例中,999%2 ^ 8 = 231。

    Swift 4通过强制可选的转换来解决问题,只有当数字可以在包含类型中被安全地表达时才返回值。

    Swift包管理器

    在过去几个月里,Swift Package Manager已经有了一些更新。 一些最大的变化包括:

    • 从分支或提交哈希采购依赖关系
    • 更多控制可接受的包版本
    • 用更为常见的解决方案替代非直观的钉扎命令
    • 能够定义用于编译的Swift版本
    • 指定每个目标的源文件的位置

    这些都是获得SPM所需要的重大步骤。 SPM还有很长的路要走,但是我们可以通过保持积极的建议来帮助形成一个。

    有关最近解决的提案的全面了解,请查看“ Swift 4软件包管理器更新” 。

    从哪里走?

    还在举行视频
    想要更快学习吗?节省时间与我们的视频课程

    Swift语言多年来一直在增长和成熟。 提案过程和社区参与使得跟踪管道中出现的变化非常容易。 它也使东方任何一个人直接影响演变。

    随着Swift 4中的这些变化,我们终于到了一个ABI稳定性就在拐角处的地方。 升级Swift版本的痛苦越来越小。 构建性能和工具大大提高。 在苹果生态系统之外使用Swift变得越来越可行。 并且想一想,我们可能只是从一个直观的实现中完全重写String的一些;]。

    斯威夫特还有更多的东西。 要保持最新的所有更改,请确保查看以下资源:

    你对Swift 4有什么想法? 你最喜欢的变化是什么? 你还想从语言中看到什么? 你有没有找到新的和令人兴奋的东西,这里没有涵盖? 让我们在下面的评论中知道!

    本文由陈云峰翻译,转载请注明。原文地址

    展开全文
  • swift4.0 适配

    2019-02-20 00:10:44
    一、前言在我们的工程中处于swift和OC混编的状态,使用swift已经有一年半的时间了,随着Xcode9的更新,swift3.2和swift4.0也随之到来,swift3.2相较于Xcode8的swift3.1变动极小,适配没遇到问题,主要关注swift4.0的...

    一、前言

    在我们的工程中处于swiftOC混编的状态,使用swift已经有一年半的时间了,随着Xcode9的更新,swift3.2swift4.0也随之到来,swift3.2相较于Xcode8swift3.1变动极小,适配没遇到问题,主要关注swift4.0的适配。

    二、查看当前工程的 swift 版本

    image.png

    三、使用 Xcode 将工程转换到 swift4.0

    1、环境

    • Xcode9.1
    • 当前 swift 版本 3.2

    2、转换步骤

    1. 选中要转换的 target
    2. Edit -> Convert -> To Current Swift Syntax
      image.png
    3. 勾选需要转换的 targetpod引用不用勾选),Next
      image.png
    4. 选择转换选项,Next
      这两个选项是关于 swift@objc推断特性的,如果使用了 swift4.0显式的 @objc属性,能减少整体代码的大小。此时我们选 Minimize Inference(recommend),
      image.png
      关于两个选项:
    • Minimize Inference(recommend)
      根据静态推断,仅在需要的地方添加@objc属性。使用此选项后,需要按照Completing a Swift 4 minimize inference migration来完成转换。

    • Match Swift 3 Behavior
      在编译器隐式推断的任何地方向代码添加一个@objc属性。这个选项不会改变你的二进制文件的大小,因为被Swift 3隐式推断在所有的地方都添加了显式的@objc属性。

    1. 预览转换代码,没问题,Save。

    3、修改错误

    完成上述5步之后,看一下 swift版本,已经是4.0了:
    image.png

    至此打完收工,适配结束。然而并没有,当你运行的时候会看到这个:
    image.png

    是否欲哭无泪,居然这么多错误,不用怕,其实要改动的地方并不多,有些都是重复的,可以直接全局替换就行。

    举个栗子:

    • class dynamic func
    // 转换前
    class dynamic func bookMoneyToUpController() -> MPBookMoneyToUpController {
      let vc = MPBookMoneyToUpController.init(nibName: "MPBookMoneyToUpController", bundle: Bundle.main)
      return vc
    }
    
    // 转换后
    class @objc dynamic func bookMoneyToUpController() -> MPBookMoneyToUpController {
      let vc = MPBookMoneyToUpController.init(nibName: "MPBookMoneyToUpController", bundle: Bundle.main)
      return vc
    }
    
    // 问题 @objc 修饰符需要前置
    // 修改成下面即可
    @objc class dynamic func bookMoneyToUpController() -> MPBookMoneyToUpController {
      let vc = MPBookMoneyToUpController.init(nibName: "MPBookMoneyToUpController", bundle: Bundle.main)
      return vc
    }
    
    // 全局替换即可
    class @objc dynamic func  -> @objc class dynamic func
    

    image.png


    上面使用 dynamic修饰符是由于以前使用 JSPatch来做 hotfix,需要用到原来OC的运行时特性。

    四、@objc

    swift4.0最大的特性之一就是 @objc修饰符的变化了,它主要处理 OCswift混编时一些方法的调用以及属性获取问题,swift4.0将在 swift3.x中一些隐式类型推断的特性去除以后,需要我们来手动管理 @objc修饰符。
    在上文中使用 Xcode转换 swift4.0时我们勾选了 Minimize Inference选项,那么我们就需要手动处理相关的 @objc修饰符,来保证 OCswift代码能正常相互调用。

    1、@objc修饰符手动处理步骤

    使用“最小化”转换代码后,需要处理构建和运行时的问题,在完成初始的 swift4.0转换后,需要按照下面步骤来处理其它问题。

    1. 运行你的工程

    2. 修复编译器提示需要添加 @objc的地方

    3. 测试你的代码,并修复编译器提示使用了不推荐的隐式 @objc引用的警告。直到没有警告发生。

    4. 打开工程的 build settings.

    5. Swift 3 @objc inference 设置为 Default.

    2、@objc修饰符需要处理的问题

    1. 编译警告
    • swift 中编译的警告
      #selector 参数指定的实例方法必须使用 @objc 修饰,因为swift4中弃用了 @objc属性推断。
    // 下面的代码会有警告
    class MyClass : NSObject {
       func foo() {
       }
      
       func bar() {
          self.perform(#selector(MyClass.foo)
       }
    }
    warning: argument of ‘#selector’ refers to instance method ‘foo’ in ‘MyClass’ that depends on ‘@objc’ attribute inference deprecated in Swift 4
    
    • Objective-C 编译时警告
      OC 中调用的 swift 方法,在 swift 中需要追加 @objc 修饰,swift4 废弃了该类型推断。
    // 下面的代码会有警告
    @implementation MyClass (ObjCMethods)
    - (void)other {
          [self foo];
       }
    @end
    warning: Swift method MyClass.foo uses @objc inference deprecated in Swift 4; add @objc to provide an Objective-C entrypoint
    
    • 修复编译时警告
    // 通过追加 @objc 来消除警告
    class MyClass : NSObject {
       @objc func foo() {
       }
      
       func bar() {
          self.perform(#selector(MyClass.foo)
       }
    }
    
    • 查看所有需要添加 @objc 的编译警告
      image.png
      直接选中定位到相应位置,追加 @objc 修饰即可。
    1. 运行时警告
      运行时警告会打印在控制台:
    ***Swift runtime: 
    ClassName.swift:lineInFile:columnInLine: 
    entrypoint -[ClassName methodName] generated by implicit @objc inference is deprecated and will be removed in Swift 4; 
    add explicit @objc to the declaration to emit the Objective-C entrypoint in Swift 4 and suppress this message
    

    Xcode9.1 中,运行时警告在这里也能看到:
    image.png

    想要修复运行时警告,需要添加 @objc 修饰符到对应的方法或者符号。

    • 运行时警告的常见原因:

      • OC 中使用 SEL
      • swift 中使用了 perform methods
      • OC 中使用了 performSelector methods
      • 使用了 @IBOutlet 或者 @IBAction
    // 下面 swift 代码会产生运行时警告
    class MyClass : NSObject {
       func foo() {
       }
      
       func bar() {
          let selectorName = "foo"
          self.perform(Selector(selectorName)
       }
    }
    ***Swift runtime: MyClass.swift:7:7: entrypoint -[MyClass foo] generated by implicit @objc inference is deprecated and will be removed in Swift 4; add explicit @objc to the declaration to emit the Objective-C entrypoint in Swift 4 and suppress this message
    

    五、swift4.0其它部分特性

    1、NSAttributedStringKey

    NSAttributedString的初始化方法变化:

    // swift3.x
    public init(string str: String, attributes attrs: [AnyHashable : Any]? = nil)
    
    // swift4.0
    public init(string str: String, attributes attrs: [NSAttributedStringKey : Any]? = nil)
    

    示例:

    // 转换前
    let attributes = [NSForegroundColorAttributeName: RGB(128, g: 134, b: 146),
                              NSParagraphStyleAttributeName: paragraph,
                              NSFontAttributeName: UIFont.systemFont(ofSize: 14)] as [String : Any]
    var tipAttrText = NSAttributedString.init(string: tipText, attributes: attributes)
    
    // 转换后
    let attributes = [NSAttributedStringKey.foregroundColor.rawValue: RGB(128, g: 134, b: 146),
                              NSAttributedStringKey.paragraphStyle: paragraph,
                              NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14)] as! [String : Any]
    var tipAttrText = NSAttributedString(string: tipText, attributes: attributes)
    
    // tipAttrText 初始化报错提示
    Cannot convert value of type '[String : Any]' to expected argument type '[NSAttributedStringKey : Any]?'
    
    // 修改
    NSAttributedStringKey.foregroundColor.rawValue -> NSAttributedStringKey.foregroundColor
    去掉 as! [String : Any]
    

    2、String

    • Stringcharacters属性被废弃了
    let string = "abc"
    var count = string.characters.count
    
    // 第二行报错
    'characters' is deprecated: Please use String or Substring directly
    
    // 对应新方法
    count = string.count
    
    • StringaddingPercentEscapes方法被废弃了
    // swift3.x
    var url = @"http://www.example.com?username=姓名"
    url = url.addingPercentEscapes(using: String.Encoding.utf8)!
    
    // 报错
    'addingPercentEscapes(using:)' is unavailable: Use addingPercentEncoding(withAllowedCharacters:) instead, which always uses the recommended UTF-8 encoding, and which encodes for a specific URL component or subcomponent since each URL component or subcomponent has different rules for what characters are valid.
    
    // 修改
    uri = uri.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
    
    • substring(to:) 被废弃了
    let index = tagText.index(tagText.startIndex, offsetBy: MPMultipleStyleListItemTagMaxLength)
    
    // 警告:'substring(to:)' is deprecated: Please use String slicing subscript with a 'partial range upto' operator.
    let b = tagText.substring(to: index)
    
    // 新 API
    // 注意:a 的类型是 Substring,不是 String
    let a = tagText.prefix(upTo: index)
    

    3、initialize 废弃

    // swift3.x
    override class func initialize() {
        // some code
    }
    
    // 报错
    Method 'initialize()' defines Objective-C class method 'initialize', which is not permitted by Swift
    

    Swift3.x 继续 Method Swizzling这篇文章里面介绍了一种解决思路。

    4、swift3使用 #selector指定的方法,只有当方法权限为 private时需要加 @objc修饰符,swift4.0都要加 @objc修饰符

    // 示例代码
    func startMonitor() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.refreshUserLoginStatus), name: NSNotification.Name.XSLUserLogin, object: nil)
    }
    func refreshUserLoginStatus() {
        // some code
    }
    
    // 第二行警告
    Argument of '#selector' refers to instance method 'refreshUserLoginStatus()' in 'MPUnreadMessageCountManager' that depends on '@objc' inference deprecated in Swift 4
    
    // 追加 private
    func startMonitor() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.refreshUserLoginStatus), name: NSNotification.Name.XSLUserLogin, object: nil)
    }
    private func refreshUserLoginStatus() {
        // some code
    }
    
    // 第二行报错
    Argument of '#selector' refers to instance method 'refreshUserLoginStatus()' that is not exposed to Objective-C
    
    • swift4.0不再允许重载 extension中的方法(包括instancestaticclass方法)
    // 示例代码
    class TestSuperClass: NSObject {
    }
    extension TestSuperClass {
        func test() {
            // some code
        }
    }
    class TestClass: TestSuperClass {
        // 报错:Declarations from extensions cannot be overridden yet
        override func test() {
            // some code
        }
    }
    
    

    六、pod 引用

    添加以下内容到 Podfile

    post_install do |installer|
        installer.pods_project.targets.each do |target|
            if ['WTCarouselFlowLayout', 'XSLRevenue', 'OHHTTPStubs/Swift'].include? target.name
                target.build_configurations.each do |config|
                    config.build_settings['SWIFT_VERSION'] = '3.2'
                end
            end
        end
    end
    

    七、踩坑

    UITableViewDelegate 协议方法名变更,没有错误提示:

    // swift3.x
    func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: IndexPath) -> CGFloat 
    
    // swift4.0
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
    
    展开全文
  • 本书 精通 Swift 4 (Mastering Swift 4.0)是 非扫描版PDF 作者是 Jon Hoffman
  • Swift4.0源代码最新20180210.函数和闭包.playground 11.初始化和释放.playground 12.协议protocol.playground 13.类和结构体的区别.playground 14.枚举.playground 15.扩展.playground 16.下标.playground 17.泛型....
  • Swift4.0入门视频教程,课程内容包含Swift入门知识、数据类型(元组、Dictionary、运算符)、流程控制、函数、类与对象、扩展协议、常见概念、实用进阶、案例实战。 1、119节大容量课程:包含了Swift4.0语言大部分...
  • 免费下载地址:https://itunes.apple.com/cn/app/id1320746678◈ 不看视频不看书,手把手带您学习Swift语言◈ 利用手指来互动式学习Swift 4.0◈ 无痛上手,比观看视频、阅读书籍更加有趣、更加有效的学习方式 ◈ 变...
  • HDCP, 微菜谱(Swift4.0) 新增功能
  • ios-Swift4.0小程序.zip

    2020-07-15 23:33:10
    使用Swift4.0 的一些新语法
  • The Swift Programming Language 4.0 中文版学习笔记 11. 基础部分注释嵌套注释: /* A /* B */ /常量和变量常量和变量名可以使用Unicode字符来命名,但不能包含数学符号,箭头,保留的(或者非法的)Unicode码位...
  • 之前Swift一直不稳定,看过书,读过blog,今天开始,我以对比(OC:Swift)的形式将swift中常见的语法,以及注意事项,欢迎大家跟我一起学习Swift。// 1.导入框架 // OC导入框架 #import &lt;UIKit/UIKit.h&....
  • Swift4.0 study1 语法

    2018-01-19 10:29:45
    swift学习第一天 :语法 1.常量和变量 var 修饰变量 (可以修改) let 修饰常量(不可变) print代理oc中的NSLog输出 2.数据类型转换:Swift是强语言(OC是弱语言)故进行转换时必须指明数据类型,即...
  • Swift4.0 简单项目demo

    2018-06-13 19:00:48
    swift4.0基于MVVM开发模式的简单demo1.Alamofire (swift版的AFNetworking)封装与使用2.ESPullToRefresh 上拉加载下拉刷新 简单使用3.Kingfisher(swift版的SDWebImage)使用4.SnapKit(swift版的Masonry)使用5....
  • Swift4.0】属性

    2017-12-28 15:19:36
    希望对Swift的学习者有所帮助,使用的编写工具:JQNote  InNote(iPhone) 属性是把值和某个类,结构体或者枚举联系在一起。存储型属性存储一个实例的常量和变量值部分,然而计算型属性会计算出(而不是...
  • Swift:没有隐式转换,必须指明类型后才能运算。 1.算数运算符:+ - * / +=(swift中的++变成了+=) -=(swift中的--变成了-=) 2.关系运算符: &gt; &lt; &gt;= &lt;= == != 3.赋值运算:= += ...
  • Swift中的函数相当于OC中的方法Swift中函数的格式 func 函数名(参数列表) -&gt;返回值类型{ 代码块 return 返回值 }常见的函数类型:// 1.无参数,无返回值类型 箭头和void都可以省略 func test() -&gt; ...
  • 许多优秀的 Swift 第三方框架还没有来得及迎接 Swift 4.0 的到来,它们还停留在 Swift3.x 的状态。 这个时候新建一个项目,使用cocoapods 引入所需的第三方,即使 pod install 成功后,一编译工程就是满屏红, 且...
  • swift4.0中的KVO

    2019-07-10 02:04:50
    swift4.0 中 KVO 出现了变更,添加了监听回调.变得更加简单易用.同时会自动释放.不用再remove了.但是相对于OC中使用来说还是有一些需要注意的地方.1. 需要在当前类中添加一个属性来持有NSKeyValueObservation 否则...
1 2 3 4 5 ... 20
收藏数 5,589
精华内容 2,235
关键字:

swift4.0