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