swift4.0语法杂记(精简版)

小视频

001--swift简史小视频

002--Playground体验

003--常量&变量

1、swift简史

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

"👧🏽".count // 人 + 肤色
"👨‍👩‍👧‍👦".count // 有4个成员的家庭
"👱🏾\u{200D}👩🏽\u{200D}👧🏿\u{200D}👦🏻".count // 家庭 + 肤色
"👩🏻‍🚒".count // 人 + 肤色 + 职业
复制代码

二、特色objective-c

  • swift取消了预编译指令,宏也被包括在内。 某些开发者为了让Objective-C和swift代码兼容,会尽少在Objective-C中定义宏。
  • 取消了Objective-C中的指针等其余不安全访问的使用
  • 使用点语法来调用属性或者函数
  • 去除了NS前缀

三、为何要学习swiftjson

  • swift做为面向协议语言,不只能写移动端,也能够作到搭建服务器端。
  • 纵观国内外iOS开发界,已经有许多公司直接或间接采用swift开发,使用swift语言开发已成为将来iOS开发的趋势。
  • swift以简洁、优雅等优势迅速俘获广大开发者的青睐。

2、用playground体验swift开发

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

  • 建立一个普通的UIView对象

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

  • swift与objective-C的重大区别api

    • 在swift中是没有.h和.m文件之分的。全部的代码所有都存储在一个文件里面。
    • 在swift中全部的代码都被封装在{}里面
    • OC使用alloc init进行初始化,而swift使用()
    • OC中使用[]来调用方法,而swift中采用点语法。好比UIColor.red
    • swift中不须要用分号分割语句

3、常量和变量

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

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

二、变量和常量bash

  • 声明 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)
复制代码

4、String类型和Bool类型

一、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: "%02d:%02d", 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)   //从第十个字符开始截取
复制代码

二、Bool类型

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

5、可选类型

在Objective-C开发中,若是一个变量暂时不会使用到,能够将它赋值为0或者赋值为空,而在swift中,nil是一个特殊的类型,若是它和真实类型不匹配是不能进行赋值的。可是开发中将变量赋值为空是在所不免的事情,所以就推出了可选类型。 可选类型是swift的一大特点,在定义变量时,若是指定这个变量是可选的话,就是说这个变量能够有一个指定类型的值或者为nil。

一、定义一个optional的变量

let x:Optional = 10
print(x)
复制代码

点击进去查看,能够发现Option实际上是一个枚举类型。这个枚举有两个值,一个是none,表示没有值,而另外一个是some,表示某一类值。 在输出的时候,能够看见控制台上的内容Optional(10),它的做用就是提示这是一个可选值。

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

let x:Optional = 10  //第一种写法

let x:Int? = 20     //第二种写法
print(x)
复制代码

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

二、 解包

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

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

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

print(x!+y!)
复制代码

三、解包常见错误

//错误示范1
let y : Int?
print(y)
复制代码

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

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

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

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

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

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

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

五、可选绑定

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)
}
复制代码

6、swift中的分支

一、if语句 在swift中,if语句是不用带小括号的,可是后面跟的语句必须有花括号,哪怕只有一行代码。许多公司的代码规范也是规定必须使用这一格式。 注意:在swift中没有非0即真的说法,因此不能写成if(num)这样的格式。

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

二、三目运算符

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

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

很是有意思的是,若是开发者只想处理条件成立的部分,此时能够在冒号后面用一个小括号来代替条件不成立的部分。

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

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

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

let x:Int? = nil
let y:Int? = 9
print((x ?? 0) + (y ?? 0))
复制代码

运行以后的结果为9。

以后再来讲说运算符的优先级。举个简单的栗子🌰!

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

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

四、 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分支强。

五、 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("其余")
}
复制代码

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

一、变化

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

二、表示区间

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

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

三、逆序操做

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

func demo1() {
    for i in (0..<5).reversed() {
        print(i)
    }
    
}
demo1()
复制代码

8、swift中的数组

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

数组使用有序列表存储同一类型的多个值。相同的值能够屡次出如今一个数组的不一样位置中。

一、定义数组

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

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

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

//定义可变数组
var arrayM = [String]()
var arrayM1:[String]
var arrayM2 = Array<String>()
复制代码

二、建立带有默认值的数组

swift中的array类型还提供一个能够建立特定大小而且全部数据都被默认的构造方法。开发者能够在里面指定它的数量和类型。

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

三、对可变数组的基本操做

使用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])
复制代码

四、数组的遍历

//根据下标值进行遍历
for i in 0..<arrayM.count {
    print(arrayM[i])
}
//直接遍历数组中的元素
for i in arrayM {
    print(i)
}
复制代码

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

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

