自定義
Description內(nèi)容打印
- 通過遵守
CustomStringConvertible、CustomDebugStringConvertible協(xié)議來自定義實例打印字符串- 遵守
CustomStringConvertible、CustomDebugStringConvertible協(xié)議 - 實現(xiàn)協(xié)議中的
description: String、debugDescription: String類型變量get方法來確保內(nèi)容自定義
- 遵守
- 需要注意的點
-
print調(diào)用的是CustomStringConveritable協(xié)議的description -
debugPrint、po調(diào)用的是CustomDebugStringConveritable協(xié)議的debugDescription
-
關(guān)于
Self的使用
- 特點
-
Self代表當(dāng)前的類型 -
Self一般用作返回值類型,限定返回值跟方法調(diào)用者必須是同一類型(也可以作為參數(shù)類型)。比如協(xié)議中定義一個方法,方法的返回值可以是Self來確保實現(xiàn)協(xié)議的類型描述
-
關(guān)于斷言
assert
- 特點
- 常用于調(diào)試階段,默認(rèn)debug模式生效,發(fā)布模式會忽略
- 通過在
other Swift flags中設(shè)置Debug模式下的-assert-coinfig Release來強制關(guān)閉斷言 - 通過在
other Swift flags中設(shè)置Release模式下的-assert-coinfig debug來強制關(guān)閉斷言
//條件滿足true的時候才會繼續(xù)執(zhí)行,否則中斷進程,打印錯誤
assert(fasle, "斷言錯誤")
關(guān)于
fatalError錯誤
- 特點
-
fatalError錯誤是無法被do-catch所捕獲的 - 使用了
fatalError函數(shù),不需要再寫return返回值了 - 在某些不得不實現(xiàn)、但是又不希望別人調(diào)用的方法,可以考慮內(nèi)部使用
fatalError函數(shù)
-
訪問控制
-
Swift提供了五個不同層面的訪問限制
-
open: 允許其他模塊訪問,并允許其他模塊進行繼承、重寫(open只能用在類、類成員上) -
public: 允許在定義實體的模塊、其他模塊中訪問,不允許其他模塊進行繼承、重寫 -
internal: 只允許在定義實體的模塊訪問,不允許在其他模塊訪問 -
fileprivate:只允許定義實體的源文件中訪問 -
private:只允許定義實體的封閉聲明中訪問
注: 絕大部分實體默認(rèn)都是
internal級別 -
-
訪問級別的使用準(zhǔn)則
- 一個實體不能被更低訪問級別的實體定義
- 變量/常量類型 >= 變量/常量
- 父類 >= 子類
- 父協(xié)議 >= 子協(xié)議
- 原類型 >= typealias
- 原始值類型、關(guān)聯(lián)值類型 >= 枚舉類型
- 定義類型A時用到的其他類型 >= 類型A
class Person{}
public typealias MuyPerson = Person //該語句會報錯,原因是public訪問級別低于class Person 的默認(rèn)訪問級別 internal
元組類型
元組類型的訪問級別是所有成員類型最低的那個泛型類型
泛型類型的訪問級別是類型的訪問級別以及所有泛型類型參數(shù)的訪問級別中最低的那個-
成員嵌套類型
- 類型的訪問級別會影響成員(屬性、方法、初始化器、下標(biāo))、嵌套類型的默認(rèn)訪問級別
- 一般情況下,類型為
private或者fileprivate,那么成員/嵌套類型默認(rèn)也是private或者fileprivate - 一般情況下,類型為
internal或者public,那么成員/嵌套類型默認(rèn)是internal
注:
- 父類的訪問權(quán)限要小于子類
- 直接在全局作用域下定義的
private等價于fileprivate
public class PublicClass {
public var p1 = 0
var p2 = 0 //internal
fileprivatre func f1() {} //fileprivate
private func f2() {} //private
}
class InternalClass { // internal
var p = 0 // internal
fileprivate func f1() {} //fileprivate
private func f2 () {} //private
}
class Test {
private class Person {} //private所作用的范圍是外面的大括號
fileprivate class Student : Person {} //會報錯,因為子類Student的訪問權(quán)限低于父類
}
private class Person {} //private所作用的范圍是整個文件,由于文件聲明在全局區(qū)域,所以private作用域等同于fileprivate
fileprivate class Student : Person {} //不會報錯,此時兩者的權(quán)限是一致的
/*
報錯原因:
1. Dog中的成員變量是由private修飾,所以在其所在的作用域內(nèi)是能夠訪問的,由于Person中walk方法在Dog成員變量的作用域外,所以不能夠訪問其成員變量以及方法
2. 由于 Dog 定義在全局區(qū),即使使用關(guān)鍵字 private 修飾,也等同于 fileprivate 修飾,所以 Person 類與其成員變量 Dog 類型訪問權(quán)限一致,所以能夠正常實例
*/
private struct Dog {
private var age: Int = 0
private func run() {}
}
fileprivate struct Person {
var dog: Dog = Dog()
mutating func walk() {
dog.run() // 會報訪問錯誤
dog.age = 1 // 會報訪問錯誤
}
}
-
getter、setter訪問權(quán)限- 兩者默認(rèn)自動接收他們所屬環(huán)境的訪問級別
- 可以給
setter單獨設(shè)置一個比getter更低的訪問級別,用以限制寫的權(quán)限
fileprivate(set) public var num = 10 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 } } } -
公開部分方法的訪問權(quán)限可以通過
public修飾如果想讓某個類的方法能夠提供給其他模塊使用,可以通過
public修飾方法,表示其可以提供給其他模塊使用 -
間接訪問權(quán)限影響
如果在賦值的時候設(shè)置了當(dāng)前的變量訪問權(quán)限,那么其他的沒有被修飾的成員變量也會因此收到同樣的權(quán)限管理,均為修飾變量一樣的訪問權(quán)限
struct Point { fileprivate var x = 0 var y = 0 } var p = Point(x: 10, y: 20)
初始化器
如果一個public類想要在另一個模塊調(diào)用編譯生成的默認(rèn)無參初始化器,必須顯示提供public的無參初始化器,因為public類的默認(rèn)初始化器是 internal 訪問級別
注意:
-
required初始化器必須跟它所屬的類擁有相同的訪問級別 - 如果結(jié)構(gòu)體有
private/fileprivate的存儲實例屬性,那么它的成員初始化器也是private/fileprivate,否則默認(rèn)就是internal
枚舉類型的
case訪問權(quán)限
- 特點
- 不能給
enum的每個case單獨設(shè)置訪問級別 - 每個
case自動接收enum的訪問級別 -
public enum定義的case也是public
- 不能給
協(xié)議的訪問權(quán)限
- 特點
- 協(xié)議中定義的內(nèi)容的權(quán)限,將自動接收協(xié)議的訪問級別,不能單獨設(shè)置訪問級別
- 協(xié)議實現(xiàn)的訪問級別必須 >= 類型的訪問級別,或者 >= 協(xié)議的訪問級別
注意:
public 修飾的類型/結(jié)構(gòu)體,成員變量以及方法的默認(rèn)類型是 internal 類型,權(quán)限比public權(quán)限小,所以此時遵守的協(xié)議的權(quán)限是 public ,所以會報錯
public protocol Runnable {
func run()
}
public class Person : Runable {
internal func run {}
}
擴展的訪問權(quán)限
-
特點
- 如果有顯示設(shè)置擴展的訪問級別,擴展添加的成員自動接收擴展級別
- 如果沒有顯示設(shè)置擴展的訪問級別,擴展添加的成員默認(rèn)訪問級別,跟直接在類型中定義的成員一樣
- 可以單獨給擴展添加的成員設(shè)置訪問級別
- 不能給用于遵守協(xié)議的擴展顯示設(shè)置擴展的訪問級別
-
擴展的多重聲明
- 在同一個文件中的擴展,可以寫成類似多個部分的類型聲明
- 在原本的聲明中聲明一個私有成員,可以在同一文件的擴展中訪問它
- 擴展中聲明一個私有成員,可以在同一個文件的其他擴展中、原本聲明中訪問它
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()
}
}
- 技巧:方法的拆分存儲調(diào)用
有時候需要將某些方法暫存到變量中,等到合適的實際動態(tài)去調(diào)用它,可以參照如下的方法:
- 將需要調(diào)用的方法取出來并存到變量中
- 實例方法所在的類并傳入到第一步的方法中,并存儲到變量中
- 通過第二步存儲的方法進行相關(guān)調(diào)用
struct Person {
var age: Int
func run(_ v: Int) { print("func run"), age, v }
}
/*
調(diào)用步驟
*/
var fn1 = Person.run
var fn2 = fn1(Person(age: 10))
fn2(20)
當(dāng)存在兩個重名的類型方法與實例方法的時候,在取值的時候,設(shè)定取值結(jié)果的類型來確定當(dāng)前的靜態(tài)方法或者實例方法
struct Person {
var age: Int
func run(_ v: Int) { print("func run", age, v) }
static func run(_ v: Int) { print("static func run", v) }
}
//取實例方法
var fn: (Person) -> (Int) -> () = Person.run
fn(Person(age: 20))(20)
//取靜態(tài)方法
var fn1: (Int)->() = Person.run
fn1(20)
內(nèi)存管理
- 特點
- 跟OC一樣,
Swift也是采用引用技術(shù)ARC內(nèi)存管理技術(shù)方案來管理內(nèi)存的(針對于堆空間) - 引用默認(rèn)都是強引用
- 通過
weak定義弱引用(ARC自動給弱引用設(shè)置為nil的時候,不會觸發(fā)屬性觀察器) - 無主引用(unowned reference):通過
unowned定義無主引用,改引用不安全,會產(chǎn)生野指針 -
weak、unowned的使用只能使用在類的實例上面
- 跟OC一樣,
-
循環(huán)引用
-
weak、unowned可以解決循環(huán)引用問題,unowned要比weak少一些性能損耗 - 在生命周期中可能會變成
nil的時候使用weak - 初始化賦值之后再也不會變成
nil的時候使用unowned
-
閉包的循環(huán)引用問題
閉包表達(dá)式默認(rèn)會對用到的外層對象產(chǎn)生額外的強引用(對外層對象進行 retain 操作)
/*
案例一
*/
class Person {
var fn: (()->())?
func run() { print("run") }
deinit { print("deinit") }
}
func test() {
let p = Person()
//會導(dǎo)致循環(huán)引用問題
p.fn = { p.run() }
//解決方法1:weak
p.fn = {[weak p] in
p?.run()
}
//解決方法2:unowned
p.fn = {[unowned p] in
p?.run()
}
//聲明多個弱引用
p.fn = {[weak wp = p, unowned up = p, a = 10 + 20] in
wp?.run()
}
}
/*
案例二:閉包中引用self, self中引用閉包
*/
class Person1 {
//使用weak去除循環(huán)引用問題
lazy var fn: (()->()) = {[weak p = self] in
p?.run()
}
func run() { print("run") }
deinit { print("deinit") }
}
/*
案例三:閉包中引用self之后直接實例運行,不差生循環(huán)引用
以下不會產(chǎn)生引用循環(huán):
閉包聲明之后立即調(diào)用,調(diào)用完成之后,閉包銷毀,返回閉包結(jié)果,此時getAge不會對閉包產(chǎn)生引用,引用的是閉包返回的值結(jié)果,所以此種情況下不會出現(xiàn)循環(huán)引用問題
*/
class Person2 {
var age: Int = 0
//使用weak去除循環(huán)引用問題
lazy var getAge: Int = {
self.age
}()
func run() { print("run") }
deinit { print("deinit") }
}
逃逸閉包
@escaping
- 特點
- 非逃逸閉包、逃逸閉包,一般都是當(dāng)做參數(shù)傳遞給函數(shù)
- 非逃逸閉包:閉包調(diào)用發(fā)生在函數(shù)結(jié)束前,閉包調(diào)用在其作用域范圍內(nèi)
- 逃逸閉包:閉包有可能會在函數(shù)結(jié)束后調(diào)用,閉包調(diào)用逃離了函數(shù)的作用域,需要通過
@escaping聲明修飾
typealias Fn = ()->()
var gFn: Fn?
//非逃逸閉包
func test(_ fn: Fn) { fn() }
//fn逃逸閉包
func test2(_ fn: @escaping Fn) { gFn = fn }
//fn放在dispatch中同樣是逃逸閉包
func test3(_ fn: @escaping Fn) {
DispatchQueue.global().async {
fn()
}
}
func run(){
//這一塊可以根據(jù)具體業(yè)務(wù)來定,如果想要在異步線程執(zhí)行完成之后強制執(zhí)行當(dāng)前dispatch所在區(qū)域方法,
//那么這里直接引用self確保當(dāng)前作用域范圍內(nèi)能夠執(zhí)行完成并不會立即銷毀,可以保證在執(zhí)行完成dispatch之后銷毀內(nèi)容達(dá)到延遲銷毀的目的
//如果需要立即中斷,則可以使用弱引用的方式來完成
DispatchQueue.global().async {[weak p = self] in
p?.fn()
}
}