一、擴展(Extension)
-
1.1、擴展介紹
- Swift中的擴展,有點類似于OC中的分類(Category)
- 擴展可以為枚舉、結(jié)構(gòu)體、類、協(xié)議添加新功能;可以添加方法、計算屬性、下標、(便捷)初始化器、嵌套類型、協(xié)議等等
- 擴展不能辦到的事情
- 不能覆蓋原有的功能
- 不能添加存儲屬性,不能向已有的屬性添加屬性觀察器 ;原因是:不允許改變原有的內(nèi)存結(jié)構(gòu)
- 不能添加父類 ;因為牽扯到繼承也會改變內(nèi)存結(jié)構(gòu)的,所以不能添加父類
- 不能添加指定初始化器,不能添加反初始化器
- ...
-
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、協(xié)議、初始化器
如果希望自定義初始化器的同時,編譯器也能夠生成默認初始化器(可以在擴展中編寫自定義初始化器),如下面的
Point除了系統(tǒng)生成的 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、協(xié)議
-
如果一個類已經(jīng)實現(xiàn)了協(xié)議的所有要求,但是還沒有聲明它遵守了這個協(xié)議,可以通過擴展來讓它遵守這個協(xié)議,如下
protocol TestProtocol { func test() } class TestClass { func test() { print("test") } } extension TestClass: TestProtocol {} -
編寫 一個 函數(shù),判斷一個整數(shù)是否為奇數(shù)?
extension BinaryInteger { func isOdd() -> Bool { self % 2 != 0 } }提示:整數(shù)是繼承于
BinaryInteger,在擴展里面寫比較好,這樣只要是遵守 BinaryInteger 協(xié)議的都可以調(diào)用 -
擴展可以為協(xié)議提供默認實現(xiàn),也間接實現(xiàn)
[可選協(xié)議]的效果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- 這是一個細節(jié)在協(xié)議中沒有聲明一個方法,在協(xié)議擴展里面實現(xiàn)了一個新的方法如上面
extension TestProtocol里面的test2(),但是在遵守協(xié)議的類里面實現(xiàn)了協(xié)議中沒聲明的方法test2(),在var cls2: TestProtocol = TestClass(),特指出協(xié)議TestProtocol,那么在cls2.test2()調(diào)用的時候,如下協(xié)議里面沒聲明test2(),就會去調(diào)用協(xié)議擴展里面實現(xiàn)的test2()方法
- 這是一個細節(jié)在協(xié)議中沒有聲明一個方法,在協(xié)議擴展里面實現(xiàn)了一個新的方法如上面
-
-
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 個不同的訪問級別(以下是從高 -> 低排列,實體指被訪問級別修飾的內(nèi)容,模塊等同于文件)-
open:允許在定義實體的模塊、其他模塊中訪問,允許其他模塊進行繼承、重寫(
open只能用在類、類成員上) - public:允許在定義實體的模塊、其他模塊中訪問,不允許其他模塊進行繼承、重寫
-
internal:只允許在定義實體的模塊中進行訪問,不允許在其他模塊中進行訪問
提示:
internal只允許在當前的項目訪問,不允許成為一個庫,在其他模塊進行使用 -
fileprivate:只允許在定義實體的原文件中訪問
提示:
fileprivate代表只能在當前的.swift文件中進行訪問 -
private:只允許在定義實體的封閉聲明中訪問
提示:
private:只允許在封閉的實體內(nèi)訪問的意思是,如下:age 只能在person 的大括號內(nèi)進行訪問class person { private var age: Int = 0 }
提示:絕大部分實體默認都是 internal 級別
-
open:允許在定義實體的模塊、其他模塊中訪問,允許其他模塊進行繼承、重寫(
-
2.2、訪問級別的使用準側(cè),一個實體不可以被更低訪問級別的實體定義,如下
-
變量/常量類型
≥變量/常量,如下:Person的類型(fileprivate)是小于 person 變量類型(internal)的 -
參數(shù)類型、返回值類型
≥函數(shù),如下, 默認:Int、Double 都是 publicfileprivate func test(_ num: Int) -> Double { return 2.0 } 父類
≥子類,這個比較好理解,因為我們訪問子類的時候,必然會用到父類父協(xié)議
≥子協(xié)議-
原類型
≥typealias -
原始值類型、關聯(lián)值類型
≥枚舉類型 定義類型 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、泛型類型
泛型類型的訪問級別是 類型的訪問級別 以及 所有泛型類型參數(shù)的訪問級別 中最低的那個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,那么成員\嵌套類型默認是internalpublic 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、觀察編譯代碼
-
下面的代碼能過不能通過,要看放的位置,如果:在一個文件內(nèi)編譯器不報錯,不在同一個文件內(nèi)會報錯
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),這樣屬性只能訪問不能修改定義一個全局變量,只能在該文件內(nèi)進行修改,其他文件內(nèi)只能讀,如下
fileprivate(set) public var num = 10 -
2.9、初始化器
-
如果一個 public 類想在另一個模塊調(diào)用編譯生成的默認無參初始化器,必須顯式提供public的無參初始化器;因為public類的默認初始化器是internal級別;如下如果 Person 是在一個 .dylib 里面的,那么想要訪問
var person = Person(),Person 類里面的init() {}前面必須加publicpublic class Person { public init() { } } var person = Person() required 初始化器 ≥ 它的默認訪問級別
-
如果結(jié)構(gòu)體有
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、協(xié)議
協(xié)議中定義的要求自動接收協(xié)議的訪問級別,不能單獨設置訪問級別;public協(xié)議定義的要求也是public
-
協(xié)議實現(xiàn)的訪問級別必須 ≥ 類型的訪問級別,或者 ≥ 協(xié)議的訪問級別
public protocol Runnable { func run() } fileprivate class Person : Runnable { fileprivate func run() {} }提示:
fileprivate func run() {}的權限要大于等于類 Person和協(xié)議Runnable中的一個
-
2.12、擴展
-
如果有顯式設置擴展的訪問級別,擴展添加的成員自動接收擴展的訪問級別,如下:
run()的權限就是fileprivateclass Person {} fileprivate extension Person { func run() { } } -
如果沒有顯式設置擴展的訪問級別,擴展添加的成員的默認訪問級別,跟直接在類型中定義的成員一樣 ,如下:
func run()寫在 類 和 擴展里面沒啥區(qū)別class Person { func run() { } } extension Person { } -
可以單獨給擴展添加的成員設置訪問級別,如下面給
func run()設置fileprivateclass Person {} extension Person { fileprivate func run() { } } -
不能給用于遵守協(xié)議的擴展顯式設置擴展的訪問級別,如下:不能在
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() } }提示:如果上面的段代碼在同一個文件內(nèi),那么后面兩個代碼的功能類似寫在第一段代碼里面,所有雖然寫的
private,但是可以訪問
-
-
2.13、講方法賦值給let或者var
方法也可以像函數(shù)那樣,賦值給一個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
三、內(nèi)存管理
-
3.1、內(nèi)存管理
- 跟OC一樣,Swift也是采取基于引用計數(shù)的ARC內(nèi)存管理方案(針對堆空間)
- Swift的ARC中有3種引用
強引用(strong reference):默認情況下,引用都是強引用
-
弱引用(
weakreference):通過weak定義弱引用提示:弱引用
- 必須是可選類型的var,因為實例銷毀后,ARC會自動將弱引用設置為nil
- ARC自動給弱引用設置nil時,不會觸發(fā)屬性觀察器
-
無主引用(
unownedreference):通過unowned定義無主引用- 不會產(chǎn)生強引用,實例銷毀后仍然存儲著實例的內(nèi)存地址(類似于OC中的
unsafe_unretained) - 試圖在實例銷毀后訪問無主引用,會產(chǎn)生運行時錯誤(野指針)
- Fatal error: Attempted to read an unowned reference but object 0x0 was already deallocated
- 不會產(chǎn)生強引用,實例銷毀后仍然存儲著實例的內(nèi)存地址(類似于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 代表這個協(xié)議只能被類遵守 unowned var p10: Person? unowned var p11: AnyObject? unowned var p12: Livable? -
3.3、autoreleasepool,有時候為了緩解內(nèi)存壓力,我們可以把創(chuàng)建實例放在自動釋放池里面
public func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result autoreleasepool { let p = MJPerson(age: 20, name: "Jack") p.run() } -
3.4、循環(huán)引用 (Reference Cycle)
- weak 和 unowned 都能解決循環(huán)引用的問題,unowned 要比 weak 少一些性能消耗
- 在生命周期中可能會變?yōu)?nil 的使用 weak
- 初始化賦值后再也不會變?yōu)?nil 的使用 unowned
-
3.5、閉包的循環(huán)引用
閉包表達式默認會對用到的外層對象產(chǎn)生額外的強引用(對外層對象進行 retain 操作)
-
下面的代碼會造成循環(huán)引用,導致 Person 對象無法釋放(看不到 Person 的 deinit 被調(diào)用)
class Person { var fn: (() -> ())? func run() { print("run") } deinit { print("deinit") } } func test() { let p = Person() p.fn = { p.run() } } test()提示:
對象 p 強引用閉包表達式,而閉包表達式又強引用對象 p,從而造成相互強引用(循環(huán)引用)
解決循環(huán)引用的辦法- 捕獲列表
-
辦法一:
[weak 對象],這樣就代表閉包表達式對 對象p 進行弱引用p.fn = { [weak p] in p?.run() } -
辦法二:
[unowned 對象],這樣就代表閉包表達式對 對象p 進行弱引用p.fn = { [unowned p] in p.run() } -
如果閉包有參數(shù),比如上面的 var fn: ((Int) -> ())?,那么解決循環(huán)引用如下,age 是參數(shù)的名字,捕獲列表 [unowned p] 要寫在參數(shù)列表的前面,否則會報錯
p.fn = { [unowned p](age) in p.run() }
-
如果想在定義閉包屬性的同時引用 self,這個閉包必須是 lazy的(因為在實例初始化完畢之后才能引用 self),下面的閉包
fn內(nèi)部如果用到了實例成員(屬性、方法),編譯器會強制要求明確寫出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 屬性是閉包調(diào)用的結(jié)果,那么不用考慮循環(huán)引用的問題(因為閉包調(diào)用后,閉包的生命周期就結(jié)束了)
class Person { var age: Int = 0 lazy var getAge: Int = { self.age }() deinit { print("deinit") } }提示 :
lazy var getAge: Int = { self.age }()雖然閉包表達式對 self 進行了強引用,但是這個閉包表達式是直接()執(zhí)行的,對self的強引用也就結(jié)束了,等同于lazy var getAge: Int = self.age,所以不會產(chǎn)生循環(huán)引用
-
3.6、@escaping
非逃逸閉包、逃逸閉包,一般都是當做參數(shù)傳遞給函數(shù)
非逃逸閉包:閉包調(diào)用發(fā)生在函數(shù)結(jié)束前,閉包調(diào)用在函數(shù)作用域內(nèi),函數(shù)結(jié)束之前閉包就會調(diào)用結(jié)束
-
逃逸閉包:閉包有可能在函數(shù)結(jié)束后調(diào)用,閉包調(diào)用逃離了函數(shù)的作用域,需要通過
@escaping聲明,也就說函數(shù)結(jié)束后閉包也可能沒有進行調(diào)用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不會造成循環(huán)引用,因為是閉包表達式強引用 self,而 self 沒有對閉包表達式進行強引用,單向強引用不會造成循環(huán)引用
-
3.7、逃逸閉包的注意點:逃逸閉包不可以捕獲
inout參數(shù)typealias Fn = () -> () func other1(_ fn: Fn) { fn() } func other2(_ fn: @escaping Fn) { fn() } func test(value: inout Int) -> Fn { other1 { value += 1 } // error: 逃逸閉包不能捕獲inout參數(shù) other2 { value += 1 } func plus() { value += 1 } // error: 逃逸閉包不能捕獲inout參數(shù) return plus }逃逸閉包不可以捕獲 inout 參數(shù)-
逃逸閉包不可以捕獲 inout 參數(shù),假如可以捕獲,我們可以試想一下代碼,
abc()函數(shù)結(jié)束后 x 變量就銷毀了,但是 other2 里面的代碼可能還沒執(zhí)行,就會造成內(nèi)存壞訪問fun abc () { var x = 10 test(value:&x) }
-