五、数组的合并

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

let resultArray = arrayM + array
复制代码

9、swift中的集合

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

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

一、定义集合

使用set<Element>定义。

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

//建立空集合
var letters = Set<Character>()
//使用字面量建立集合
var favorite:Set<String> = ["绮罗生","意琦行"]
复制代码

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

var favorite:Set = ["绮罗生","意琦行"]
复制代码

二、访问和修改集合

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

三、添加元素

favorite.insert("寒烟翠")
print(favorite.count)
复制代码

四、删除元素

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

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

五、集合操做

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

print(oddD.union(evenD).sorted()) //并集

print(oddD.intersection(evenD).sorted())//交集

print(oddD.subtracting(siggleDPrime).sorted())//取差值

print(oddD.symmetricDifference(siggleDPrime).sorted())//去掉相同值
复制代码

六、遍历集合

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

七、集合的成员关系

==来判断两个集合是否包含所有相同的值 用 isSubset(of:)来判断一个集合中的值是否也被包含在另一个集合中 用 isSuperset(of:)来判断一个集合中包含另外一个集合全部的值 用isStrictSubset(of:)或者isStrictSuperset(of:)方法来判断一个集合是不是另一个集合的子集合或父集合而且两个集合不相等

10、字典

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

一、定义字典

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

let dict = [1:"one",2:"two",3:"three"]  //定义不可变字典

var dictM = Dictionary<String,NSObject>()  //定义可变字典
var dictM1 = [String:NSObject]()

//AnyObject通常用于指定类型,NSObject通常用于建立对象
复制代码

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

添加、删除和获取元素

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

三、修改元素

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

dictM["name"] = "llx"
复制代码

四、遍历字典

能够经过范围for遍历全部的key和value。也能够遍历全部的键值对。

for (key,value) in dictM {
    print(key)
    print(value)
}
复制代码

五、合并字典

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

var dict1 = ["name":"llx","age":"17"]
var dict2 = ["num":"007"]

for (key,value) in dict2 {
    dict1[key] = value
}
dict
复制代码

11、元组

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

一、定义元组

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

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

复制代码

二、起别名

能够给元素加上名称,以后能够经过元素名称访问元素

//给元素加上名称,以后能够经过元素名称访问元素
let info2 = (id:"1001",name:"张三",age:30)
info2.name
复制代码

元组通常用于做为方法的返回值。元组中元素的别名,就是元组的名称

let (name,age) = ("张三",18)
name
复制代码

12、函数

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

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

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

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

func phone()->Void {
    print("小米")
}
phone()
复制代码

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

func phoneNum() -> String {
    return "123456"
}
 print(phoneNum())
复制代码

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

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

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

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)
复制代码

五、默认参数

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

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

六、可变参数

有些时候,在建立方法的时候,并不肯定参数的个数,因而swift推出了可变参数。参数的类型以后使用...表示多个参数。

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

复制代码

七、引用传递

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

若是按照上面的写法就会报错,能够按住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)")
复制代码

十3、类

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

class 类名:SuperClass {
    //定义属性和方法
}
复制代码

一、定义存储属性和建立类对象

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

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

二、给类的属性赋值

能够直接赋值,也能够经过KVC进行赋值

p.age = 10
p.name = "llx"
if let name = p.name {
    print(name)
}
复制代码

三、定义方法

在swift中,若是使用当前某一对象的属性或者方法,能够直接使用,不须要加self

// 定义方法,返回平均成绩
func getAverage() -> Double {
        return (mathScore + EnglishScore)*0.5
    }
复制代码
let average = p.getAverage()
复制代码

四、定义计算属性

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

var averageS : Double {
        return (mathScore + EnglishScore) * 0.5
    }
复制代码

五、定义类属性

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

static var courseCount : Int = 0
复制代码

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

Person.courseCount = 2
复制代码

六、类的构造函数

构造函数相似于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()

复制代码

七、自定义构造函数

自定义构造函数能够传入参数,作赋值操做时采用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

八、类的属性监听器

在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"
复制代码

十4、闭包

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

一、定义闭包

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

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

let b1 = {
  print("干掉他们")
}
b1()
复制代码

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

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

b2("string")
复制代码

二、闭包案例

这个案例要模拟封装一个网络请求的类。利用闭包将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<UITouch>, with event: UIEvent?) {
        //用闭包将json数据拿到
        tools.loadData { (jsonData) ->() in
            print("在viewcontroller中拿到数据\(jsonData)" )
        }
    }

}
复制代码

三、尾随闭包

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

