参考资源
《swifter》
https://github.com/iOS-Swift-Developers/Swift
闭包逃逸
swift3中,闭包默认是非逃逸的。如果一个函数参数可能导致引用循环,那么它需要被显示的标记出来。
没有逃逸的闭包的作用域是不会超过函数本身的,
weakSelf unowned
如果能够确定访问时不会被释放的话,尽量用unowned,如果存在被释放的可能性的话,就用weak。
array 的高级函数 filter map
闭包捕获值
闭包和函数是引用类型
常量和变量
如果定义的时候初始化一个变量,可以不用写数据类型,会根据初始化的值自动推断出变量的类型(其他语言是没有类型推断的)
@objc 是为了 swift 和 oc 之间的交互,在使用 optional
泛型函数
如果函数的泛型列表只有一个 T ,每个节点类型的参数必须是一样的。
可以对节点进行一些限制,比如要求泛类型遵守某些协议。
有时候节点中的泛型需要更多的限制,需要使用 where 语句来补充约束条件。
泛型协议
关键字
associaetdType
self :适用于比较这类方法,其必须传入一个相同类型的参数才有意义。
方法
实例方法和函数的区别:函数默认没有外部参数
在类方法中没有 self
mutating 方法 :值类型(结构体和枚举)默认方法是不可以修改属性的,加 mutating 关键字,可以变为改变方法。
类方法: static 关键字(结构体/枚举),class(类),不存在class
封装
把隐藏属性、方法和方法实现细节的过程称为封装
open:任何时候都能被继承,被访问
public 从外部模块和本模块都可以访问.在模块外不能被继承,只能被访问
internal:默认设置, 只有本模块能访问
private:只在当前类中使用
fileprivate:
多态:用父类的类型调用子类的方法
嵌套类型:在一个类型中嵌套定义另一个类型
构造方法:
用于初始化属性
隐式调用
如果所有的存储属性都有默认值,可以不提供构造方法
如果存储属性可以提供缺省值,尽量提供。简化代码
常量存储属性只允许通过缺省值和构造方法中被修改
可选值存储属性可以不在构造方法中初始化
结构体默认提供一个构造器
构造方法之间的相互调用称为构造器代理
通过闭包或全局函数设置存储属性的缺省值
懒加载是用到才执行,闭包赋值是初始化时就会执行
被 convenience 关键字修饰的构造方法称之为便利构造器。一定是通过调用其他构造方法来初始化,一定要出现self.init
派生类的构造方法 :
默认情况下构造方法不能被继承
基类的存储属性只能通过基类的构造方法初始化
初始化存储属性必须先初始化当前类再初始化父类
便利构造器必须调用同类中的其他构造器(指定或者便利)
调用子类构造器一定能够初始化所有属性
只有在调用完父类指定构造器之后才能访问父类属性
确保当前类和父类所有存储属性都被初始化
重写父类方法,加上 override 关键字,
便利构造方法不存在重写
子类的便利构造方法不能直接访问父类的方法,所以不存在重写的概念。
只要在构造方法的前面加上一个required 关键字, 那么所有的子类只要定义了构造方法都必须实现该构造方法
函数参数
内部函数
外部函数
第二个参数默认既是外部参数又是内部参数
可以在定义函数的时候给某个参数默认值,当外部调用没有传递该参数时自动使用默认值
默认情况下 Swift 中的参数都是常量参数,如果需要在函数中改变参数的值,需要添加 var 关键字
可以将 var 换成 inout,这会传递参数本身而不是参数的值
函数类型
函数类型是由函数参数类型和返回值类型组成的。
可以利用函数类型定义函数变量和常量
函数类型可以作为函数的参数
函数类型可以作为函数的返回值
基本数据类型
整形
浮点型
长:Int8 Int 16 Int32 Int64
短
有符号:UInt
无符号:比有符号的取值范围更大。
swift 是类型安全的语言,取值错误的时候会直接报错。
oc中可以隐式类型转化,而在swift 中是不可以的
继承
新的类能够继承已有类的属性和方法,并且扩展新的能力
优点:代码重用
缺点:增加代码耦合性。
super 关键字
重写属性:无论是存储属性还是计算属性,都只能重写为计算属性
属性观察器:willSet didSet
利用 final 关键字防止重写
可以修饰属性、方法和类
修饰的属性和方法不能被重写
修饰的类不能被继承
结构体
用于封装相同和不同类型数据的,swift 中结构体是一类类型,可以定义属性和方法,甚至构造方法和析构方法。
如果结构体的属性有默认值,可以使用()构造一个结构体
结构体有一个默认的逐一构造器,用于在初始化时给所有属性赋值。
如果没有,必须使用逐一构造器实例化
Swift 中结构体和其他面向对象语言一样都有构造函数,而 OC 没有的
结构体中的成员方法必须使用某个实例调用
结构体是值类型
赋值有两种情况
1.指向同一块存储空间
2.内容相同的两个不同实例
结构体是值类型,结构体间的赋值就是拷贝一份数据,还是不同的实例
可选类型的本质其实是一个枚举
None
Some
格式:Optional<类型>
由于可选类型在 Swift 中随处可见,所以系统做了这个语法糖,在类型后面加 ?
基本类型变量在使用之前必须初始化,否则报错
目的:安全,不管什么时候方法都是有意义的;
可选类型安全吗?安全,可以使用可选绑定判断后再使用。
Swift 的发明者出于安全的考量,让我们在使用基本类型的时候不必考虑是否有值
Swift 中的可选类型变量更贴近于 oc 中的普通变量
可选链:通过可选类型变量来调用相应的属性和方法
1.强制解包。非常危险
2.通过可选绑定,代码繁琐,但是简单
3.通过可选链,如果 ?前面变量没有值,那么整个可选链会无效
可选链的返回值会自动包装成可选值
可选链调用下标索引,必须实现 subscript 方法,访问,实现get;赋值,实现set方法
判断赋值操作是否成功。判断返回值()? 或 Void? 都是代表成功
扩展
不需要知道目标源码,就可以给一个现存类、枚举、结构添加属性或者方法
限制条件:
1.不能添加已经存在的方法或属性
2.添加的属性不能是存储属性,只能是计算属性
格式:extension 某个先有类型 {}
扩展整数类型
扩展下标:
类
Swift 中结构体和类非常相似,但又有不同之处
类是具有相同属性和方法的抽象
类没有逐一构造器
类是引用类型,结构体是值类型
恒等运算符。用于判断是否是同一个实例,也就是是否指向同一存储空间
类型转换
不允许隐式类型转换,可以使用强制类型转换
枚举
Swift 中枚举要比 OC 中强大,因为它是一等类型,可以增加属性和方法
格式:
enum Method { case 枚举值}
枚举值可以连在一起写 case A, B, C, D.
可以使用枚举类型常量或变量接收枚举值
利用 Switch 匹配, 如果 case 包含了所有的枚举值,不需要写 default,如果没有,必须写default。
原始值
oc 中枚举的本质就是整数,所以有原始值,从0开始
Swift 中的枚举是没有原始值的,可以在定义的时候告诉系统让枚举有原始值
enum Method:枚举值原始值类型 {}
Swift 中枚举值类型除了可以指定整型还可以指定其他类型,但是必须给所有枚举值赋值,因为不能自动递增
rawValue 代表将枚举值转换为原始值
hashValue 访问成员值对应的哈希值,不可改变,系统自动生成,Swift 在背后实际上使用哈希值来识别枚举符号。
通过原始值创建枚举值 Method(rawValue:),返回的是可选值,因为原始值对应的枚举值不一定存在,有可能为nil,所以最好使用可选绑定。
枚举相关值:可以让枚举值相关的原始值是一个变量
Swift 内存管理
管理引用类型的内存,不会管理值类型,值类型不需要管理
内存管理原则:当没有强引用指向,就销毁
ARC 自动回收内存
弱引用,引用计数不变
如果利用weak 修饰变量,当对象释放后会被置为 nil
所以利用 weak 修饰的变量必定是一个可选类型。
unowned 无主引用,相当于 OC 中的 unsafe_unretained
unowned 和 weak 的区别
unowned 修饰的变量在释放之后不会置为nil,不安全
unowned 修饰的变量不是可选类型。
循环引用:ARC 不是万能的,它能够解决内存问题,但某些时候他不能解决内存泄漏问题。对象没有被销毁,但是我们没有办法访问他们了。
当某一个变量或常量必须有值,一直有值,那么可以使用 unowned 修饰
属性
存储属性:就是 oc 中的普通属性,在结构体和类中定义的属性,默认就是存储属性
常量存储属性:只能在定义或构造的时候修改,构造好之后不能修改
结构体和枚举是值类型
类是引用类型
不能修改结构体\枚举常量对象中的值
可以修改类中常量的值,不可以修改类常量的指向
延迟存储属性:swift 语言中当构造完一个对象后,对象中所有的存储属性都必须要有初始值,但是也有例外,其中延迟存储属性可以将属性的初始化推迟到该属性第一次被调用的时候
应用场景:
1.有可能不会用到
2.依赖于其他值
如果不是 lazy 属性,定义的时候对象还没有初始化,所以不能访问self
如果加上 lazy 属性,代表在使用时才加载,也就是在使用到属性的时候才会调用self
而访问一个类的对象必须使用对象方法,所以访问时对象已经初始化完成了。所以可以使用self。
计算属性:
计算属性不直接存储值,没有任何的“后端存储与之对应”
用于计算,可以实现setter 和 getter 两种计算方法
枚举不可有存储属性,但可以有计算属性
计算属性不具有存储功能,所以不能直接赋值
setter 可以传递一个自定义的参数,也可以使用系统默认的参数 newValue
如果使用系统默认参数,必须删除自定义参数。
只读计算属性
对应 oc 中的 readOnly,只提供 getter 方法。
只读属性必须是变量,不能是常量。
只读属性可以省略 get{}
应用:只能通过计算获得,不需要外界设置
属性观察器
类似 oc 中的kvo,可以用于监听属性什么时候被修改,只有被修改时才会调用。
willSet:在设置新值之前调用
didSet:在设置新值之后调用
可以为除计算属性和lazy属性之外的属性添加属性观察器,但是可以在继承类中添加父类计算属性的观察器
因为在计算属性中也可以监听到属性的改变,所以给计算属性添加观察期没有意义
类属性:
在结构体和枚举中使用static
在类中使用 class,并且在类中不允许将存储属性设置为类属性
普通属性是每个对象一份
类属性是所有对象共用一份
数组
有值数组
空数组
不可变数组
可变数组
获取长度:.count
判断是否空:.isEmpty
检索: [index]
追加:.append or +=
插入: .insert(any:at)
更新: arr[0] = 0
删除:remove(at:) removeLast removeFirst removeAll(keepingCapacity:Bool)
Range: removeSubrange(0…1) replaceSubrange(0..<1, with:)
遍历:for number in array for i in 0..<array.count
取出数组某个区间的值 array[0..<3]
析构方法
对象的内容被回收前隐式调用的方法,相当于 oc 中的delloc
只要执行一些额外操作,例如释放一些持有资源、关闭文件、断开网络
deinit{}
父类的析构方法会被自动调用,不需要子类管理
subscript 下标:访问对象中数据的快捷方式
所谓下标脚本语法就是能够通过,实例[索引值]来访问实例中的数据
类似于访问数组和字典,Swift 中数组和字典其实就是结构体
要想实现下标访问, 必须实现 subscript 方法
下标访问要实现 get 方法
下标赋值要实现 set 方法
协议
定义协议: prtocol ProtocolName {}
协议可以继承一个或多个协议
结构体、枚举可以实现协议
类实现协议和继承父类,协议一般写在父类后面
协议的属性:
协议不指定是否该属性是一个存储属性还是计算属性,只指定该属性的名称和读写属性。属性要求总是声明为变量属性,用 var 关键字做前缀。
协议普通方法实现
协议可以要求指定的实例方法和类型方法被一致的类型实现。这些方法被写为定义协议的一部分。不允许给协议方法参数指定默认值。
定义协议,指定方法要求
实现协议,实现方法
协议不仅可以声明属性、方法、下标,还可以声明构造器。但是在 Swift 中除了某些特殊情况,构造器是不会被子类继承的。所以我们需要在实现协议要求的构造器的时候,添加 required 关键字确保子类也得实现这个构造器。
举个简单的栗子,有一只猫和狗,他们都属于宠物,用类实现的时候,定义一个父类叫宠物,里面有喂食和玩耍两个方法,猫和狗都继承于宠物这个父类。这样操作自然可以实现。但是要知道,猫和狗并不完全是宠物,这里把宠物当做父类就不是很合适,这里应该把宠物定义成协议就合适很多了
宠物猫和宠物狗,利用协议可以这样实现。定义一个 Animal 类,让猫和狗都继承于 Animal。再定义一个宠物的协议,让猫和狗都实现协议。
同时继承父类和协议的时候,父类要写在前面。
swift 是面向协议编程。是因为 swift 中协议的强大。
与面向对象的不同:
面向对象: 动物-宠物-猫。缺点:宠物可能不需要动物的一些属性。
面向协议:动物-猫 协议:宠物
面向协议编程:从协议出发
extension 协议 可以给协议的方法增加默认实现
typealias 与协议结合的使用
typealias 的作用是给类型进行扩展,它与协议放在一起会碰撞出不一样的火花。
协同协议的使用
CustomStringConvertible 用于自定义打印
Comparable 协议用于自定义比较符号
Equatable 协议用于自定义“==”
协议是 Swift 非常重要的一部分,苹果甚至为了他单独出来—面向协议编程,利用协议的优点和灵活性可以使项目的架构更加灵活,拥有更加易于延展的架构。
元祖
在其他语言中很早就是有元祖这个概念,但是对于 oc 程序员这是一个新的概念
将多个相同或者不同类型的值用一个小括号括起来就是一个元祖
元祖其实和结构体很像,只是不需要提前定义类型
元祖其实是复合类型,小括号内可以写任意类型
元祖的其他定义方式:
- 指明应用元祖元素的名称。
- 通过指定的名称提取元祖对应的值,会将对应位置的值赋值给对应位置的名称
如果不关心元祖中的某个值可以利用 “_”通配符忽略提取
以前没有元祖的时候,oc 是通过传入指针或结构体的方式返回多个值,而有了元祖之后就可以简单实现让一个函数返回多个值
元祖的定义:
一元元祖,编译器会优化为其实际元素的类型
元祖支持嵌套
可以将元祖的类型重定义为一个类型名
元祖的数据访问
当元素未命名时,采用自然序号访问,序号从0开始
当元素命名时,可以用命名访问数据,当然仍可以使用序号访问
可以用多个变量同时进行访问
元祖是值类型,元祖的拷贝是值拷贝
元祖的访问级别
取决于它包含的元素,遵循最小的原则。
元祖元素的修改
元祖的数据不能增删,但是可以修改。但是不可以修改类型。
如果是常量定义,元祖的数据就不能更改
如果指定数据类型是 Any,那么可以改变类型
元祖的常见使用场景:
非常适用于 Dictionary 的遍历
非常适合 Array 的 enumrated()
适合函数返回多元数据
函数也可以返回可选元祖
也可以部分元素可选
运算符
算术运算符
Swift 是安全严格的编程语言,会在编译时候检查是否溢出,但是只会检查字面量,不会检查变量,所以在 Swift 中一定要注意隐式溢出。
Swift 中不允许连续赋值
区间:
闭区间 a…b
半闭区间: a..<b
区间只能用于整数,写小数有问题
字典
key 一定是可以 hash 的(String, Int, Double, Bool),
哈希就是将字符串变成唯一的整数,便于查找,提高字典遍历速度
获取 key 对应的值增加 ??
字符串
计算字符串长度:.lengthOfBytes(using:String.Encoding.utf8)
字符串拼接: stringA + stringB
格式化字符串 (format)
字符串比较:== / != / >= / <=
判断前后缀:hasPrefix hasSuffix
大小写转换:uppercased lowercased
转换基本数据类型:强转
字符:Character
Swift 使用双引号
双引号下只能放一个字符。
oc 中字符串是以\0结尾的。
Swift 并不是
break :跳出循环,无论循环条件是否保持还为真都不会再执行循环
continue:跳出本次循环,如果循环条件还为真还会继续执行循环
for
stride(from:to:by)遍历,跨步,开区间
stride(from:through:by)遍历,跨步,闭区间
Swift4 语法
struct 也支持 kvc
KVO: 目前依然只有 NSObject 才支持 KVO
被观察的属性需要用 dynamic 修饰,否则也无法观察到
断言:当程序发生异常时,如果希望找到出错位置并打印一条消息,就可以使用断言,即通过一个全局的函数 assert
assert 接受一个闭包做为其第一个参数,第二个参数是一个字符串。加入第一个闭包的返回值是false,那么这个字符串就会被打印到中控制台上。
assert(()->Bool.”Message”)
precondition:它和 assert 的格式类型,也是动态的,它会造成程序的提前终止并抛出错误信息;
使用扩展的方法向数组增加一个方法判断下标是否越界。
precondition 在一般代码中并不常见,因为它是动态的,只会在程序运行时进行检查。适用于那些无法在编译器确定的风险情况
switch
在 case 中定义变量要加大括号,否则作用域混乱
可以判断对象类型,不可以穿透,不能不写 default,位置必须在最后;定义变量不需要加大括号
区间
元祖匹配
值绑定
根据条件绑定
while
swift 不需要 import 类名也可以使用这个类
参数名称缩写
Swift 自动为内联闭包提供参数缩写功能,你可以使用 $0,$1, m2 依次获取闭包的第1,2,3个参数
CGRect CGRectDivide
将一个 CGRect 在一定位置切分成两个区域
CGRectDivede(rect,&small,&large,20,CGRectMinXEdege)
将 rect 分割成两部分,{0,0,20,100} {20,0,80,100}
swifter 王巍
swift 新元素
柯里化(Currying)
如果你固定某些参数,你将得到接受余下参数的一个函数
函数式编程在把函数当做一等公民的时候,就不可避免的会产生“柯里化”这种用法
柯里化是量产相似方法的好办法
protocol 的方法声明为 mutating
如果没在协议方法里写 mutating 的话,别人如果用 struct 或 enum 实现这个协议的时候就无法在方法里面改变自己的变量了
Sequence
swift 中 for in 这种方式的遍历是针对 sequenceType 这个 protocol 的。为了实现 Sequence 首先需要实现一个 IteratorProtocol。
Sequence 协议扩展已经实现了 map,filter,reduce
map 方法 可以用在 Optionals 和 SequenceType 上,可以把数组元素转变类型
filter 用于选择数组元素中满足某种条件的元素
reduce 用于把数组元素组合计算为一个值
flatMap 返回一个将变换结果连接起来的数组(压平)。空值过滤。
多元组(Tuple)
应用:交换输入 (a, b) = (b, a)
let rect = CGRect(x:0,y:0,width:100,height:100)
let (small,large) = rect.divided(atDistance:20,from: .minXEdge)
@autoclosure 和 ??
@autoclosure 可以说是 Apple 的一个非常神奇的创造,因为这更多的像是在“hack”这门语言
就是把一句表达式自动的封装成一个闭包,这样有时候在语法上看起来就会很漂亮。
logIfTrue(2>1)
不支持带有输入参数的写法
?? 这个操作符可以判断输入并在当左侧的值是非 nil 的 Optional 值时返回其 value,否则返回右侧的值。
内部实现:defaultValue 就是一个 @autoclosure 修饰的参数。
为什么不直接传入 T 参数?
是因为如果这个默认值是经过一系列计算得到的话,可能会造成浪费。
为什么会浪费?
是因为当 optional 不是 nil 的话,我们实际上是完全没有用到这个默认值。而是直接返回解包之后的值。这样的开销是完全可以避免的。方法既是将默认值的计算推迟到了 optional 判定为 nil 之后
@escaping
最简单的 block 做为参数的调用,默认隐藏了一个假设,就是参数中 block 会在 方法返回前完成。也就是说对 block 的调用是同步行为。如果我们改变一下代码,将 block 放到一个 dispatch 中,让它在方法返回后被调用的话,我们就需要在 block 类型前面加上 @escaping 标记来表明这个闭包是会逃逸出该方法的。
没有逃逸行为的闭包,因为作用域就在函数内部,所以不用担心闭包内部持有 self。
有逃逸行为的闭包,Swif 强制我们写明 self,以起到提醒作用,我们就需要考虑 self 的持有情况。如果我们不希望在闭包中持有 self,可以使用 【weak self】的方式来表达。
这时,在闭包执行时已经没有对实例的应用,因此输出为 nil。
Optional Chaining 可选链
Optional Binding 可选绑定
使用可选链可以让我们摆脱很多不必要的判断和取值
因为可选链随时都可能提前返回 nil ,所以使用 optional chaining 所得到的东西都是 optional 的。
使用 optional Binding 来判定方法是否调用成功
操作符
与 OC 不同,swift 支持重载操作符这样的特性。最常见的使用方式就是定义一些简便的计算。比如二维向量的数据结构的操作。
如果想要定义一个全新的操作符,需要自己来做声明。
precedencegroup:操作符优先级别
associativity:结合律
higherThan:运算的优先级
infix:表示定义的是一个中位运算符,即前后都是输入。
Swift 的操作符是不能定义在局部域中的,因为至少为希望能在全局范围内使用你的操作符,否则操作符也就失去意义了.
在重载或者自定义操作符的时候。应当尽量将其作为其他方法的“简便写法”,避免在其中实现大量逻辑或提供独一无二的功能。
来自不同 module 的操作符是有可能冲突的,在使用重载或者自定义操作符时,请先再三权衡斟酌,你或你的用户是否真的需要这个操作符。
func 的参数修饰
Swift 是一门讨厌变化的语言,所有有可能的地方,都被默认为是不可变的。这样不仅可以确保安全,还可以在编译器的性能优化上更有作为。
有时候我们希望在方法内部直接修改输入的值,这时候我们可以用 inout 来对参数进行修饰。在前面加上 & 符号。
值类型,并不能直接修改它的地址来让它指向新的值。
字面量表达
指像特定的数字、字符串或者是布尔值这样,能够直截了当指出自己类型并为变量赋值的值。
在 Swift 中,Array 和 Dictionary 在使用简单的描述赋值的时候,使用的也是字面量。
Swift 为我们提供了一组非常有意思的协议,使用字面量来表达特定的类型。对于那些实现了字面量表达协议的类型,在提供字面量赋值的时候,就可以简单de按照协议方法中定义的规则“无缝对应”的通过赋值的方式将值表达为对应类型。
下标
在绝大多数语言中使用下标读写类似数组或者字典这样的数据结构。
在字典中使用下标访问得到的结果是一个 optional 的值。
做为一门代表了先进生产力的语言,Swift 是允许我们自定义下标的。不仅包含对自己写的类型进行下标定义,也包含那些已经实现下标访问的类型进行扩展。
向数组添加一个接受数组下标输入的读取方法。不推荐使用这样的方式。
方法嵌套
方法成为了一等公民,这样就可以把方法做为变量或者参数来使用了。我们可以在方法内定义新的方法,这个代码结构层次和访问级别的控制带来了新的选择。
另一个重要的考虑是有些方法我们完全不希望在其他地方被直接使用。
命名空间
oc 一直以来令人诟病的地方就是没有命名空间,在应用开发时,所有的代码和引用的静态库都会被编译到同一个域和二进制中。这样的后果一旦我们有重复的类名的话,就会导致编译时的冲突和失败。
在 Swift 中可以使用命名空间了,即使是名字相同的类型,只要是来自不同命名空间的话,就会和平共处的。Swift 的命名空间是基于 module 而不是在代码中显示地指明,每个 module 代表了 Swift 中的一个命名空间。
typealias
为已经存在的类型重新定义名字。可以使用 typealias ,给类型取一个别名,增加代码的可读性。可以不再关心代码里那成堆的 Int 和 string 之类的基本类型代表的是什么东西。
typealias 是单一的,不能将整个泛型类型进行重命名。
另一个使用场景是某个类型同时实现多个协议的时候,我们可以用 & 符号连接协议。然后给一个新的更符合上下文的名字,增强代码可读性
typealias Pet = Cat & Dog
associatedtype
使用 associatedtype 可以在协议中添加一个限定,来指定事物的具体类型。在 Tiger 通过 tyoealias 具体指定 F 为 Meat 之前, 只需要满足协议的类型中 F 和 eat 参数一致即可。不过这里忽视了被吃的必须是 Food 类型这个前提。associatedtype 声明中可以使用冒号来指定类型满足某个协议。另外,在 Tiger 中只要实现了正确类型的 eat,F 的类型都可以推算出来,不需要显式的写明 F。
在添加 assoicatedtype 之后,Animal 协议就不能被当做独立的类型使用了。这是因为 Swift 在编译的时候需要确定所有类型。在一个协议加入了 associatedtype 或者 Self 的约束之后,它将只能被用为泛型约束,而不能做为独立类型的占位使用,也是去了动态派发的特性。
可变参数函数
就是可以接受任意多个参数的函数。我们最熟悉的就是 NSString 的 stringWithFormat: 方法了。
在 Swift 中写一个可变参数的函数只需要在声明参数时在类型后面加上 … 就可以啦。输入的参数在函数体内部将被做为数组来使用。
在 Swift 中可以随意的放置可变参数的位置,而不必拘泥于最后一个参数。
在同一个方法中只能有一个参数是可变的,可变参数都必须是同一个类型的。
数组的两个便利方法:forin forEach
当遍历的时候有 return 关键字的时候就会有区别
初始化方法顺序
设置自己需要初始化的参数
调用父类相关的初始化方法
对父类中的需要改变的成员进行设定
如果没有第三步,第二步也可以省略。默认最后调用第二步。
Swift 中的初始化想要达到什么样的目的。
其实就是安全。Swift 有超级严格的初始化方法。不加修饰的 init 方法都需要在方法中保证所有非 Optional 的实例变量被赋值初始化,而在子类也强制(显示或隐式)调用 super 版本的 designated 初始化,所以无论如何走何种路径,被初始化的对象总是可以完成完整的初始化。
在 init 方法里面我们可以对 let 的实例常量进行赋值,这是初始化的重要特点.这是因为在 Swift 中 init 方法只会被调用一次。
在 init 前面加上 convenience 关键字的初始化方法是 Swift 中的“二等公民”,所有的 convenience 的方法都必须调用同一个类中的 designated 初始化完成设置,另外,convenience 方法不能被子类重写也不能从子类使用 super 调用
只要在子类中实现了父类 convenience 所需的 init 方法,我们在子类中就可以使用父类的 convenience 的初始化方法了。
对于某些我们希望子类中一定实现的 designated 方法,可以通过添加 required 关键字进行限制,强制子类对这个方法重写实现。可以保证依赖于某个 designated 初始化方法的 convenience 一直可以被使用。
对于 convenience 方法我们也可以加上required 以确保子类对其实现。这在要求子类不直接使用父类中的 convenience 初始化方法时有帮助。
初始化返回 nil
Swift 中我们可以在 init 声明时在其之后加上 ? 或者 !来表明初始化失败可能会返回一个 nil。所有的结果都将是 Optional 类型,我们可以通过 Optional Binding,就能知道是否初始化成功,并安全的使用它们了。 我们在这类初始化方法中还可以对 self 赋值,也算是 init 方法里的特权之一
static 和 class
表示“类型范围作用域”。类变量/类方法,静态变量/静态函数。非 class 中,使用 static 来描述类型作用域。包括在 enum 和 struct 中描述类型方法和类型属性时。在这两个值类型中,我们可以在类型范围内声明并使用存储属性、计算属性和方法。
class 关键字是专门用在 class 类型的上下文中的。用来修饰类方法和类计算属性。但不能出现 class 的存储属性。
如果我们想在 protocol 中实现一个类型域上的方法或者计算属性的话,应该用 static 关键字。实现的时候,不管在什么结构类型中,使用 static 是没有问题的。
多类型和容器
Swift 中原生容器类型有三种。它们分别是 Array、Dictionary 和 Set;
它们都是泛型的,也就是说我们在一个集合中只能放入同一个类型的元素。
如果我们要把不相关的类型放入到同一个容器中的话,需要做一些转化工作。
这样的转换会造成部分信息的缺失,我们从容器中取值时只能取到信息完全丢失之后的结果。在使用时还需要进行一次类型转换。这其实是无其他可选方案后的最差选择,因为这样的转换之后,编译器就不能再给我们提供警告信息了。我们可以随意添加,也可以将取出的值随意转换,这是一件非常危险的事情。
我们还可以在容器中添加实现了同一协议的类型的对象,对于对象中存在某种共同特性的情况下无疑是最方便的。
另一种方式是利用 enum 可以带有值得特点,将类型信息封装到特定的 enum 中。
default 参数
Swift 的方法是支持默认参数的,也就是在声明方法的时候,可以给参数制定一个默认使用的值。
在调用的时候,我们如果想要使用默认值的话,只要不出入相应的值就可以了。
默认参数写的是 default,这是含有默认参数的方法所生成的 Swift 的调用接口。当我们指定一个编译时就能确定的常量做为默认参数的取值的时候,这个取值是隐藏在方法内部,而不应该暴露给其他部分。
举个例子:NSLocalizedString(key:String, tableName:String? = default,..)
assert(@autoclosure condition:()->Bool,@autoclosure _ message:()->String = default..)
正则表达式
作为一门先进的编程语言,Swift 可以说是吸收了众多其他先进语言的特点,但是让人略微失望的,就是 Swift 至今没有在语言层面上支持正则表达式。就像 Perl 或者 Ruby 那样的语言使用比如 =~ 这样的符号来进行正则匹配。
最简单的想法自然是自定义 =~ 这个运算符。在 Cocoa 中我们可以使用 NSRegularExpression 来做正则匹配。我们可以先写一个接受正则表达式的字符串,以此生成 NSRegularExpression 对象,然后使用该对象来匹配输入字符串,并返回结果告诉调用者是否匹配成功。
模式匹配
Swift 中的模式匹配只能支持最简单的相等匹配和范围匹配。使用 ~= 来表示模式 匹配的操作符。
在操作符两边分别接受可以判等的类型,可以与 nil 比较的类型,已经一个范围输入和某个特定值,返回值很明了,都是是否匹配成功的 Bool 值。
Swift 的 switch 就是使用了 ~= 操作符进行模式匹配。在 switch 中做 case 判断的时候,我们完全可以使用自定义的模式匹配方法来进行判断。
… 和 ..<
在很多脚本语言中,都有很多类似 0..3 或者 0…3 这样的 Range 操作符,用来简单指定一个从 X 开始连续计数到 Y 的范围。
最基础的用法当然是在两边指定数字。0…3 表示从 0 开始 到 3 为止包含 3 这个数字的范围,成为全闭合的范围。 0..< 3 是不包含最后一个数字的范围。对于这样得到的数字的范围,我们可以对他进行 for…in 的访问。
看看 Swift 对这两个操作符的定义,可以发现都是支持泛型的。可以接受 comparable 的输入,返回 ClosedInterval 或者 HalfOpenInterval 。在 Swift 中除了数字另一个实现 comparable 的基本类型就是 String。
例子:
确认某个单词全部字符都是小写英文字母 interval = a…z
是不是有效的 ASCII 字符 \0…~ ,分别是 ASCII 的第一个和最后一个字符。
AnyClass,元类型和 .self
在 Swift 中能够表示“任意”这个概念的除了 Any 和 AnyObject 外,还有 AnyClass。
AnyClass 在 Swift 中被一个 typealias 定义:typealias AnyClass = AnyObject.Type
。这样得到的是一个元类型。在声明时我们总是在类型后面加上 .Type。代表这个类型的类型。从 A 中取出其类型的时候,我们需要用到 .self。
元类型或者元编程在我们编写某些框架性的代码时非常方便。可以通过读入配置文件之类的方式实现。
DSL:领域专属语言的方式。不触及源码的情况下,很简单的完成一系列复杂的操作。
在注册 tableview 的 cell 的类型的时候就需要输入 AnyClass 。
获得协议的元类型:在protocol 的名字后面使用 .Protocol 获取。
协议和类方法中的 Self
在声明协议的时候,我们希望在协议中使用的类型就是实现这个协议本身的类型的话,就需要使用 Self 进行指代。
使用 type(of:)初始化,保证方法与当前类型上下文无关。
在类方法中也可以使用 Self。核心在于保证子类也能返回恰当的类型。
动态类型和多方法
Swift 中我们虽然可以通过 dynamicType 来获取一个对象的动态类型,但是在使用中,Swift 现在是不支持多方法,不能根据对象在动态时的类型进行合适的类型方法调用。方法的调用只在编译时决定。
Swift 我们可以重载同样名字的方法,而只需要保证参数类型不同。
属性观察
利用属性观察我们可以在当前类型内监视对于属性的设定。Swift 为我们提供两个属性观察的方法,willSet 和 didSet。
属性观察的一个重要用处是作为设置值的验证。
存储属性会在内存中实际分配地址对属性进行存储,而计算属性则不包括背后的存储,只是提供 set 和 get 两种方法。
在同一类型中属性观察和计算属性是不能同时共存的。如果我们无法改动这个类,又想要通过属性观察做一些事情的话,可能就需要子类化这个类,并且重写它的属性。重写的属性并不知道父类属性的具体情况,只是继承了属性的名称和类型,因此在子类的重载属性中我们是可以添加对父类属性的属性观察,而不用在意父类中的属性到底是计算属性还是存储属性。
didSet 中会用到 oldValue,而这个值需要在 set 之前获取并存储,否则无法保证正确性。
final
final 关键字可以用在 class 、func 、var 前面进行修饰,表示不允许对该内容进行继承或重写操作。
为了父类中某些代码一定会被执行。
有时候父类的方法在被继承之后必须执行的,如果子类重写了父类的方法,是没有办法强制子类方法一定会去调用相同的父类方法的。
子类继承和修改是一件危险的事情
lazy 修饰符和 lazy 方法
我们在使用 lazy 做为属性修饰符时,只能声明属性是变量,并且显式的指定属性类型,还有一个给属性赋值的语句在首次访问属性时运行。
在 Swift 标准库中,还有一组 lazy 方法。这些方法可以配合 像 map 或 filter 这类接受闭包并进行运行的方法一起,让整个行为变成延迟执行。
隐式解包 Optional
相对于普通的 Optional ,在 Swift 中还有一种特殊的 Optianal 类型,对他的成员或者方法进行访问的时候,编译器会帮我们自动进行解包,也就是 ImplicitlyUnwrappedOptional。在声明的时候我们可以在类型后面加上一个(!)来告诉编译器我们需要一个可以隐式解包的 optional 值
如果 这个 optional 值是 nil 的话不加检查的写法调用会导致程序奔溃。这是一种危险写法
因为 oc 的 Cocoa 的所有类型变量都可以指向 nil,在将 Cocoa API 从 oc 转移到 Swift 时,无法判定是否存在 nil 的可能。这时候 ,Optional 隐式解包就作为一种妥协方案出现了。使用隐式解包的最大好处是对于那些我们能确定的 api 来说,我们可直接访问属性和调用方法,会很方便。这是一种简单但是危险的使用方式了。
现在比较常见的隐式解包就只有 IBOutlet 了
少用 隐士解包 optional ,推荐多写 optional binding。
多重 Optional
optional 类型是一个 enum。Optional<T> 这个 T 可是任意类型,亦可以是一个 Optional。
如果我们把 optional 比作一个一个盒子,那么打开这个盒子之后可能的结果会有空气、糖果,或者是另一个盒子。
使用 fr v -R 可以打印 optional 值
Optional Map
map 方法可以在 collectionType(这是一个协议) 的 extension 中找到定义
在其他语言中 map 是常见常用的一种语言特性了。
在 optional 的声明中,也有一个 map 方法。这个方法可以让我们方便的对可选值做变化和操作,而不用手动解包。
正符合函数式编程函子的概念。函子指的是可以被函数使用,并映射为另一组结果,而这组结果也是函子的值。
Protocol Extension
在 Swift 中标准库的功能都是基于 protocol 来实现的,举个例子,Array 就是遵守了 collectionType 这个协议的。 CollectionType 是一个非常重要的协议,除了 Array 以外,Dictionary 和 Set 也都实现了这个协议所定义的内容。
为实现了某个协议的所有类型添加一些另外的共通的功能。
我们可以对一个 Protocol 进行扩展,而扩展中实现的方法将作为实现扩展的类型的默认实现。在具体的实现这个协议的类型中,即使什么都不写,也可以编译通过。调用的时候会直接使用 extension 中的实现。
protocol extension 为 protocol 中定义的方法提供了一个默认实现。
where 和模式匹配
在 switch 语句中,使用 where 来限定某些条件 case,这可以说是模式匹配的标准用法。
在 for in中 可以使用 where 做类似的条件限定
在 Swift 3 中,if let 和 guard let 的条件判断不再使用 where 语句,而是和普通的条件判断一样,用逗号写在后面。
这两种方式都可以用另外的 if 来替代,只不过让我们的代码更加的易读了。也有一些场合只有 where 才能准确表达的。比如我们在泛型中想要对方法的类型进行限定的时候。
例如 :标准库里对 RawRepresentable 协议定义 != 运算符。
当我们希望一个协议扩展的默认实现只在一些特定的情况下适用,我们就可以用 where 关键字来进行限定。
例如:Sequence 的 sorted 方法就被限定在这样一个类型限定的协议扩展中。
indirect 和嵌套 enum
涉及到数据结构的理论和模型(链表、树、图)时,我们往往会用到嵌套的类型。
2.从 Objective-C 到 Swift
Selector
@selectior 是 oc 时代的关键字,它可以把一个方法转换为 SEL 类型,它的表现很类似一个动态的函数指针。
如果要追求灵活的话,我们更愿意用 NSSelectiorFromString ,因为我们可以在运行时动态生成字符串,从而通过方法的名字调用到方法。
在 Swift 中,我们用 #selector 从暴露给 oc 的方法中获取方法名字。对应原来的 SEL 是一个叫 selector 的结构体。
需要注意的是,selector 其实是 OC runtime 的概念。如果你的 selector 只对 Swift 可见的话(也就是一个 private 方法),在调用这个方法的时候你会遇到一个 unrecognized selector 的错误。正确的方法是在 private 前面加上 @objc 关键字,这样在运行时就可以找到方法了。
值得一提的是,如果方法名字在方法所在域内是唯一的话,我们可以只是用方法名字做为 #selector 的内容。相比于带有冒号写法的完整形式来说,这么写起来会方便一些。
但是如果同一个作用域中存在相同名字的方法,我们可以使用强制转化来区分它们。
实例方法的动态调用
在 Swift 中有这样一种写法,可以不直接使用实例来调用这个实例的方法,而是通过类型取出这个类型的某个实例方法的签名,然后再通过传递实例来拿到实际需要调用的方法。
Swift 可以用 Type.instanceMethod 的语法来生成一个柯里化的方法。
如果遇到有类型方法的冲突时,默认取到的是类型方法,如果想要取得实例方法,可以显式声明类型。
单例
对于一些希望能在全局方便访问的实例,或者在 app 的生命周期内只应该存在一个的对象,我们一般使 用单例进行存储和访问。
在 oc 中使用 GCD 保证代码只执行一次。保证单例在线程上的安全。
可以用dispatch_once 保证线程安全,但是在 Swift 中其实有一种更简单的保持线程安全的方式,就是 let。
Swift 1.2 之前不支持 static let 和 static var 这样的存储类变量。在 1.2 中支持了类变量。
在初始化变量的时候,Swift 会把初始化包装在 swift_once_block_invoke 中,保证唯一性。
在类型中加入了一个私有的初始化方法,来覆盖默认公开的初始化方法,这使的项目中的其他地方不能通过 init 初始化单例实例,保证了类型单例的唯一性。
如果你需要的是类似 default 的形式的单例(也就是可以创建自己的实例),可以去掉这个私有的 init 方法。
条件编译
在 c 系语言中,可以使用 #if #ifdef 之类的编译条件分支来控制哪些代码需要编译。Swift 没有宏定义的概念,因此不能用 #ifdef 方法来检查某个符号是否经过宏定义。
if #else #endif
编译标记
// MARK: 粗体标签的形式将名称显示在导航栏
// TODO:还没完成
// FIXME:需修改
Swift 中没有类似 OC 的 #warning
@UIApplicationMain
调用方法,根据第三个参数初始化一个 UIApplication 或其子类的对象并开始接受事件(传入nil。默认 UIApplication),最后一个参数指定了 Appdelegate 作为应用的代理。用来接收 didFinishLaunching 或者 didEnterBackground 这样的与应用生命周期相关的委托方法。
Swift 中在默认的 Appdelegate 类的声明上方有一个 @UIAppdelegateMain 的标签。这个标注做的事情就是将标注的类作为委托。创建一个UIapplication并启动程序。
Swift 中也可以有一个 main.swift 的文件,不需要定义作用域,直接书写代码。这个文件中的代码将作为 main 函数执行。
我们可以将第三个参数替换为我们自己的 UIApplication 子类,这样我们就可以轻易做一些控制整个应用行为的事情了。
例如:每次发送事件(点击按钮),我们都可以监听到这个事件了。
@objc 和 dynamic
虽然说 Swift 的初衷是摆脱 oc 沉重的历史包袱,但是不可否认的是,经过二十多年的洗礼,Cocoa 框架早就烙上了不可磨灭的 oc 的印记。无数的第三方库是 oc 写成的,这些积累不容小觑。所以 Swift 做了与 oc 的兼容
Apple 的做法是允许我们在同一个项目中同时使用 Swift 和 oc 开发。其实一个项目中的 Swift 和 oc 是处于两个世界中的,为了能够让他们能够互通,我们需要添加一些桥梁。
通过添加{product-module-name}-Bridging-Header.h 文件,并在其中填写想要使用的头文件名称,我们就可以在 Swift 中使用 oc 代码了。
如果想要在 oc 中使用 Swift 代码,可以直接导入自动生成的头文件 {product-module-name}-Swift.h 来完成。
oc 对象是基于运行时,骨子里遵循了 KVC 和动态派发,在运行时再决定具体实现。Swift 为了追求性能,类型和方法在编译时就已经决定,运行时不再需要经过一次查找,可以直接使用。
在 Swift 类型中,我们可以把需要暴露给 oc 的任何地方加上 @objc 修饰符。
可选协议和协议扩展
Swift 中的 protocol 的所有方法都必须被实现,那些如果没有实现协议就无法正常工作的方法一般是必须的,而相对的像做为事件通知或对非关键属性进行配置的方法一般都是可选的。
如果我们想要将 Swift 像 oc 那样定义可选的协议方法,就需要将协议本身和方法都定义成 oc 的。也就是在 protocol 定义之前已经协议方法之前加上 @objc。和 oc 不同的是我们使用 optional 来定义可选方法。
对于所有的声明,它们的前缀都是分开的,也就是说你不能像在 oc 里那样用一个 @optional 指定接下来的若干个方法都是可选的了。必须对每一个可选方法添加前缀,对于没有前缀的方法来说,它们是默认必须实现的。
一个不可避免的限制是,使用 @objc 修饰的 protocol 就只能被 class 实现了。对于 struct 和 enum 来说。我们是无法让他们所实现的协议中含有可选方法或者属性的。另外,实现它的 class 中的方法还必须被标记为 @objc。或者整个类继承自 NSObject。
在 Swift 2.0 后,我们有了另一种选择,那就是使用 protocol extension。我们可以在声明一个 protocol 之后再用 extension 的方式给出部分方法的默认实现。这样这些方法在实际的类中就是可选实现了。
内存管理,weak 和unowned
Swift 是自动管理内存的,初始化创建一个对象时,替我们管理和分配内存。释放的原则遵循自动引用计数的原则:当一个对象没有引用的时候,其内存将会被自动回收。这套机制从很大程度上简化了我们的代码,我们只要保证在合适的时候将引用置空,就可以确保内存不会出错。
但是所有自动引用计数机制都有一个无法绕过的限制,那就是循环引用。
在 Swift 里防止循环引用,我们必须给编译器一点提示,表示我们不希望它们互相持有。一般来说我们希望“被动”的一方不要去持有“主动”的一方。
unowned 更像是 oc 的 unsafe_unretained。unowned 设置以后即使它原来引用的内容已经被释放了,它仍然会保持对被已经释放了的对象的一个“无效的”引用。他不能是 optional 值,也不会指向 nil。如果你尝试调用这个引用的方法或者访问成员属性的话,程序就会崩溃。而 weak 就友好些,在引用的内容被释放之后,标记为 weak 的成为会被置为 nil(所以被标记为 weak 的变量一定是 optional ),关于两者的选择,Apple 给我们的选择是如果能确保使用时不会被释放,尽量使用 unowned,如果存在被释放的可能,那就选择用 weak。
闭包循环引用。如果我们可以确定在整个过程中 self 不会被释放的话,可以把 weak 改为 unowned ,这样可以不判断 StrongSelf。
这种在闭包参数的位置进行标注的语法结构是将要标注的内容放在参数的前面,并使用中括号括起来。如果有多个需要标注的元素,用逗号隔开。
@autoreleasepool
Swift 在内存管理上使用的是自动引用计数的一套方法,在 ARC 下虽然不需要手动的调用 retain, release 这样的方法来管理引用计数,但是这些方法还是会被调用,只是编译器在编译时帮我们在合适的地方加入了而已。
在 app 中,整个主线程是在自动释放池中运行的,
autoreleasepool 的使用情况。1.再循环中一直不断创建 autorelease 对象。可以再循环中加入释放池。但是每一次循环都生成自动释放池,虽然可以保证内存使用达到最小,但是释放过于频繁可能回带来性能忧虑。
在 swift 中更提倡用初始化的方法而不是用类方法来生成对象。使用初始化的话,我们就不需要面临自动释放的问题。每次超过作用域,自动内存管理将会为我们做好内存相关的事情。
值类型和引用类型
Swift 的类型分为值类型和引用类型两种,值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个“指向”。struct 和 enum 是值类型,class 是引用类型。Swift 中的内建类型都是值类型,不仅包括传统意义像 Int,Bool,甚至 String, Array, Dictionary 都是值类型。
使用值类型的好处。一个显而易见的优势就是减少了堆上内存分配和回收的次数。Swift 的值类型,特别是数组和字典这样的容器,在内存管理上经过了精心的设计。值类型的一个特点是在传递和赋值时进行复制,肯定会产生额外开销,但是在 Swift 中这个消耗被控制在了最小范围,在没有必要复制的时候,值类型的复制都是不会发生的。也就是说,简单的复制,参数的传递等等普通的操作,虽然我们可能用不同的名字来回设置和传递值类型,但是在内存上他们都是同一块内容。
值类型被复制的时机是值类型的内容发生改变时。
值类型在复制时,会将存储在其中的值类型一并进行复制,而对于其中的引用类型来说,则只复制一份引用。
虽然将数组和字典设置为值类型最大的考虑是为了线程安全,但是这样的设计在存储的元素或条目较少时,给我们带来一个优点,就是非常高效。但是在少数情况下,当容器内容比较多,并且还要对内容进行增加或删除,在这时,Swift 内建的值类型的容器在每次操作的时候都要复制一遍。这个开销就就不容忽视。幸好我们有 Cocoa 中的引用类型来应对,就是 NSMutable。
String 还是 NSString
像 String 这样的 Swift 类型对于 Foundation 对应的类是可以无缝转换的。
Swift 中 string 是 struct 类型,相比起 NSObject 的 NSString 类来说,更切合字符串的不变特性。
使用 String 唯一一个比较麻烦的地方在于他和 Range 的配合。在使用 String 对应的 API ,NSRange 会被映射成他在 Swift 中且对应 String 的特殊版本:Range<String.Index>.这时候就非常讨厌。这种情况下,将 String 转为 NSString 是个不错的选择
UnsafePointer
不安全的指针
指针在 Swift 中并不被提倡,语言标准中也是完全没有与指针完全等同的概念的。为了与庞大的 c 系帝国进行合作,Swift 定义了一套对 C 语言指针的访问和转换方法。就是 UnsafePointer 和他的一系列变体。对于使用 C API 时如果遇到接受内存地址做为参数,或者返回是内存地址的情况,在 Swift 中会转为 UnsafePoin<Type> 的类型。
const 对应 UnsafePointer,可变对应 UnsafeMutablePointer
在 C 中,对某个指针进行取值使用 *,在 Swift 中使用 meemory 属性读取相应内存中存储的内容。通过传入指针地址进行方法调用就比较类似了,都是 &。
C 指针内存管理