一、扩展(Extension)
-
1.1、扩展介绍
- Swift中的扩展,有点类似于OC中的分类(Category)
- 扩展可以为枚举、结构体、类、协议添加新功能;可以添加方法、计算属性、下标、(便捷)初始化器、嵌套类型、协议等等
- 扩展不能办到的事情
- 不能覆盖原有的功能
- 不能添加存储属性,不能向已有的属性添加属性观察器 ;原因是:不允许改变原有的内存结构
- 不能添加父类 ;因为牵扯到继承也会改变内存结构的,所以不能添加父类
- 不能添加指定初始化器,不能添加反初始化器
- ...
-
1.2、计算属性、下标、方法、嵌套类型
extension Double { var km: Double { self * 1_000.0 } var m: Double { self } var dm: Double { self / 10.0 } var cm: Double { self / 100.0 } var mm: Double { self / 1_000.0 } } extension Int { func repetitions(task: () -> Void) { for _ in 0..<self { task() } } mutating func square() -> Int { self = self * self return self } enum Kind { case negative, zero, positive } var kind: Kind { switch self { case 0: return .zero case let x where x > 0: return .positive default: return .negative } } subscript(digitIndex: Int) -> Int { var decimalBase = 1 for _ in 0..<digitIndex { decimalBase *= 10 } return (self / decimalBase) % 10 } } extension Array { subscript(nullable idx: Int) -> Element? { if (startIndex..<endIndex).contains(idx) { return self[idx] } return nil } }
-
1.3、协议、初始化器
如果希望自定义初始化器的同时,编译器也能够生成默认初始化器(可以在扩展中编写自定义初始化器),如下面的
Point
除了系统生成的 4 个初始化器,还多了扩展中的 1 个初始化器-
required初始化器也不能写在扩展中
class Person { var age: Int var name: String init(age: Int, name: String) { self.age = age self.name = name } } extension Person : Equatable { static func == (left: Person, right: Person) -> Bool { left.age == right.age && left.name == right.name } convenience init() { self.init(age: 0, name: "") } } struct Point { var x: Int = 0 var y: Int = 0 } extension Point { init(_ point: Point) { self.init(x: point.x, y: point.y) } } var p1 = Point() var p2 = Point(x: 10) var p3 = Point(y: 20) var p4 = Point(x: 10, y: 20) var p5 = Point(p4)
-
1.4、协议
-
如果一个类已经实现了协议的所有要求,但是还没有声明它遵守了这个协议,可以通过扩展来让它遵守这个协议,如下
protocol TestProtocol { func test() } class TestClass { func test() { print("test") } } extension TestClass: TestProtocol {}
-
编写 一个 函数,判断一个整数是否为奇数?
extension BinaryInteger { func isOdd() -> Bool { self % 2 != 0 } }
提示:整数是继承于
BinaryInteger
,在扩展里面写比较好,这样只要是遵守 BinaryInteger 协议的都可以调用 -
扩展可以为协议提供默认实现,也间接实现
[可选协议]
的效果protocol TestProtocol { func test1() } extension TestProtocol { func test1() { print("TestProtocol test1") } func test2() { print("TestProtocol test2") } // 扩展类方法 static func test3() { print("TestProtocol test3") } } var cls = TestClass() cls.test1() // TestClass test1 cls.test2() // TestClass test2 var cls2: TestProtocol = TestClass() cls2.test1() // TestClass test1 cls2.test2() // TestProtocol test2
提示:这里主要说下
cls2.test2()
为什么打印TestProtocol test2
- 这是一个细节在协议中没有声明一个方法,在协议扩展里面实现了一个新的方法如上面
extension TestProtocol
里面的test2()
,但是在遵守协议的类里面实现了协议中没声明的方法test2()
,在var cls2: TestProtocol = TestClass()
,特指出协议TestProtocol,那么在cls2.test2()
调用的时候,如下协议里面没声明test2()
,就会去调用协议扩展里面实现的test2()
方法
- 这是一个细节在协议中没有声明一个方法,在协议扩展里面实现了一个新的方法如上面
-
-
1.5、泛型
class Stack<E> { var elements = [E]() func push(_ element: E) { elements.append(element) } func pop() -> E { elements.removeLast() } func size() -> Int { elements.count } }
扩展中依然可以使用原类型中的泛型类型 extension Stack {
func top() -> E { elements.last! } }
符合条件才扩展
extension Stack : Equatable where E : Equatable { static func == (left: Stack, right: Stack) -> Bool { left.elements == right.elements } }
二、访问控制(Access Control)
-
2.1、访问权限的 5 个级别
在访问权限这块,Swift提供了 5 个不同的访问级别(以下是从高 -> 低
排列,实体指被访问级别修饰的内容,模块等同于文件)-
open:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(
open
只能用在类、类成员上) - public:允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
-
internal:只允许在定义实体的模块中进行访问,不允许在其他模块中进行访问
提示:
internal
只允许在当前的项目访问,不允许成为一个库,在其他模块进行使用 -
fileprivate:只允许在定义实体的原文件中访问
提示:
fileprivate
代表只能在当前的.swift
文件中进行访问 -
private:只允许在定义实体的封闭声明中访问
提示:
private
:只允许在封闭的实体内访问的意思是,如下:age 只能在person 的大括号内进行访问class person { private var age: Int = 0 }
提示:绝大部分实体默认都是 internal 级别
-
open:允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写(
-
2.2、访问级别的使用准侧,一个实体不可以被更低访问级别的实体定义,如下
-
变量/常量类型
≥
变量/常量,如下:Person的类型(fileprivate)是小于 person 变量类型(internal)的 -
参数类型、返回值类型
≥
函数,如下, 默认:Int、Double 都是 publicfileprivate func test(_ num: Int) -> Double { return 2.0 }
父类
≥
子类,这个比较好理解,因为我们访问子类的时候,必然会用到父类父协议
≥
子协议-
原类型
≥
typealias -
原始值类型、关联值类型
≥
枚举类型 定义类型 A 时用到其他类型
≥
类型A
-
-
2.3、元组类型的访问级别是所有成员类型最低的那个,也就是下面的
data1
和data2
的级别要小于右边元祖中最小的级别internal struct Dog {} fileprivate class Person {} // (Dog, Person)的访问级别是fileprivate fileprivate var data1: (Dog, Person) private var data2: (Dog, Person)
-
2.4、泛型类型
泛型类型的访问级别是 类型的访问级别 以及 所有泛型类型参数的访问级别 中最低的那个internal class Car {} fileprivate class Dog {} public class Person<T1, T2> {} fileprivate var p = Person<Car, Dog>()
Person<Car, Dog>
的访问级别是fileprivate
-
2.5、成员、嵌套类型
类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别一般情况下,类型为
private
或fileprivate
,那么成员\嵌套类型默认也是private
或fileprivate
-
一般情况下,类型为
internal
或public
,那么成员\嵌套类型默认是internal
public class PublicClass { public var p1 = 0 // public var p2 = 0 // internal fileprivate func f1() {} // fileprivate private func f2() {} // private } class InternalClass { // internal var p = 0 // internal fileprivate func f1() {} // fileprivate private func f2() {} // private } fileprivate class FilePrivateClass { // fileprivate func f1() {} // fileprivate private func f2() {} // private } private class PrivateClass { // private func f() {} // private }
-
2.6、成员的重写
- 子类重写成员的访问级别必须 ≥ 子类的访问级别,或者 ≥ 父类被重写成员的访问级别
- 父类的成员不能被成员作用域外定义的子类重写
-
2.7、观察编译代码
-
下面的代码能过不能通过,要看放的位置,如果:在一个文件内编译器不报错,不在同一个文件内会报错
private class Person {} fileprivate class Student : Person {} private struct Dog { var age: Int = 0 func run() {} } fileprivate struct Person { var dog: Dog = Dog() mutating func walk() { dog.run() dog.age = 1 } }
-
特别指出一下下面的代码:
private struct Dog
里面的age
和run()
其实也是 private ,只是在访问域是 和Dog
一个等级,所以在Person
里面也可以访问class test { private struct Dog { var age: Int = 0 func run() {} } fileprivate struct Person { var dog: Dog = Dog() mutating func walk() { dog.run() dog.age = 1 } } }
提示:如果我们可以在
var age: Int = 0
前面加private
,那么dog.age = 1
会直接报错
-
-
2.8、getter、setter
class Person { private(set) var age = 0 fileprivate(set) public var weight: Int { set {} get { 10 } } internal(set) public subscript(index: Int) -> Int { set {} get { index } } } var person = Person() person.age = 100 print(person.age)
提示:person.age = 100 会直接报错,因为我们在
private(set) var age = 0
设置的是private(set)
,这样属性只能访问不能修改定义一个全局变量,只能在该文件内进行修改,其他文件内只能读,如下
fileprivate(set) public var num = 10
-
2.9、初始化器
-
如果一个 public 类想在另一个模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器;因为public类的默认初始化器是internal级别;如下如果 Person 是在一个 .dylib 里面的,那么想要访问
var person = Person()
,Person 类里面的init() {}
前面必须加public
public class Person { public init() { } } var person = Person()
required 初始化器 ≥ 它的默认访问级别
-
如果结构体有
private\fileprivate
的存储实例属性,那么它的成员初始化器也是private\fileprivate
;否则默认就是internalstruct Point { fileprivate var x = 0 var y = 0 } var point = Point(x: 10,y: 20)
提示:在其他文件
var point = Point(x: 10,y: 20)
会报错,因为fileprivate var x = 0
设置后,整个指定初始化器都是fileprivate
-
-
2.10、枚举类型的 case
不能给enum的每个case单独设置访问级别
-
每个case自动接收enum的访问级别;public enum定义的case也是public
public enum Season { case spring case summer }
-
2.11、协议
协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别;public协议定义的要求也是public
-
协议实现的访问级别必须 ≥ 类型的访问级别,或者 ≥ 协议的访问级别
public protocol Runnable { func run() } fileprivate class Person : Runnable { fileprivate func run() {} }
提示:
fileprivate func run() {}
的权限要大于等于类 Person
和协议Runnable
中的一个
-
2.12、扩展
-
如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别,如下:
run()
的权限就是fileprivate
class Person {} fileprivate extension Person { func run() { } }
-
如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样 ,如下:
func run()
写在 类 和 扩展里面没啥区别class Person { func run() { } } extension Person { }
-
可以单独给扩展添加的成员设置访问级别,如下面给
func run()
设置fileprivate
class Person {} extension Person { fileprivate func run() { } }
-
不能给用于遵守协议的扩展显式设置扩展的访问级别,如下:不能在
extension Person
前面设置访问级别protocal Runnable {} class Person {} extension Person: Runnable { func run() { } }
-
在同一文件中的扩展,可以写成类似多个部分的类型声明
在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
-
在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它
public class Person { private func run0() {} private func eat0() { run1() } } extension Person { private func run1() {} private func eat1() { run0() } } extension Person { private func eat2() { run1() } }
提示:如果上面的段代码在同一个文件内,那么后面两个代码的功能类似写在第一段代码里面,所有虽然写的
private
,但是可以访问
-
-
2.13、讲方法赋值给let或者var
方法也可以像函数那样,赋值给一个let或者varstruct Person { var age: Int func run(_ v: Int) { print("func run", age, v) } static func run(_ v: Int) { print("static func run", v) } } let fn1 = Person.run fn1(10) // static func run 10 let fn2: (Int) -> () = Person.run fn2(20) // static func run 20 let fn3: (Person) -> ((Int) -> ()) = Person.run fn3(Person(age: 18))(30) // func run 18 30
三、内存管理
-
3.1、内存管理
- 跟OC一样,Swift也是采取基于引用计数的ARC内存管理方案(针对堆空间)
- Swift的ARC中有3种引用
强引用(strong reference):默认情况下,引用都是强引用
-
弱引用(
weak
reference):通过weak定义弱引用提示:弱引用
- 必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为nil
- ARC自动给弱引用设置nil时,不会触发属性观察器
-
无主引用(
unowned
reference):通过unowned定义无主引用- 不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC中的
unsafe_unretained
) - 试图在实例销毁后访问无主引用,会产生运行时错误(野指针)
- Fatal error: Attempted to read an unowned reference but object 0x0 was already deallocated
- 不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC中的
-
3.2、weak 和 unowned 的使用限制
weak
和unowned
只能用在类实例上面,类是放在 堆空间 的protocol Livable : AnyObject {} class Person {} weak var p0: Person? weak var p1: AnyObject? // AnyObject 代表所有类的实例 weak var p2: Livable? // 写上 : AnyObject 代表这个协议只能被类遵守 unowned var p10: Person? unowned var p11: AnyObject? unowned var p12: Livable?
-
3.3、autoreleasepool,有时候为了缓解内存压力,我们可以把创建实例放在自动释放池里面
public func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result autoreleasepool { let p = MJPerson(age: 20, name: "Jack") p.run() }
-
3.4、循环引用 (Reference Cycle)
- weak 和 unowned 都能解决循环引用的问题,unowned 要比 weak 少一些性能消耗
- 在生命周期中可能会变为 nil 的使用 weak
- 初始化赋值后再也不会变为 nil 的使用 unowned
-
3.5、闭包的循环引用
闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行 retain 操作)
-
下面的代码会造成循环引用,导致 Person 对象无法释放(看不到 Person 的 deinit 被调用)
class Person { var fn: (() -> ())? func run() { print("run") } deinit { print("deinit") } } func test() { let p = Person() p.fn = { p.run() } } test()
提示:
对象 p 强引用闭包表达式,而闭包表达式又强引用对象 p,从而造成相互强引用(循环引用)
解决循环引用的办法- 捕获列表
-
办法一:
[weak 对象]
,这样就代表闭包表达式对 对象p 进行弱引用p.fn = { [weak p] in p?.run() }
-
办法二:
[unowned 对象]
,这样就代表闭包表达式对 对象p 进行弱引用p.fn = { [unowned p] in p.run() }
-
如果闭包有参数,比如上面的 var fn: ((Int) -> ())?,那么解决循环引用如下,age 是参数的名字,捕获列表 [unowned p] 要写在参数列表的前面,否则会报错
p.fn = { [unowned p](age) in p.run() }
-
如果想在定义闭包属性的同时引用 self,这个闭包必须是 lazy的(因为在实例初始化完毕之后才能引用 self),下面的闭包
fn
内部如果用到了实例成员(属性、方法),编译器会强制要求明确写出selfclass Person { lazy var fn: (() -> ()) = { [weak self] in self?.run() } func run() { print("run") } deinit { print("deinit") } } func test() { let person = Person() person.fn() } test()
-
如果 lazy 属性是闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生命周期就结束了)
class Person { var age: Int = 0 lazy var getAge: Int = { self.age }() deinit { print("deinit") } }
提示 :
lazy var getAge: Int = { self.age }()
虽然闭包表达式对 self 进行了强引用,但是这个闭包表达式是直接()
执行的,对self的强引用也就结束了,等同于lazy var getAge: Int = self.age
,所以不会产生循环引用
-
3.6、@escaping
非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数
非逃逸闭包:闭包调用发生在函数结束前,闭包调用在函数作用域内,函数结束之前闭包就会调用结束
-
逃逸闭包:闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过
@escaping
声明,也就说函数结束后闭包也可能没有进行调用import Dispatch typealias Fn = () -> () // fn是非逃逸闭包 func test1(_ fn: Fn) { fn() } // fn是逃逸闭包 var gFn: Fn? func test2(_ fn: @escaping Fn) { gFn = fn } // fn是逃逸闭包 func test3(_ fn: @escaping Fn) { DispatchQueue.global().async { fn() } }
-
DispatchQueue.global().async也是一个逃逸闭包
class Person { var fn: Fn // fn是逃逸闭包 init(fn: @escaping Fn) { self.fn = fn } func run() { // DispatchQueue.global().async也是一个逃逸闭包 // 它用到了实例成员(属性、方法),编译器会强制要求明确写出self DispatchQueue.global().async { self.fn() } }
提示:
DispatchQueue.global().async { self.fn() }
里面使用self不会造成循环引用,因为是闭包表达式强引用 self,而 self 没有对闭包表达式进行强引用,单向强引用不会造成循环引用
-
3.7、逃逸闭包的注意点:逃逸闭包不可以捕获
inout
参数typealias Fn = () -> () func other1(_ fn: Fn) { fn() } func other2(_ fn: @escaping Fn) { fn() } func test(value: inout Int) -> Fn { other1 { value += 1 } // error: 逃逸闭包不能捕获inout参数 other2 { value += 1 } func plus() { value += 1 } // error: 逃逸闭包不能捕获inout参数 return plus }
-
逃逸闭包不可以捕获 inout 参数,假如可以捕获,我们可以试想一下代码,
abc()
函数结束后 x 变量就销毁了,但是 other2 里面的代码可能还没执行,就会造成内存坏访问fun abc () { var x = 10 test(value:&x) }
-