//这个函数接受一个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)
}

复制代码

四、逃逸闭包

当一个闭包做为参数传到一个函数中,可是该闭包要在函数返回以后才被执行,因而就称这样的闭包为逃逸闭包。也就是说闭包逃离了函数的做用域。写法是在这个闭包参数前加一个@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是由于闭包最后才被执行……

  • 解决循环引用的三种方式 一、可使用weak关键字将对象之间的联系变为弱引用
weak var weakself = self
复制代码

二、第一种方式的简化

[weak self]
复制代码

三、使用unowned解决

[unowned self]
复制代码

可是该方法十分危险,要确保数据必定有值。不然会发生奔溃。

__weak 与__unretained有何区别? __weak修饰的弱引用,若是指向的对象被销毁,那么指针会立马指向nil __unretained修饰的弱引用,若是指向的对象被销毁,它的指针依然会指向以前的内存地址,很容易产生野指针(僵尸对象)

十5、tableView的用法

一、 懒加载

swift中也有懒加载的方式,而且在swift中有专门的关键字lazy来实现某一个属性实现懒加载。 格式:lazy var 变量:类型 = {建立变量代码}() 懒加载的本质在第一次使用的时候执行闭包,将闭包的返回值赋值给属性,而且只会赋值一次。

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

复制代码

二、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)")
    }
复制代码

##十6、swift中的注释 在swift中,相似于paramg --mark的写法是不可行的。

它是以下两种形式 //MARK:- 要写的内容 用于分组

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

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

/// 提示信息 用于提示

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

十7、枚举

一、定义

在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("没有方向")
        }
    }
}
复制代码

二、关联值

能够定义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和其字符串关联值所替代了。

三、枚举的原始值

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

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
复制代码

四、枚举递归

枚举成员的关联值为当前枚举类型时称为递归枚举。那咱们能够经过使用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的式子。

十8、结构体

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

struct teacher {
    var name : String = ""
    var age : Int  = 30
}
复制代码

十9、扩展

扩展 (Extension)能够作到无需修改本来的代码就直接把想要的功能实现。

extension 某个现有的class {
    //添加新功能
  }
复制代码

限制:

  • 不能添加任何已存在的 法或是属性
  • 添加的属性不能是存储属性,只能是计算属性

一、扩展在方法中的应用

extension String {
    func sayHello() {
        print("Hello from extension")
    }
}
复制代码

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

var hello = "hi"
hello.sayHello()
复制代码

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

二、用扩展进行计算

extension Int {
    var squared : Int {
        return (self * self)
    }
}
复制代码

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

var newInt = 30
newInt.squared
999.squared
复制代码

三、扩展类或结构体

  • 建立一个普通类
class Lisa {
    var lisa = "半边天使"
}
复制代码
  • 对类扩展,新增一个方法,使其能作自我介绍
extension Lisa {
    func describe() -> String {
        return "我但是会傲娇的"
    }
    
}
复制代码
  • 建立对象调用方法

二10、泛型

泛型可让开发者写出灵活可重复使用的方法跟结构。 先看一个栗子🌰!!

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<T>(a:[T]){
    for element in a {
              print(element)
          }
      }
复制代码

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

二11、协议

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

  • 使用子类时,协议继承父类的属性和方法。其中某些方法或属性并非开发者所须要的。这会让代码变得异常的臃肿。
  • 若一个类拥有不少父类,会让开发者很难找到每一个类中的问题并进行修改。
  • 对象引用到内存的同一地方,如果发生改变,可能会形成代码混乱的现象。

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

二、一个简单的协议案例

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

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

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)")
    }
}
复制代码

三、协议的继承

  • 建立一个协议,让该协议继承自以前建立的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")
}
}
复制代码

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

二12、swift4新特性

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

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

一、语法改进

  • 在扩展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

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<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 字符串在计算 count 时的正确性改善

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

var family = "👩"
family += "\u{200D}👩"
family += "\u{200D}👧" 
family += "\u{200D}👦"

print(family)
print(family.characters.count)
复制代码

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

  • 更快的处理速度

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

  • 去掉了characters

  • One-sided Slicing

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

Swift 3:

let values = "abcdefg"
let startSlicingIndex = values.index(values.startIndex, offsetBy: 3)
let subvalues = values[startSlicingIndex..<values.endIndex]
// defg
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 协议的代码写起来很痛苦,尤为是当属性很是多的时候。几年前有一个工具能自动生成 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]
复制代码

四、构建过程改进

  • 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 <(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。

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%。

五、 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,使得这个错误能够在编译时被检查出来。

相关文章
相关标签/搜索