SwiftNote
/– 課時22)標準庫源碼分析、項目實戰 –/
要點:
- RxSwift有點重,一旦用了,整個項目就很難脫離它了。
標準庫源碼分析
Swift源碼簡介
- Swift與2015年正式開源,github地址:https://github.com/apple/swift
- 幾個可能會經常看的目錄
- docs:一些文檔
- stdlib:Swift源碼
- lib:C++源碼
- include:C++頭文件
- 標準庫源碼位置
- https://github.com/apple/swift/tree/main/stdlib/public/core
Array分析
- map、filter
- flatMap、compactMap、reduce
Substring分析
- apped、lowercased、uppercased
Optional分析
- map、flatMap、==、??
Metadata分析
反射
- 反射是編程語言中一項強大的能力,比如Java語言的反射機制
- 對于任意一個類型,都能夠動態獲取這個類的所有屬性和方法信息
- 對于任意一個實例,都能夠動態調用它的任意方法和屬性
- Swift的反射機制目前比較弱,通過Mirror類型來提供簡單的反射功能
常用Swift第三方庫
- 網絡請求:https://github.com/Alamofire/Alamofire
- 圖片下載:https://github.com/onevcat/Kingfisher
- JSON訪問:https://github.com/SwiftyJSON/SwiftyJSON
- JSON-Mode轉換:https://github.com/kakaopensource/KaKaJSON
- HandyJSON:https://www.jianshu.com/p/939d33a626a0
Kingfisher注意點
- Kingfisher默認不支持WebP格式的圖片,需要額外安裝KingfisherWebP
- pod ‘KingfisherWebP’
詳細了解Xcode單元測試
/—/
/– 課時21)面向協議編程、響應式編程 –/
協議用途:
利用協議實現前綴效果
利用協議實現類型判斷
響應式編程
- 響應式編程(Reactive Programming,簡稱RP)
- 也是一種編程范式,于1997年提出,可以簡化異步編程,提供更優雅的數據綁定
- 一般與函數式融合在一起,所以也會叫做:函數響應式編程(Functional Reactive Programming,簡稱FRP)
- 比較著名的、成熟的響應式框架
- ReactiveCocoa
- 簡稱RAC,有Objective-C、Swift版本
- 官網:http://reactivecocoa.io/
- GitHub:https://github.com/ReactiveCocoa
- ReactiveX(建議使用這個)
- 簡稱Rx,有眾多編程語言的版本,比如RxJava、RxKotlin、RxJS、RxCpp、RxPHP、RxGo、RxSwift等等
- 官網:http://reactivex.io/
- GitHub:https://github.com/ReactiveX
- ReactiveCocoa
RxSwift
- RxSwift(ReactiveX for Swift),是ReactiveX的Swift版本
- 源碼:https://github.com/ReactiveX/RxSwift
- 中文文檔:https://beeth0ven.github.io/RxSwift-Chinese-Documentation/
- RxSwift的github上已經有詳細的安裝教程,這里只演示CocoaPods方式的安裝
- 模塊說明
- RxSwift:Rx標準API的Swift實現,不包括任何iOS相關的內容
- RxCocoa:基于RxSwift,給iOS UI控件擴展了很多Rx特性
RxSwift的核心角色
- Observable:負責發送事件(Event)
- Observer:負責訂閱Observable,監聽Observable發送的事件(Event)
- Event有3種
- next:攜帶具體數據
- error:攜帶錯誤信息,表明Observable終止,不會再發出事件
- completed:表明Observable終止,不會再發出事件
創建、訂閱Observable1
創建、訂閱Observable2
Disposable
- 每當Observable被訂閱時,都會返回一個Disposable實例,當調用Disposable的dispose,就相當于取消訂閱
- 在不需要再接收事件時,建議取消訂閱,釋放資源,有3種常見方式取消訂閱
創建Observer的兩種方式(video 6373s)
/—/
/– 課時20)函數式編程、面向協議編程 –/
函數式編程(Funtional Programming)
- 函數式編程(Funtional Programming,簡稱FP)是一種編程范式,也就是如何編寫程序的方法論
- 主要思想:把計算過程盡量分解成一系列可復用函數的調用
- 主要特征:函數是“第一等公民”
- 函數與其他數據類型一樣的地位,可以賦值給其他變量,也可以作為函數參數、函數返回值
- 函數式編程最早出現在LISP語言,絕大部分的現代編程語言也對函數式編程做了不同程度的支持,比如
- Haskell、JavaScript、Python、Swift、Kotlin、Scala等
- 函數式編程中幾個常用的概念
- Higher-Order Function、Function Currying
- Functor、Applicative Functor、Monad
- 參考資料
- https://adit.io/posts/2013-04-17-functors,_appclatives,_and_monads_in_pictures.html
- http://www.mokacoding.com/blog/functor-applicative-monads-in-pictures
FP實踐 - 傳統寫法
// 假設要實現一下功能: [(num + 3) * 5 - 1] % 10 / 2 let num = 1func add(_ v1: Int, _ v2: Int) -> Int { v1 + v2 } func sub(_ v1: Int, _ v2: Int) -> Int { v1 - v2 } func multiple(_ v1: Int, _ v2: Int) -> Int { v1 * v2 } func divide(_ v1: Int, _ v2: Int) -> Int { v1 / v2 } func mod(_ v1: Int, _ v2: Int) -> Int { v1 % v2 }divide(mod(sub(multiple(add(num, 3), 5), 1), 10), 2)FP實踐 - 函數式寫法
let num = 1 func add(_ v: Int) -> (Int) -> Int { { $0 + v } } func sub(_ v: Int) -> (Int) -> Int { { $0 - v } } func multiple(_ v: Int) -> (Int) -> Int { { $0 * v } } func divide(_ v: Int) -> (Int) -> Int { { $0 / v } } func mod(_ v: Int) -> (Int) -> Int { { $0 % v } } // 函數合成 infix operator >>> : AdditionPrecedence func >>><A, B, C>(_ f1: @escaping (A) -> B,_ f2: @escaping (B) -> C) -> (A) -> C { { f2(f1($0)) } } var fn = add(3) >>> multiple(5) >>> sub(1) >>> mod(10) >>> divide(2) fn(num)高階函數(Higher-Oder Function)
- 高階函數是至少滿足下列一個條件的函數
- 接受一個或多個函數作為輸入(mpa、filter、reduce等)
- 返回一個函數
- FP中到處都是高階函數
柯里化(Currying)
- 什么是柯里化
- 將一個接受多參數的函數變換為一系列只接受單個參數的函數
- Array、Optional的map方法接收的參數就是一個柯里化函數
- 例子,將已有函數柯里化
- 例子1
- 例子2
提示:如果一個函數需要接收4個以上的參數,就需要思考函數的是否存在設計上的Bug,并思考如何減少參數
函子(Functor)
- 像Array、Optional這樣支持map運算的類型,稱為函子(Functor)
適用函子(Applicative Functor)
- 對任意一個函子 F , 如果能支持以下運算,該函子就是一個適用函子
- Optional可以成為適用函子
- Array可以成為適用函子
單子(Monad)
- 對任意一個類型 F,如果能支持以下運算,那么就可以稱為是一個單子(Monad)
- 很顯然,Array、Optional都是單子
面向協議編程
- 面向協議編程(Protocol Oriented Programming,簡稱POP)
- 是Swift的一種編程范式,Apple于2015年WWDC提出
- 在Swift標準庫中,能見到大量POP的影子
- 同時,Swift也是一門面向對象的編程語言(Object Oriented Programming,簡稱OOP)
- 在Swift開發中,OOP和POP是相輔相成的,任何一方并不能取代另一方
- POP能彌補OOP一些設計上的不足
回顧OOP
- OOP的三大特性:封裝、繼承、多態
- 當多個類(比如A、B、C類)具有很多共性時,可以將這些共性抽取到一個父類中(比如D類),最后A、B、C類繼承D類
OOP的不足
- 但有些問題,使用OOP并不能很好解決,比如
- 如何將BVC(繼承UIViewController)、DVC(UITableViewController)的公共方法run抽取出來?
- 基于OOP想到的一些解決方案
- 1、將run方法放到另一個對象A中,然后BVC、DVC擁有對象A屬性
- 多來一些額外的依賴關系
- 2、將run方法添加到UIViewController分類中
- UIViewController會越來越臃腫,而且會影響它的其他所有子類
- 3、將run方法抽取到新的父類,采用多集成?(C++支持多繼承)
- 會增加程序設計復雜度,產生菱形繼承等問題,需要開發者額外解決
- 1、將run方法放到另一個對象A中,然后BVC、DVC擁有對象A屬性
POP的解決方案
- 首先添加protocol,然后給protocol擴展一個方法實現,最后讓其使用了該方法的對象添加這個擴展即可
POP的注意點
- 優先考慮創建協議,而不是父類(基類)
- 優先考慮值類型(struct、enum),而不是引用類型(class)
- 巧用協議的擴展功能
- 不要為了面向協議而使用協議
/—/
/– 課時19)從OC到Swift、函數式編程 –/
課前補充:
- 0 Substring
- Substring 發生修改 或者 轉為String時,會重新分配新的內存存儲字符串數據
- 1 String 無法橋接轉換成 NSMutableString
- 但是,Swift可以通過 NSMutableString(string: “string content”) 創建一個NSMutableString對象
- 2 class Car: NSObject的內存結構
- 內存結構是isa指針和對象中的屬性
只能被class繼承的協議
protocol Runnable1: AnyObject {} protocol Runnable2: class {} @objc protocol Runnable3 {} // 以上協議只能被類去遵守,結構體和枚舉都無法遵守- 被 @objc 修飾的協議,還可以暴露給OC去遵守實現
可選協議
- 可以通過 @objc 定義可選協議,這種協議只能被class遵守
dynamic
- 被 @objc dynamic 修飾的內容會具有動態性,比如調用方法會走runtime那一套流程
KVC\KVO
- Swift支持KVC\KVO的條件
- 屬性所在的類、監聽器最終繼承自NSObject
- 用 @objc dynamic 修飾對應的屬性
block方式的KVO
- 可以使用Swift的閉包監聽內容的修改
關聯對象(Associated Object)
- 在Swift中,class依然可以使用關聯對象
- 默認情況,extension不可以增加存儲屬性
- 借助關聯對象,可以實現類似extension為class增加存儲屬性的效果
資源名管理
- 有一種做法實際上是參考了Android的資源名管理方式
資源名管理還有其他思路
- 更多優秀的思路參考
- https://github.com/mac-cain13/R.swift
- https://github.com/SwiftGen/SwiftGen
多線程開發 - 異步
class demo {public typealias Task = () -> Voidpublic static func async(_ task: @escaping Task) {_async(task)}public static func async(_ task: @escaping Task,_ mainTask: @escaping Task) {_async(task, mainTask)}private static func _async(_ task: @escaping Task,_ mainTask: Task? = nil) {let item = DispatchWorkItem(block: task)DispatchQueue.global().async(execute: item)if let main = mainTask {item.notify(queue: DispatchQueue.main, execute: main)}} }小提示:使用Swift開發時,當加上NS前綴無法得到對象的時候,可以嘗試將NS前綴去掉,就可以得到之前在OC使用過的類
多線程開發 - 延遲
@discardableResult public static func delay(_ seconds: Double,_ block: @escaping Task) -> DispatchWorkItem {let item = DispatchWorkItem(block: block)DispatchQueue.main.asyncAfter(deadline: DispatchTime.now(),execute: item)return item }多線程開發 - 異步延遲
private static func _asyncDelay(_ seconds: Double,_ task: @escaping Task,_ mainTask: Task? = nil) -> DispatchWorkItem {let item = DispatchWorkItem(block: task)DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + seconds,execute: item)if let main = mainTask {item.notify(queue: DispatchQueue.main, execute: main)}return item // 返回的item可以調用item.cancel()取消延遲執行的任務 }多線程開發 - once
- dispatch_once在Swift中已被廢棄,取而代之
- 可以用 類型屬性 或者 全局變量\常量
- 默認自帶 lazy + dispatch_once 效果
多線程開發 - 加鎖
- gcd信號量(使用DispatchSemaphore加鎖)
- Foundation(使用NSRecursiveLock加鎖)
思考:在Swift中,什么時候使用結構體,什么時候使用類?
函數式編程
Array的常見操作
lazy的優化
- array.lazy.map 可以使其元素用到時才去調用閉包函數
Optional的map和flatMap
- 對于可選類型使用map或flatMap,它是一個整體
- flatMap發現如果本身就是可選類型就不會再包裝一次。
if a>0 && b>0
等價于
if a>0, b>0
/—/
/– 課時18)從OC到Swift –/
課前回顧
- .catch { error } : error是默認自帶的
- #warning(“undo”)
- 編碼習慣
iOS程序的入口
- 在AppDelegate上面默認有個@UIApplicationMain標記,這表示
- 編譯器自動生成入口代碼(main函數代碼),自動設置AppDelegate為APP的代理
- 也可以刪掉@UIApplicationMain,自定義入口代碼:新建一個main.swift文件(文件名只能是main.swift)
Swift調用OC
- 新建1個橋接頭文件,文件名格式默認為:{targetName}-Bridging-Header.h(targetName一般為項目名稱)
- 在{targetName}-Bridging-Header.h 文件中 #import OC需要暴露給Swift的內容
- 如果C語言暴露給Swift的函數名跟Swift中的其他函數名沖突了
- 可以在Swift中使用 @_silgen_name 修改C函數名
OC調用Swift
- Xcode已經默認生成一個用于OC調用Swift的頭文件,文件名格式是:{targetName}-Swift.h
- Swift暴露給OC的類最終繼承自NSObject
- 使用 @objc 修飾需要暴露給OC的成員
- 使用 @objcMembers 修飾類
- 代表默認所有成員都會暴露給OC(包括擴展中定義的成員)
- 最終是否成功暴露,還需要考慮成員自身的訪問級別
- Xcode會根據Swift代碼生成對應的OC聲明,寫入 {targetName}-Swift.h 文件
- 可以通過 @objc 重命名Swift暴露給OC的符號名(類名、屬性名、函數名等)
選擇器(Selector)
- Swift中依然可以使用選擇器,使用 #selector(name) 定義一個選擇器
- 必須是被 @objcMembers 或 @objc 修飾的方法才可以定義選擇器
補充:
- 純Swift是沒有runtime的
- runtime只存在在OC中
- dynamic關鍵字修飾的函數,是使用runtime機制去執行的
問題:
- 1、為什么Swift暴露給OC的類最終要繼承自NSObject?
- 因為OC需要執行一些runtime的函數得到isa指針,而只有繼承自NSObject才能夠執行runtime函數
- 2、(p as Person).run()底層是怎么調用的?反之,OC調用Swift底層有時如何調用?
- 純OC類暴露給Swift使用,其底層還是使用runtime機制
- OC調用Swift底層也是使用runtime機制
- 3、在Swift文件中,暴露給OC使用的類,依然在Swift中使用并執行 car.run() 底層是怎么調用的?
- 其底層依然是用Swift的虛表機制
String
- Swift的字符串類型String,跟OC的NSString,在API設計上還是有較大差異
- String的插入和刪除方法
Substring
- String可以通過下標、prefix、suffix等截取子串、子串類型不是String,而是Substring
- Substring和它的base,共享字符串數據
- Substring轉為String時,會重新分配新的內存存儲字符串數據
String 與 Character
for c in "jack" { // c是Character類型print(c) }var str = "jack" // c是Character類型 var c = str[str.startIndex]String相關的協議
- BidirectionalCollection 協議包含的部分內容
- startIndex、endIndex屬性、index方法
- String、Array 都遵守了這個協議
- RangeReplaceableCollection 協議包含的部分內容
- append、insert、remove方法
- String、Array都遵守了這個協議
- Dictionary、Set 也有實現上述協議中聲明的一些方法,只是并沒有遵守上述協議
多行String
let str = """ 1"2" 3'4' """String 與 NSString
- String 與 NSString 之間可以隨時隨地橋接轉換
- 如果你覺得String的API過于復雜難用,可以考慮將String轉為NSString
- String 不能 橋接轉換成 NSMutableString。反之,NSMutableString可以橋接轉換成String,因為NSMutableString繼承自NSString
- 比較字符串內容是否等價
- String使用 == 運算符
- NSString使用isEqual方法,也可以使用 == 運算符(本質還是調用了isEqual方法)
Swift、OC橋接轉換表:
- String <-> NSString
- String <- NSMutableString
- Array <-> NSArray
- Array <- NSMutableArray
- Dictionary <-> NSDictionary
- Dictionary <- NSMutableDictionary
- Set <-> NSSet
- Set <- NSMutableSet
/—/
/– 課時17)字面量協議、模式匹配、條件編譯 –/
字面量(Literal)
var age = 10 var isRed = false var name = "Jack"- 上面代碼中的10、false、"Jack"就是字面量
- 常見字面量的默認類型
- public typealias IntegerLiteralType = Int
- public typealias FloatLiteralType = Double
- public typealias BooleanLiteralType = Bool
- public typealias StringLiteralType = String
- Swift自帶的絕大部分類型,都支持直接通過字面量進行初始化
- Bool、Int、Float、Double、String、Array、Dictionary、Set、Optional等
字面量協議
- Swift自帶類型之所以能夠通過字面量初始化,是因為它們遵守了對應的協議
- Bool:ExpressibleByBooleanLiteral
- Int:ExpressibleByIntegerLiteral
- Float、Double:ExpressibleByIntegerLiteral、ExpressibleByFloatLiteral
- Dictionary:ExpressibleByDictionaryLiteral
- String:ExpressibleByStringLiteral
- Array、Set:ExpressibleByArrayLiteral
- Optional:ExpressibleByNilLiteral
字面量協議應用
extension Int : ExpressibleByBooleanLiteral {public init(booleanLiteral value: Bool) {self = value ? 1 : 0} } var num: Int = true print(num) // 1- 有點類似于C++中的轉換構造函數
模式(Pattern)
- 什么是模式?
- 模式是用于匹配規則,比如switch的case、捕捉錯誤的catch、if\guard\while\for語句的條件等
Swift中的模式有
-
通配符模式(wildcard Pattern)
- _ 匹配任何值
- _? 匹配非nil值
-
標示符模式(Identifier Pattern)
- 給對應的變量、常量名賦值
-
值綁定模式(Value-Binding Pattern)
-
元組模式(Tuple Pattern)
-
枚舉Case模式(Enumeration Case Pattern)
- if case語句等價于只有1個case的switch語句
- for case語句
-
可選模式(Optional Pattern)
-
類型轉換模式(Type-Casting Pattern)
-
表達式模式(Expression Pattern)
- 表達式模式用在case中
自定義表達式模式
- 可以通過重載運算符,自定義表達式模式的匹配規則
where
- 可以使用where為模式匹配增加匹配條件
從OC到Swift
MARK、TODO、FIXME
- // MARK:類似于OC中的 #pragma mark
- // MARK: - 類似于OC中的 #pragma mark -
- // TODO:用于標記未完成的任務
- // FIXME:用于標記待修復的問題
條件編譯
系統版本檢測
if #available(iOS 10, macOS 10.12, *) {// 對于iOS平臺,只在iOS10及以上版本執行// 對于macOS平臺,只在macOS 10.12及以上版本執行// 最后的*表示在其他所有平臺都執行 }API可用性說明
@available(iOS 10, macOS 10.15, *) class Person {}/—/
/– 課時16)內存訪問沖突、指針 –/
局部作用域
- 可以使用 do 實現局部作用域
逃逸閉包的注意點
- 逃逸閉包不可以捕獲inout參數
內存訪問沖突(Conflicting Access to Memory)
- 內存訪問沖突會在兩個訪問滿足下列條件時發送:
- 至少一個是寫入操作
- 它們訪問的是同一塊內存
- 它們的訪問時間重疊(比如在同一個函數內)
- 如果下面的條件可以滿足,就說明重疊訪問結構體的屬性是安全的
- 你只訪問實例存儲屬性,不是計算屬性或者類屬性
- 結構體是局部變量而非全局變量
- 結構體要么沒有被閉包捕獲,要么只被非逃逸閉包捕獲
指針
- Swift中也有專門的指針類型,這些都被定性為“Unsafe”(不安全的),常見的有以下4種類型
- UnsafePointer 類似于 const Pointee *
- UnsafeMutablePointer 類似于 Pointee *
- UnsafeRawPointer 類似于 const void *
- UnsafeMutableRawPointer 類似于 void *
獲得某個變量的指針
獲得指向某個變量的指針
獲得指向堆空間實例的指針
創建指針
(以上四個知識點內容省略,詳情請查看視頻)
指針之間的轉換
var ptr = UnsafeMutableRawPointer.allocate(buteCount: 16, alignment: 1)ptr.assumingMemoryBound(to: Int.self).pointer = 11 (ptr + 8).assumingMemoryBound(to: Double.self).pointee = 22.0print(unsafeBitCast(ptr, to: UnsafePointer<Int>.self).pointee) print(unsafeBitCast(ptr + 8, to: UnsafePointer<Double>.self).pointee)ptr.deallocate()- unsafeBitCast是忽略數據類型的強制轉換,不會因為數據類型的變化而改變原來的內存數據
- 類似于C++中的reinterpret_cast
/—/
/– 課時15)訪問控制、內存管理 –/
CustomStringConvertible
- 遵守CustomStringConvertible、CustomDebugStringConvertible協議,都可以自定義實例的打印字符串
- print調用的是CustomStringConvertible協議的description
- debugPrin、po調用的是CustomDebugStringConvertible協議的debugDescription
Self
- Self代表當前類型
assert(斷言)
- 很多編程語言都有斷言機制:不符合指定條件就拋出運行時錯誤,常用于調試(Debug)階段的條件判斷
- 默認情況下,Swift的斷言智慧在Debug模式下生效,Release模式下回忽略
- 增加Swift Flags修改斷言的默認行為
- -assert-config Release :強制關閉斷言
- -assert-config Debug :強制開啟斷言
fatalError
- 如果遇到嚴重問題,希望結束程序運行時,可以直接使用fatalError函數拋出錯誤(這是無法通過do-catch捕捉的錯誤)
- 使用fatalError函數,就不需要再寫return
- 在某些不得不實現、但不希望別人調用的方法,可以考慮內部使用fatalError函數
訪問控制(Access Control)
- 在訪問權限控制這塊,Swift提供了5個不同的訪問級別(以下是從高到低排列,實體指被訪問級別修飾的內容)
- open:允許在定義實體的模塊、其他模塊中訪問,允許其他模塊進行繼承、重寫(open只能用在類、類成員上)
- public:允許在定義實體的模塊、其他模塊中訪問,不允許其他模塊進行繼承、重寫
- internal:只允許在定義實體的模塊中訪問,不允許在其他模塊中訪問
- fileprivate:只允許在定義實體的源文件中訪問
- private:只允許在定義實體的封閉聲明中訪問
- (總結:Swift的訪問是以文件、模塊為訪問單位的)
- 絕大部分實體默認都是internal級別
訪問級別的使用準則
- 一個實體不可以被更低訪問級別的實體定義,比如
- 變量\常量類型 ≥ 變量\常量
- 參數類型、返回值類型 ≥ 函數
- 父類 ≥ 子類
- 父協議 ≥ 子協議
- 原類型 ≥ typealias
- 原始值類型、關聯值類型 ≥ 枚舉類型
- 定義類型A時用到的其他類型 ≥ 類型A
- ……
元組類型
- 元組類型的訪問級別是所有成員類型最低的那個
泛型類型
- 泛型類型的訪問級別是類型的訪問級別以及所有泛型類型參數的訪問級別中最低的那個
成員、嵌套類型
- 類型的訪問級別會影響成員(屬性、方法、初始化器、下標)、嵌套類型的默認訪問級別
- 一般情況下,類型為private或fileprivate,那么成員\嵌套類型默認也是private或fileprivate
- 一般情況下,類型為internal或public,那么成員\嵌套類型默認是internal
- 子類重寫的成員訪問級別必須 ≥ 父類的成員訪問級別
- 直接在全局作用域下定義的private等價于fileprivate
getter、setter
- getter、setter默認自動接收它們所屬環境的訪問級別
- 可以給setter單獨設置一個比getter更低的訪問級別,用以限制寫的權限
初始化器
- 如果一個public類想在另一個模塊調用編譯生成的默認無參初始化器,必須顯式提供public的無參初始化器
- 因為public類的默認初始化器是internal級別
- required初始化器 ≥ 它的默認訪問級別
- 如果結構體有private\fileprivate的存儲實例屬性,那么它的成員初始化器也是private\fileprivate
- 否則默認就是internal
枚舉類型的case
- 不能給enum的每個case單獨設置訪問級別
- 每個case自動接收enum的訪問級別
- public enum定義的case也是public
協議
- 協議中定義的要求自動接收協議的訪問級別,不能單獨設置訪問級別
- public協議定義的要求也是public
- 協議實現的訪問級別必須 ≥ 類型的訪問級別,或者 ≥ 協議的訪問級別
擴展
- 如果有顯式設置擴展的訪問級別,擴展添加的成員自動接收擴展的訪問級別
- 如果沒有顯式設置擴展的訪問級別,擴展添加的成員的默認訪問級別,跟直接在類型中定義的成員一樣
- 可以單獨給擴展添加的成員設置訪問級別
- 不能給用于遵守協議的擴展顯式設置擴展的訪問級別
- 在用一文件中的擴展,可以寫成類似多個部分的類型聲明
- 在原本的聲明中的聲明一個私有成員,可以在同一文件的擴展中訪問它
- 在擴展中聲明一個私有成員,可以在同一文件的其他擴展中、原本聲明中訪問它
將方法賦值給var\let
- 方法也可以像函數那樣,賦值給一個let或者var
內存管理
- 跟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)
weak、unowned的使用限制
- weak、unowned只能用在類實例上面
Autoreleasepool
public func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result // 閉包調用 autoreleasepool {let p = MJPerson(age:20, name: "Jack")p.run() }循環引用(Reference Cycle)
- weak、unowned都能解決循環引用的問題,unowned要比weak少一些性能消耗
- 在生命周期中可能會變為nil的使用weak
- 初始化賦值后再也不會變為nil的使用unowned
閉包的循環引用
- 閉包表達式默認會對用到的外層對象產生額外的強引用(對外層對象進行了retain操作)
- 下面代碼會產生循環引用,導致Person對象無法釋放(看不到Person的deinit被調用)
- 在閉包表達式的捕獲列表聲明weak或unowned引用,解決循環引用問題
- 如果想在定義閉包屬性的同時引用self,這個閉包必須是lazy的(因為在實例初始化完畢之后才能引用self)
- 左邊的閉包fn內部如果用到了實例成員(屬性、方法)
- 編譯器會強制要求明確寫出self
- 如果lazy屬性是閉包調用的結果,那么不用考慮循環引用的問題(因為閉包調用后,閉包的生命周期就結束了)
@escaping
- 非逃逸閉包、逃逸閉包,一般都是當做參數傳遞給函數
- 非逃逸閉包:閉包調用發生在函數結束前,閉包調用在函數作用域內
- 逃逸閉包:閉包有可能在函數結束后調用,閉包調用逃離了函數的作用域,需要通過@escaping聲明
(課后研究:什么是Swift捕獲列表?Swift的DispatchQueue詳情?)
/—/
/– 課時14)可選項的本質、運算符重載、擴展 –/
可選項的本質
- 可選項的本質是enum類型
溢出運算符(Overflow Operator)
- Swift的算數運算符出現溢出時會拋出運行時錯誤
- Swift有溢出運算符(&+、&-、&*),用來支持溢出運算
運算符重載(Operator Overload)
- 類、結構體、枚舉可以為現有的運算符提供自定義的實現,這個操作叫做:運算符重載
Equatable
- 要想得知2個實例是否等價,一般做法是遵守Equatable協議,重載 == 運算符
- 與此同時, 等價于重載了 != 運算符
- Swift為以下類型提供默認Equatable實現
- 沒有關聯類型的枚舉
- 只擁有遵守Equatable協議關聯類型的枚舉
- 只擁有遵守Equatable協議存儲屬性的結構體
- 引用類型比較存儲的地址值是否相等(是否引用著同一個對象),使用恒等運算符===、!==
Comparable
- 要想比較2個實例的大小,一般做法是:
- 遵守Comparable協議
- 重載相應的運算符
自定義運算符(Custom Operator)
- 可以自定義新的運算符:在全局作用域使用operator進行聲明
擴展(Extension)
- Swift中的擴展,有點類似于OC中的分類(Category)
- 擴展可以為枚舉、結構體、類、協議添加新功能
- 可以添加方法、計算屬性、下標、(便捷)初始化器、嵌套類型、協議等等
- 擴展不能辦到的事情
- 不能覆蓋原有的功能
- 不能添加存儲屬性,不能向已有的屬性添加屬性觀察器
- 不能添加父類
- 不能添加指定初始化器,不能添加反初始化器
- ……
初始化器
- 如果希望自定義初始化器的同時,編譯器也能夠生成默認初始化器
- 可以在擴展中編寫自定義初始化器
- 類遵守協議實現的required初始化器,也不能寫在擴展中
協議
- 如果一個類型已經實現了協議的所有要求,但是還沒有聲明它遵守了這個協議
- 可以通過擴展來讓它遵守這個協議
- 編譯一個函數,判斷一個整數是否為奇數?
- 擴展可以給協議提供默認實現,也間接實現「可選協議」的效果
- 擴展可以給協議擴充「協議中從未聲明過的方法」
泛型
class Stack<E> {var elements = [E]() }- 擴展中依然可以使用原類型中的泛型類型
- 符合條件才擴展
/—/
/– 課時13)匯編分析String、Array底層 –/
關于String的思考
- 1個String變量占用多少內存?
- 下面2個String變量,底層存儲有什么不同?
- 內存地址從低到高分別是,代碼區->常量區->全局區(數據段)->堆空間->棧空間->動態庫
- 如果對String進行拼接操作,String變量的存儲會發生什么變化?
Mach-O文件是Apple下的可執行文件
- 從編碼到啟動APP
- OC、Swift源碼通過編譯鏈接成Mach-O可以執行文件,然后啟動該文件。
dyld_stub_binder
- 符號的延遲綁定通過dyld_stub_binder完成
- jmpq *0xb31(%rip)格式的匯編指令
- 占用6個字節
關于Array的思考
- 1個Array變量占用多少內存?
- 8個字節,并且該變量是一個地址值
- 數組中的數據存放在哪里?
- 堆空間中
/—/
/– 課時12)Error處理、泛型 –/
補充上節課知識
- deinit
- Person和Person.self
創建對象的方式
* var p0 = Person() * var p1 = Person.init() * var p2 = type(of: p0).init() * var p3 = Person.self() * var p4 = Person.self.init()錯誤類型
- 開發過程常見的錯誤
- 語法錯誤(編譯報錯)
- 邏輯錯誤
- 運行時錯誤(可能會導致閃退,一般也叫做異常)
自定義錯誤
- Swift中可以通過Error協議自定義運行時的錯誤信息
- 函數內部通過throw拋出自定義Error,可能會拋出Error的函數必須加上throws聲明
- 需要使用try調用可能會拋出Error的函數
do-catch
- 可以使用do-catch捕捉Error
- 拋出Error后,try下一句直到作用域結束的代碼都將停止運行
處理Error
- 處理Error的2種方式
- ① 通過do-catch捕捉Error
- ② 不捕捉Error,在當前函數新增throws聲明,Error將自動拋給上層函數
- 如果最頂層函數(main函數)依然沒有捕捉Error,那么程序將終止
try?、try!
- 可以使用try?、try!調用可能會拋出Error的函數,這樣就不用去處理Error
- a、b是等價的
rethrows
- rethrows表明:函數本身不會拋出錯誤,但調用閉包參數拋出錯誤,那么它會將錯誤向上拋
defer
- defer語句:用來定義以任何方式(拋錯誤、return等)離開代碼塊前必須要執行的代碼
- defer語句將延遲至當前作用域結束之前執行
- defer語句的執行順序與定義順序相反(也就是,先定義的后執行,后定義的先執行)
泛型(Generics)
- 泛型可以將類型參數化,提高代碼復用率,減少代碼量
- 泛型函數賦值給變量
關聯類型(Associated Type)
- 關聯類型的作用:給協議中用到的類型定義一個占位名稱
- 協議中可以擁有多個關聯類型
- 協議中只能用關聯類型,因為泛型參數是用在類、結構體、枚舉、函數上的。
- 可以使用 typealias Element = String 給關聯類型設定真實類型;也可以不指定,編譯器會自動識別器關聯類型
類型約束
protocol Runnable { } class Person { } func swapValues<T : Person & Runnable>(_ a: inout T, _ b: intou T) {(a, b) = (b, a) } protocol Stackable {associatedtype Element: Equatable } class Stack<E : Equatable> : Stackable { }協議類型的注意點
- 如果協議中有associatedtype
泛型解決
- 解決方案①:使用泛型
不透明類型(Opaque Type)
- 解決方案②:使用some關鍵字聲明一個不透明類型
- some限制只能返回一種類型
some
- some除了用在返回值類型上,一般還可以用在屬性類型上
/—/
/– 課時11)init、deinit、可選鏈、協議、元類型 –/
required
- 用required修飾指定初始化器,表明其所有子類都必須實現該初始化器(通過繼承或者重寫實現)(這里有一種情況是子類未寫任何初始化器也不報錯,原因是因為子類無任何初始化器時,會觸發自動繼承機制)
- 如果子類重寫了required初始化器,也必須加上required,不用加override
屬性觀察器
- 父類的屬性在它自己的初始化器中賦值不會觸發屬性觀察器,但在子類的初始化器中賦值會觸發屬性觀察器
可失敗初始化器
- 類、結構體、枚舉都可以使用 init? 定義可失敗初始化器
- 不允許同時定義參數標簽、參數個數、參數類型相同的可失敗初始化器和非可失敗初始化器
- 可以用 init! 定義隱式解包的可失敗初始化器
- 可失敗初始化器可以調用非可失敗初始化器,非可失敗初始化器調用可失敗初始化器需要進行解包
- 如果初始化器調用一個可失敗初始化器導致初始化失敗,那么整個初始化過程都失敗,并且之后的代碼都停止執行
- 可以用一個非可失敗初始化器重寫一個可失敗初始化器,但反過來是不行的
反初始化器(deinit)
- deinit叫做反初始化器,類似于C++的析構函數、OC中的dealloc方法
- 當類的實例對象被釋放內存時,就會調用實例對象的deinit方法
- deinit不接受任何參數,不能寫小括號,不能自行調用
- 父類的deinit能被子類繼承
- 子類的deinit實現執行完畢后會調用父類的deinit
可選鏈(Optional Chaining)
- 如果可選項為nil,調用方法、下標、屬性失敗,結果為nil
- 如果可選項不為nil,調用方法、下標、屬性成功,結果會被包裝成可選項
- 如果結果本來就是可選項,不會進行再次包裝
- 多個 ? 可以鏈接在一起
- 如果鏈中任何一個節點是nil,那么整個鏈就會調用失敗
協議(Protocol)
- 協議可以用來定義方法、屬性、下標的聲明,協議可以被枚舉、結構體、類遵守(多個協議之間用逗號隔開)
- 協議中定義方法時不能有默認參數值
- 默認情況下,協議中定義的內容必須全部都實現
- 也有辦法辦到只實現部分內容,以后的課程會講到
協議中的屬性
- 協議中定義屬性時必須用var關鍵字
- 實現協議時的屬性權限要不小于協議中定義的屬性權限
- 協議定義get、set,用var存儲屬性或get、set計算屬性去實現
- 協議定義get,用任何屬性都可以實現
static、class
- 為了保證通用,協議中必須用static定義類型方法、類型屬性、類型下標
mutating
- 只有將協議中的實例方法標記為mutating
- 才允許結構體、枚舉的具體實現修改自身內存
- 類在實現方法時不用加mutating,枚舉、結構體才需要加mutating
init
- 協議中還可以定義初始化器init
- 非final類實現時必須加上required
- 如果從協議實現的初始化器,剛好是重寫了父類的指定初始化器
- 那么這個初始化必須同時加required、override
init、init?、init!
- 協議中定義的init?、init!,可以用init、init?、init!去實現
- 協議中定義的init,可以用init、init!去實現
協議的繼承
- 一個協議可以繼承其他協議
協議組合
protocol Livable { } protocol Runnable { } class Person { } // 接收Person或者其子類的實例 func fn0(obj: Person) { } // 接收遵守Livable協議的實例 func fn1(obj: Livable) { } // 接收同時遵守Livable、Runnable協議的實例 func fn2(obj: Livable & Runnable) { } // 接收同時遵守Livable、Runnable協議、并且是Person或者子類的實例 func fn3(obj: Person & Livable & Runnable) { } // 另一種,接收同時遵守Livable、Runnable協議、并且是Person或者子類的實例 typealias RealPerson = Person & Livable & Runnable func fn4(obj: RealPerson) { }- 協議組合,可以包含1個類類型(最多1個)
CaseIterable
- 讓枚舉遵守CaseIterable協議,可以實現遍歷枚舉值
CustomStringConvertible
- 遵守CustomStringConvertible協議,可以自定義實例的打印字符串
Any、AnyObject
- Swift提供了2種特殊的類型:Any、AnyObject
- Any:可以代表任意類型(枚舉、結構體、類,也包括函數類型)
- AnyObject:可以代表任意類類型(在協議后面寫上:AnyObject代表只有類能遵守這個協議)
is、as?、as!、as
- is用來判斷是否為某種類型,as用來做強制類型轉換
X.self、X.Type、AnyClass
- X.self是一個元類型(metadata)的指針,metadata存放著類型相關信息(跟對象在堆空間存放的前8個字節是一樣的)
- X.self屬于X.Type類型
元類型的應用
- 從結果可以看得出來,Swift還有個隱藏的基類:Swift._SwiftObject
Self
- Self一般用作返回值類型,限定返回值跟方法調用者必須是同一類型(也可以作為參數類型)
- 如果Self用在類中,要求返回時調用的初始化器是required的
/—/
/– 課時10)匯編分析多態原理、init –/
Swift中多態的實現原理:類似于C++的虛表(虛函數表)
對象的前16個字節中,前8個是類型信息,后8個是引用技術
初始化器
- 類、結構體、枚舉都可以定義初始化器
- 類有2種初始化器:指定初始化器(designated initializer)、便捷初始化器(convenience initializer)
- 每個類至少有一個指定初始化器,指定初始化器是類的主要初始化器
- 默認初始化器總是類的指定初始化器
- 類偏向于少量指定初始化器,一個類通常只有一個指定初始化器
- 初始化器的相互調用規則
- 指定初始化器必須從它的直系父類調用指定初始化器
- 便捷初始化器必須從相同的類里調用另一個初始化器
- 便捷初始化器最終必須調用一個指定初始化器
初始化器的相互調用
- 這一套規則保證了
- 使用任意初始化器,都可以完整地初始化實例
兩段式初始化
- Swift在編碼安全方面是煞費苦心,為了保證初始化過程的安全,設定了兩段式初始化、安全檢查
- 兩段式初始化
- 第1階段:初始化所有存儲屬性
- 外層調用指定\便捷初始化器
- 分配內存給實例,但未初始化
- 指定初始化器確保當前類定義的存儲屬性都初始化
- 指定初始化器調用父類的指定初始化器,不斷向上調用,形成初始化器鏈
- 第2階段:設置新的存儲屬性值
- 從頂部初始化器往下,鏈中的每一個指定初始化器都有機會進一步定制實例
- 初始化器現在能夠使用self(訪問、修改它的屬性,調用它的實例方法等等)
- 最終,鏈中任何便捷初始化器都有機會定制實例以及使用self
- 第1階段:初始化所有存儲屬性
安全檢查
- 指定初始化器必須保證在調用父類初始化器之前,其所在類定義的所有存儲屬性都要初始化完成
- 指定初始化器必須先調用父類指定初始化器,然后才能為繼承的屬性設置新值
- 便捷初始化器必須先調用同類中的其他初始化器,然后再為任意屬性設置新值
- 初始化器在第1階段初始化完成之前,不能調用任何實例方法、不能讀取任何實例屬性的值,也不能引用self
- 直到第1階段結束,實例才算完全合法
重寫初始化器
- 當重寫父類的指定初始化器時,必須加上override(即使子類的實現是便捷初始化器)
- 如果子類寫了一個匹配父類便捷初始化器的初始化器,不用加上override
- 因為父類的便捷初始化器永遠不會通過子類直接調用,因此,嚴格來說,子類無法重寫父類的便捷初始化器
自動繼承
- ① 如果子類沒有自定義任何指定初始化器,它會自動繼承父類所有的指定初始化器
- ② 如果子類提供了父類所有指定初始化器的實現(要么通過方式①繼承,要么重寫)
- 子類自動繼承所有的父類便捷初始化器
- ③ 就算子類添加了更多的便捷初始化器,這些規則仍然適用
- ④ 子類以便捷初始化器的形式重寫父類的指定初始化器,也可以作為滿足規則②的一部分
/—/
/– 課時9)匯編分析類型屬性、方法、下標、繼承 –/
方法(Method)
枚舉、結構體、類都可以定義實例方法、類型方法
- 實例方法(Instance Method):通過實例調用
- 類型方法(Type Method):通過類型調用,用 static 或者 class 關鍵字定義
self - 在實例方法中代表實例
- 在類型方法中代表類型
mutating
結構體和枚舉是值類型,默認情況下,值類型的屬性不能被自身的實例方法修改
- 在func關鍵字前加mutating可以允許這種修改行為
@discardableResult
在func前面加個@discardableResult,可以消除:函數調用后返回值未被使用的警告
下標(subscript)
使用subscript可以給任意類型(枚舉、結構體、類)增加下標功能,有些地方也翻譯為:下標腳本
- subscript的語法類似于實例方法、計算屬性、本質就是方法(函數)
- subscript中定義的返回值類型決定了
- get方法的返回值類型
- set方法中newValue的類型
- subscript可以接受多個參數,并且類型任意
下標的細節
- subscript可以沒有set方法,但必須要有get方法
- 如果只有get方法,可以省略get
- 可以設置參數標簽(例子:p[index: 1])
- 下標可以是類型方法
了解下:
結構體、類作為返回值對比;
接收多個參數的下標;
繼承(Inheritance)
值類型(枚舉、結構體)不支持繼承,只有類支持繼承
沒有父類的類,稱為:基類
- Swift并沒有像OC、Java那樣的規定:任何類最終都要繼承子某個基類
- 子類可以重寫父類的下標、方法、屬性,重寫必須加上override關鍵字
內存結構
重寫實例方法、下標
重寫類型方法、下標
- 被class修飾的類型方法、下標,允許被子類重寫
- 被static修飾的類型方法、下標、不允許被子類重寫
重寫屬性
- 子類可以將父類的屬性(存儲、計算)重寫為計算屬性
- 子類不可以將父類屬性重寫為存儲屬性
- 只能重寫var屬性,不能重寫let屬性
- 重寫時,屬性名、類型要一致
- 子類重寫后的屬性權限不能小于父類屬性的權限
- 如果父類屬性是只讀的,那么子類重寫后的屬性可以是只讀的、也可以是可讀寫的
- 如果父類屬性是可讀寫的,那么子類重寫后的屬性也必須是可讀寫的
重寫實例屬性
- 使用屬性需要加上super,不然會導致無限遞歸
重寫類型屬性
- 被class修飾的計算類型屬性,可以被子類重寫
- 被static修飾的類型屬性(存儲、計算),不可以被子類重寫
屬性觀察器
- 可以在子類中為父類屬性(除了只讀計算屬性、let屬性)增加屬性觀察器
final
- 被final修飾的方法、下標、屬性,禁止被重寫
- 被final修飾的類,禁止被繼承
/—/
/– 課時8)屬性、匯編分析inout本質 –/
屬性
Swift中跟實例相關的屬性可以分為2大類
- 存儲屬性(Stored Property)
- 類似于成員變量這個概念
- 存儲在實例的內存中
- 結構體、類可以定義存儲屬性
- 枚舉不可以定義存儲屬性
- 計算屬性(Computed Property)
- 本質就是方法(函數)
- 不占用實例的內存
- 枚舉、結構體、類都可以定義計算屬性
注意
- 存儲屬性(關于存儲熟悉,Swift有個明確的規定)
- 在創建 類 或 結構體的實例時,必須為所有的存儲屬性設置一個合適的初始值
- 可以在初始化器里(init函數)為存儲屬性設置一個初始值
- 可以分配一個默認的屬性值作為屬性定義的一部分
- 在創建 類 或 結構體的實例時,必須為所有的存儲屬性設置一個合適的初始值
- 計算屬性
- set傳入的新值默認叫做newValue,也可以自定義
- 只讀計算屬性:只有get,沒有set
- 定義計算屬性只能用var,不能用let
- 計算屬性的值是可能發生變化的(即使是只讀計算屬性)
枚舉rawValue原理
枚舉原始值rawValue的本質是:只讀計算屬性
延遲存儲屬性(Lazy Stored Property)
使用lazy可以定義一個延遲存儲屬性,在第一次用到屬性的時候才會進行初始化
- lazy屬性必須是var,不能是let
- let必須在實例的初始化方法完成之前就擁有值
- 如果多條線程同時第一次訪問lazy屬性
- 無法保證屬性只被初始化1次
延遲存儲屬性注意點
- 當結構體包含一個延遲存儲屬性時,只有var才能訪問延遲存儲屬性
- 因為延遲屬性初始化時需要改變結構體的內存
屬性觀察器(Property Observer)
可以為非lazy的var存儲屬性設置屬性觀察器
willSet會傳遞新值,默認叫newValue
didSet會傳遞舊值,默認叫oldValue
在初始化器中設置屬性值不會觸發willSet和didSet
全局變量、局部變量
屬性觀察器、計算屬性的功能,同樣可以應用在全局變量、局部變量身上
inout的再次研究
輸入輸出的傳遞就是引用傳遞
inout的本質總結
- 如果實參有物理內存地址,且沒有設置屬性觀察器
- 直接將實參的內存地址傳入函數(實參進行引用傳遞)
- 如果實參是計算屬性 或者 設置了屬性觀察器
- 采取了Copy In Copy Out的做法
- 調用該函數時,先復制實參的值,產生副本【get】
- 將副本的內存地址傳入函數(副本進行引用傳遞),在函數內部可以修改副本的值
- 函數返回后,再將副本的值覆蓋實參的值【set】
- 采取了Copy In Copy Out的做法
- 總結:inout的本質就是引用傳遞(地址傳遞)
類型屬性(Type Property)
- 嚴格來說,屬性可以分為
- 實例屬性(Instance Property):只能通過實例去訪問
- 存儲實例屬性(Stored Instance Property):存儲在實例的內存中,每個實例都有1份
- 計算實例屬性(Computed Instance Property)
- 類型屬性(Type Property):只能通過類型去訪問
- 存儲類型屬性(Stored Type Property):整個程序運行過程中,就只有1份內存(類似于全局變量)
- 計算類型屬性(Computed Type Property)
- 實例屬性(Instance Property):只能通過實例去訪問
- 可通過static定義類型屬性
- 如果是類,也可以用關鍵字class
類型屬性細節
- 不同于存儲實例屬性,你必須給存儲類型屬性設定初始值
- 因為類型沒有像實例那樣的init初始化器來初始化存儲屬性
- 存儲類型屬性默認就是lazy,會在第一次使用的時候才初始化
- 就算被多個線程同時訪問,保證只會初始化一次
- 存儲類型屬性可以是let
- 枚舉類型也可以定義類型屬性(存儲類型屬性、計算類型屬性)
單例模式
public class FileManager {
public static let shared = {
// …
// …
return FileManager()
}
Private init() { }
}
/—/
/– 課時7)匯編分析閉包本質02 –/
注意:
如果返回值是函數類型,那么參數的修飾要保持統一
自動閉包
注意:在函數調用中,如果需傳遞兩個參數,其中參數一條件不滿足才使用參數二,那么可以將參數二定義為函數的方式,這樣如果未使用參數二就不會被調用。
|- @autoclosure 會自動將20封裝成閉包 { 20 }
|- @autoclosure 只支持 () -> T 格式的參數
|- @autoclosure 并非只支持最后一個參數
|- 空合運算符 ?? 使用了 @autoclosure 技術
|- 有@autoclosure、無@autoclosure,構成了函數重載
|- 為了避免與期望沖突,使用@autoclosure的地方最好明確注釋清楚:這個值會被推遲執行
/—/
/– 課時6)匯編分析閉包本質01 –/
引用類型的賦值操作
值類型、引用類型的let
值類型定義let后不能修改成員值
引用類型定義let可以修改成員值
引用類型
引用賦值給var、let或者給函數傳參,是將內存地址拷貝一份
類似于制作一個文件的替身(快捷方式、鏈接),指向的是同一個文件。屬于淺拷貝(shallow copy)
嵌套類型
枚舉、結構體、類都可以定義方法
一般把定義在枚舉、結構體、類內部的函數,叫做方法
方法不占用對象的內存
方法的本質就是函數
方法、函數都存放在代碼段
閉包表達式(Closure Expression)(類似于OC的block)
在Swift中,可以通過func定義一個函數,也可以通過閉包表達式定義一個函數。
{
(參數列表) -> 返回值類型 in
函數體代碼
}
閉包表達式的簡寫
例子,閉包表達式的簡化
let descendingArray2 = cityArray.sorted(by: {s1,s2 in return s1 > s2})
let descendingArray3 = cityArray.sorted(by: {s1,s2 in s1 > s2})
let descendingArray4 = cityArray.sorted(by: {$0 > $1})
let descendingArray5 = cityArray.sorted(by: > )
尾隨閉包
如果將一個很長的閉包表達式作為函數的最后一個實參,使用尾隨閉包可以增強函數的可讀性。
|- 尾隨閉包是一個被書寫在函數調用括號外面(后面)的閉包表達式
如果閉包表達式是函數的唯一實參,而且使用了尾隨閉包的語法,那就不需要在函數名后邊寫圓括號
示例 - 數組的排序
忽略參數
閉包(Closure)
一個函數和它所捕獲的變量\常量環境組合起來,稱為閉包
|- 一般指定義在函數內部的函數
|- 一般她捕獲的是外層函數的局部變量\常量
可以把閉包想象成是一個類的實例對象
|- 內存在堆空間
|- 捕獲的局部變量\常量就是對象的成員(存儲屬性)
|- 組成閉包的函數就是類內部定義的方法
/—/
/– 課時5)匯編分析結構體、類的內存布局 –/
結構體
在Swift標準庫中,絕大多數的公開類型都是結構體,而枚舉和類只占很小一部分
比如Bool、Int、Double、String、Array、Dictionary等常見類型都是結構體
struct Date{
var year: Int
var month: Int
var day: Int
}
var date = Date(year:2019, month:6, day:23)
所有的結構體都有一個編譯器自動生成的初始化器(initializer,初始化方法、構造器、構造方法)
在上述代碼最后一行,可以傳入所有成員值,用以初始化所有成員變量(存儲屬性,Stored Property)
結構體的初始化器
編譯器會根據情況,可能會為結構體生成多個初始化器,宗旨是:保證所有成員都有初始化值
自定義初始化器
一旦在定義結構體時自定義了初始化器,編譯器就不會再幫它自動生成其他初始化器
窺探初始化器的本質
自己寫初始化值,系統會自動生成init函數
類
類的定義和結構體類似,但編譯器并沒有為類自動生成可以傳入成員值的初始化器
類的初始化器
如果類的所有成員都在定義的時候指定了初始值,編譯器會為類生成無參的初始化器
成員的初始化是在當前初始化器中完成的
結構體與類的本質區別
結構體是值類型(枚舉也是值類型),類是引用類型(指針類型)
值類型
值類型賦值給var、let或者給函數傳參,是直接將所有內容拷貝一份
類似于對文件進行copy、paste操作,產生了全新的文件副本。屬于深拷貝(deep copy)
Swift底層用C++寫的,所以底層跟C++很像
規律
內存地址格式為:0x4bdc(%rip),一般是全局變量,全局區(數據段)。
內存地址格式為:-0x78(%rbp),一般是局部變量,棧空間。
內存地址格式為:0x10(%rax),一般是堆空間。
值類型的復制操作(字符串、數組都是struct)
在Swift標準庫中,為了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技術
(比如僅當有“寫”操作時,才會真正執行拷貝操作)
(對于標準庫值類型的賦值操作,Swift能確保最佳性能,所以沒必要為了保證最佳性能來避免賦值)
建議:不需要修改的,盡量定義成let
引用類型
引用賦值給var、let或者給函數傳參,是將內存地址拷貝一份
(類似于制作一個文件的替身(快捷方式、鏈接),指向的是同一個文件。屬于淺拷貝(shallow copy))
/—/
/– 課時3)枚舉、可選項 –/
枚舉的基本用法
關聯值(Associated Value)
有時會將枚舉的成員值跟其他類型的關聯存儲在一起,會非常有用。
enum Score {
case points(Int)
case grade(Character)
}
原始值(Raw Values)
枚舉成員可以使用相同類型的默認值預先關聯,這個默認值叫做:原始值
enum PokerSuit: Character {
case spade = “??”
case heart = “??”
case diamond = “F”
case club = “??”
}
隱式原始值(Implicitly Assigned Raw Values)
如果枚舉的原始值類型是Int、String,Swift會自動分配原始值
遞歸枚舉(Recursive Enumeration)
indirect enum ArithExpr {
case number(Int)
case sum(ArithExpr, ArithExpr)
case difference(ArithExpr, ArithExpr)
}
MemoryLayout
可以使用MemoryLayout獲取數據類型占用的內存大小
可選項(Optional)
一般也叫可選類型,它允許將值設置為nil
在類型名稱后面加個問號(?)來定義一個可選類型
強制解包(Forced Unwrapping)
可選項是對其他類型的一層包裝,可以將它理解為一個盒子
如果為nil,那么它是個空盒子
如果不為nil,那么盒子里裝的是:被包裝類型的數據
如果要從可選項中取出被包裝的數據(將盒子里裝的東西取出來),需要使用感嘆號 !進行強制解包。
如果對值為nil的可選項(空盒子)進行強制解包,將會產生運行時錯誤。
判斷可選項是否包含值
let number = Int(“k123”)
number != nil
可選項綁定(Optional Binding)
可以使用可選項綁定來判斷可選項是否包含值
如果包含就自動解包,把值賦值給一個臨時的常量(let)或者變量(var),并返回true,否則返回false
if let number = Int(“123”) {
}else {}
等階寫法
可選項綁定需要使用逗號分割判斷
While循環中使用可選項綁定
空合運算符?? (Nil-Coalescing Operator)
let username = loginName ?? “Guest”
loginName 表示為可選性,如果loginName 為空,則使用默認名稱 Guest
a ?? b
a 是可選項
b 是可選項 或者 不是可選項
b 跟a 的存儲類型必須相同
如果a 不為nil,就返回a
如果a 為nil,就返回b
如果b 不是可選項,返回a 時會自動解包
多個 ?? 一起使用
?? 跟 if let 配合使用
let a: Int? = nil
let b: Int? = 2
if let c = a ?? b {
print?
}// 類似于 if a!=nil || b!=nil
if let c = a, let d = b {
print?
print(d)
}// 類似于 if a!=nil && b!=nil
guard語句
guard 條件 else {
退出當前作用域
}
當guard語句的條件為false時,就會執行大括號里面的代碼
當guard語句的條件為true時,就會跳過guard語句
Guard語句特別適合用來”提前退出“
當使用guard語句進行可選項綁定時,綁定的常量(let)、變量(var)也能在外層作用域中使用
隱式解包(Implicitly Unwrapped Optional)
在某些情況下,可選項一旦被設定值之后,就會一直擁有值
在這種情況下,可以去掉檢查,也不必每次訪問的時候都進行解包,因為它能確定每次訪問的時候都有值。
可以在類型后面加個感嘆號 !定義一個隱式解包的可選項。
字符串插值
可選項在字符串插插值或者直接打印,編譯器會發出警告
多重可選項
var num1: Int? = 10
var num2: Int?? = num1
var num3: Int?? = 10
可以使用lldb指令 frame variable -R 或者 fr v -R 查看區別
關于感嘆號( ! ),若在對象后面加感嘆號則表示其所對應的變量或常量一定有值。
/—/
/– 課時2)流程控制、函數、匯編分析內聯優化 –/
流程控制
if-else
if后面的條件可以省略小括號,但條件后面的大括號不可以省略。
if后面的條件只能是Bool類型
while 和 repeat-while(相當于C語言中的do-while)
從Swift3開始,去除了自增(++)、自減(–)運算符
for
閉區間運算符:a…b,a <= 取值 <= b
例子:
for i in 0…3 {
print(i)
} // 0 1 2 3
半開區間運算符: a…<b,a <= 取值 < b
例子:
for i in 1…<5 {
print(i)
} // 1 2 3 4
for - 區間運算符用在數組上
let names = [“Anna”, “Alex”, “Brian”, “Jack”]
for name in names[0…3] {
print(name)
} // Anna Alex Brian Jack
單側區間:讓區間朝一個方向盡可能的遠
names[2…] 或者 names[…2] 或者 names[…<2]
switch
在其case、default后面不能寫大括號{}
默認可以不寫break,并不會貫穿到后面的條件
但是,使用fallthrough可以實現貫穿效果
switch必須要保證能處理所有情況
case、default后面至少要有一條語句
如果不想做任何事,加個break即可
如果能保證已處理所有情況,也可以不必使用default
復合條件,switch也支持Character、String類型
區間匹配、元組匹配
值綁定、where、標簽語句(需要在嵌套的for循環里面控制外面的循環加個標簽即可)
函數的定義:
func sum(v1: Int, v2: Int) -> Int {
return v1 + v2
}
調用:sum(v1: 10, v2: 20)
形參默認是let,也只能是let
無返回值的三種寫法:
第一種
func sayHello() -> Void {
print(“Hello”)
}
第二種
func sayHello() -> () { // ()可以理解為是元組
print(“Hello”)
}
第三種
func sayHello() {
print(“Hello”)
}
隱式返回:
如果整個函數體是一個單一表達式,那么函數會隱式返回這個表達式
func sum(v1: Int, v2: Int) -> Int {
v1 + v2
}
返回元組:實現多返回值
func calculate(v1: Int, v2: Int) -> (sum: Int, difference: Int, average: Int) {
let sum = v1 + v2
return (sum, v1 - v2, sum >> 1)
}
let result = calculate(v1: 20, V2: 10)
result.sum // 30
result.difference // 10
result.average // 15
函數的文檔注釋(好麻煩的,做個了解就好)
參數標簽
可以修改參數標簽
func goToWork(at time: String) {
print(“this time is (time)”)
}
goToWork(at: “08:00”)
// this time is 08:00
可以使用下劃線_ 省略參數標簽
func sum(_ v1: Int, _ v2: Int) -> Int {
v1 + v2
}
sum(10, 20)
默認參數值(Default Parameter Value)
func check(name: String = “nobody”, age: Int, job: String = “none”) {
print(“name=(name), age=(age), job=(job)”)
}
check(name: “Jack”, age: 20, job: “Doctor”)
check(name: “Rose”, age: 18)
check(age: 10, job: “Batman”)
check(age: 15)
C++的默認參數值有個限制:必須從右往左設置。由于Swift擁有參數標簽,因此并沒有此類限制。
但是在省略參數標簽時,需要特別注意,避免出錯。
可變參數(Variadic Parameter)
func sum(_ numbers: Int…) -> Int {
var total = 0
for number in numbers {
total += number
}
return total
}
sum(10, 20, 30, 40) // 100
一個函數最多只能有1個可變參數
緊跟在可變參數后面的參數不能省略參數標簽
Swift自帶的print函數
輸入輸出參數(In-Out Parameter)
可以用inout定義一個輸入輸出參數:可以在函數內部修改外部實參的值
可變參數不能標記為inout
inout參數不能有默認值
inout參數的本質是地址傳遞(引用傳遞)
inout參數只能傳入可以被多次賦值的
函數重載(Function Overload)
規則:
函數相同
參數個數不同 || 參數類型不同 || 參數標簽不同
注意點:
返回值類型與函數重載無關
默認參數值和函數重載一起使用產生二義性時,編譯器并不會報錯(在C++中會報錯)
可變參數、省略參數標簽、函數重載一起使用產生二義性時,編譯器有可能會報錯
內聯函數(Inline Function)
如果開啟了編譯器優化(Release模式默認會開啟優化),編譯器會自動將某些函數編程內聯函數
其作用是將函數調用展開成函數體
哪些函數不會被內聯?
函數體比較長
包含遞歸調用
包含動態派發
函數類型(Function Type)
每一個函數都是有類型的,函數類型由形式參數類型、返回值類型組成
函數類型作為函數參數
函數類型作為函數返回值
返回值是函數類型的函數,叫做高階函數(Higher-Order Function)
typealias(用來給類型起別名)
typealias Byte = Int8
typealias Short = Int16
typealias Long = Int64
嵌套函數(Nested Function)
將函數定義在函數內部
func forward(_ forward:Bool) -> (Int) -> Int {
func next(_ input: Int) -> Int {
input + 1
}
func previous(_ input: Int) -> Int {
input - 1
}
return forward ? next : previous
}
forward(true)(3)
forward(false)(3)
/—/
/– 課時1)基礎語法、匯編初探 –/
swiftc是Swift的編譯指令;
swiftc存放在Xcode內部;
一些操作
+生成語法樹: swiftc -dump-ast main.swift
+生成最簡潔的SIL代碼: swiftc -emit-sil main.swift
+生成LLVM IR代碼: swiftc -emit-ir main.swift -0 main.ll
*生成匯編代碼: swiftc -emit-assembly main.swift -o main.s
能對匯編代碼進行分析,可以真正掌握編程語言的本質。
支持單行、多行、嵌套等注釋方式
還支持markup(與markdown相似)(注釋后面緊跟一個冒號開始markup)
開始markup渲染效果:Editor -> Show Rendered Markup
注意:Markup只在Playground中有效
常量
只能賦值1次
它的值不要求在編譯時期確定,但使用之前必須賦值1次
常量可以在一開始不賦值,但是需要在一開始時定義其類型
標識符
標識符(比如常量名、變量名、函數名)幾乎可以使用任何字符
標識符不能以數字開頭,不能包含空白字符、制表支付、箭頭等特殊字符
常見數據類型
值類型(value type)
|-- {枚舉(enum),Optional}
|-- {結構體(struct)Bool、Int、Float、Double、Character;
String、Array、Dictionary、Set}
引用類型(reference type)
|-- {類(class}
整數
let intDecimal = 17 // 十進制
let intBinary = 0b10001 // 二進制
let intOctal = 0o21 // 八進制
Let intHexadecimal = 0x11 // 十六進制
/—/
/– 目錄 –/
目錄:從入門到精通Swift編程
├─01-基礎語法、匯編初探
│ └─1-1-【回放】基礎語法、匯編初探.mp4
│
├─02-流程控制、函數、匯編分析內聯優化
│ └─2-1-【回放】流程控制、函數、匯編分析內聯優化.mp4
│
├─03-枚舉、可選項
│ └─3-1-【回放】枚舉、可選項.mp4
│
├─04-匯編分析枚舉的內存布局
│ └─4-1-【回放】匯編分析枚舉的內存布局.mp4
│
├─05-匯編分析結構體、類的內存布局
│ └─5-1-【回放】匯編分析結構體、類的內存布局.mp4
│
├─06-匯編分析閉包本質01
│ └─6-1-【回放】匯編分析閉包本質01.mp4
│
├─07-匯編分析閉包本質02
│ └─7-1-【回放】匯編分析閉包本質02 .mp4
│
├─08-屬性、匯編分析inout本質
│ └─8-1-【回放】屬性、匯編分析inout本質 .mp4
│
├─09-匯編分析類型屬性、方法、下標、繼承
│ └─9-1-【回放】匯編分析類型屬性、方法、下標、繼承 .mp4
│
├─10-匯編分析多態原理、init
│ └─10-1-【回放】匯編分析多態原理、初始化、可選鏈 .mp4
│
├─11-init、deinit、可選鏈、協議、元類型
│ └─11-01-【回放】init、deinit、可選鏈、協議、元類型 .mp4
│
├─12-Error處理、泛型
│ └─12-01-【回放】Error處理、泛型 .mp4
│
├─13-匯編分析String、Array底層
│ └─13-01-【回放】匯編分析String、Array底層 .mp4
│
├─14-可選項的本質、運算符重載、擴展
│ └─14-01-【回放】可選項的本質、運算符重載、擴展 .mp4
│
├─15-訪問控制、內存管理
│ └─15-01-【回放】訪問控制、內存管理.mp4
│
├─16-內存訪問沖突、指針
│ └─16-01-【回放】內存訪問沖突、指針 .mp4
│
├─17-字面量協議、模式匹配、條件編譯
│ └─17-01-【回放】字面量協議、模式匹配、條件編譯 .mp4
│
├─18-從OC到Swift
│ └─18-01-【回放】從OC到Swift.mp4
│
├─19-從OC到Swift、函數式編程
│ └─19-01-【回放】從OC到Swift、函數式編程 .mp4│
│
├─20- 函數式編程、面向協議編程
│ └─20-01-【回放】 函數式編程、面向協議編程 .mp4
│
├─21-面向協議編程、響應式編程
│ └─21-01-【回放】面向協議編程、響應式編程.mp4
│
├─22-標準庫源碼分析、項目實戰
│ └─22-01-【回放】標準庫源碼分析、項目實戰.mp4
│
└─22. 標準庫源碼分析、項目實戰
└─22. 標準庫源碼分析、項目實戰_batch.mp4
/—/
總結
- 上一篇: IR-61|1895075-34-9|七
- 下一篇: window10关闭磁盘bitlocke