導(dǎo)語
歷時5年發(fā)展, 從swift1.x發(fā)展到了swift5.x版本, 經(jīng)歷了多次重大改變, ABI終于穩(wěn)定.
API(Application Programming Interface): 應(yīng)用程序編程接口
源代碼和庫之間的接口
ABI(Application Binary Interface): 應(yīng)用程序二進制接口
應(yīng)用程序與操作系統(tǒng)之間的底層接口
設(shè)計的內(nèi)容有: 目標文件格式(maco格式)挖垛、數(shù)據(jù)類型大小/布局/對齊,函數(shù)調(diào)用約定等等
隨著ABI的穩(wěn)定, swift語法基本不會再有太大的變動, 此時正在學(xué)習(xí)swift的最佳時刻
編譯器分前端和后端:
1.前端: 語法分析...
2.后端:生成對應(yīng)平臺的二進制代碼
編譯流程:
OC
c --> (編譯前端)clang --> (編譯后端)LLVM IR 通過 LLVM compiler --->x86/ARM/other
Swift
Swift --> (編譯前端) swiftc --> (編譯后端)LLVM IR 通過 LLVM compiler --->x86/ARM/other
Swift編譯大概流程:
Swift Code(swift代碼) --> Swift AST(根據(jù)swiftc生成語法樹) --> Raw Swift IL(swift特有中間代碼) --> Canonical Swift IL(swift特有中間代碼更簡潔) --> LLVM IR(編譯后端生成LLVM中間代碼) --> Assembly(匯編代碼) --> Executable(二進制代碼)
swiftc
swiftc存放在Xcode內(nèi)部
Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
一些操作
1.生成.swift文件的語法樹
命令行輸入:cd (.swift所在文件目錄)
swiftc -dump-ast main.swift
2.生成最簡潔的SIL代碼: swiftc -emit-sil main.swift
3.生成LLVM IR代碼: swiftc -emit-ir main.swift -o main.ll
4.生成匯編代碼: swiftc -emit-assembly main.swift -o main.s
對匯編代碼進行分析, 可以真正掌握編程語言本質(zhì)
斷點后,轉(zhuǎn)匯編代碼查看: Debug >> Debug Workflow >> AlwaysShow Dissassembly(永遠顯示反匯編,就是把你平時寫的代碼轉(zhuǎn)換成匯編代碼)
第一章 基本運算锯岖、流程控制匀借、函數(shù)**
1. Hello World
1.不用編寫main函數(shù), Swift將全局范圍內(nèi)的首句可執(zhí)行代碼作為程序入口
2.一句代碼尾部可以省略分號(;), 多句代碼寫到同一行時必須用分號(;)隔開
3.用var定義變量, let定義常量, 編譯器能自動推斷出變量/常量的類型
4.Playground可以快速預(yù)覽代碼效果,是學(xué)習(xí)語法的好幫手
快捷鍵:
Command + Shift + Enter: 運行整個Playground
var a = 10
print("Hello Wrold! - \(a)")//拼接字符串
2. Playground
快捷鍵:
Command + 1 顯示左測文件欄
Command + 0 隱藏左側(cè)文件欄
Command + N 新建代碼文件page頁
import UIKit
import PlaygroundSupport
//使用Playground展示一個view
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
view.backgroundColor = UIColor.red
PlaygroundPage.current.liveView = view
//使用Playground展示一個圖片
let imageView = UIImageView(image: UIImage(name: "logo"))
PlaygroundPage.current.liveView = imageView
//使用Playground展示一個視圖控制器
let vc = UITableViewController()
vc.view.backgroundColor = UIColor.lightGray
PlaygroundPage.current.liveView = vc
//Playground--多Page
File >> New >> Playground Page
3. 注釋
0. Swift支持注釋嵌套
// 單行注釋
/*
多行注釋
*/
/*
1
// 多行注釋嵌套單行注釋
/* 多行注釋嵌套多行注釋 */
2
*/
1.Playground的注釋支持markup語法(與markdown類似) //: 或 /*:*/
單行markup:
編寫markup語法 //: # 一級標題
多行markup:
編寫markup語法
/*:
# 學(xué)習(xí)Swift
## 基礎(chǔ)語法
- 變量
- 常量
## 面向?qū)ο? - 類
- 屬性
*/
運行markup語法 Editor >> Show Rendered Markup
4. 常量
// 只能賦值1次
// 它的值不要求在編譯時期確定, 在運行時確定也可以, 但使用之前必須賦值1次
let age1 = 10
let age2: Int //這種寫法,必須說明類型
age2 = 20
func getAge() -> Int {
return 30
}
let age3 = getAge()//在運行時確認值
// 常量乘碑、變量在初始化之前, 都不能被使用, 否則會報錯
let age: Int
var height: Int
print(age)//報錯
print(height)//報錯
5. 標識符_數(shù)據(jù)類型
// 標識符(比如常量名熊镣、變量名、函數(shù)名)幾乎可以使用任何字符
// 標識符不能以數(shù)字開頭, 不能包含空白字符拓售、制表符窥摄、箭頭等特殊字符
常見的數(shù)據(jù)類型:整數(shù)類型: Int8、Int16础淤、Int32崭放、Int64哨苛、UInt8、UInt16币砂、UInt32建峭、UInt64
在32bit平臺, Int等價于Int32, 在64bit平臺, Int等價于Int64
整數(shù)的最值: UInt8.max、Int16.min
一般情況下, 都是直接使用Int即可
浮點類型:Float, 32位, 精度只有6位; Double, 64位, 精度至少15位.
6. 字面量
// 布爾
let bool = true // 取反是false
// 字符串
let string = "小哥哥"
// 字符 (可存儲ASCII字符决摧、Unicode字符)
let character: Character = "??"
// 整數(shù)
let intDecimal = 17 //十進制
let intBinary = 0b10001 //二進制
let intOctal = 0o21 // 八進制
let intHexadecimal = 0x11 //十六進制
// 浮點數(shù)
let doubleDecimal = 125.0 //十進制,等價于1.25e2, 0.0125等價于1.25e-2
let doubleHexadecimal1 = 0xFp2//十六進制, 意味著15x(2^2), 相當(dāng)于十進制的60.0
let doubleHexadecimal2 = 0xFp-2//十六進制,意味著15x(2^-2),相當(dāng)于十進制的3.75
//以下都是表示12.1875
//十進制:12.1875亿蒸、1.21875e1
//十六進制: 0xC.3p0
// 整數(shù)和浮點數(shù)可以添加額外的零或者添加下劃線來增強可讀性
比如: 100_0000、1_000_000.000_000_1蜜徽、000123.456
// 數(shù)組
let array = [1,3,5,7,9]
// 字典
let dictionary = ["age" : 18, "height" : 165]
7. 類型轉(zhuǎn)換
// 整數(shù)轉(zhuǎn)換
let int1: UInt16 = 2_000
let int2: UInt8 = 1
let int3 = int1 + UInt16(int2)
// 整數(shù)、浮點數(shù)轉(zhuǎn)換
let int = 3
let double = 0.14159
let pi = Double(int) + double
let intPi = Int(pi)
// 字面量可以直接相加, 因為數(shù)字字面量本身沒有明確的類型
let result = 3 + 0.14159
8. 元組(tuple)
let http404Error = (404, "Not Found") //將多種類型組合在一起, 賦值給元祖對象
print("The status code is \(http404Error.0)")
let(statusCode, statusMessage) = http404Error //接收元祖
print("The status code is \(statusCode)")
let(justTheStatusCode, _) = http404Error //不想接收的值,用_占位
let http200Status = (statusCode: 200, description: "OK")
print("The status code is \(http200Status.statusCode)")
第二章 if-else和while票摇、區(qū)間拘鞋、Switch、函數(shù)
1矢门、if-else 和 while
// 1.if-else
/*
if后面的條件可以省略小括號;
條件后面的大括號不可以省略;
if后面的條件只能是Bool類型;
*/
// 2.while
/*
repeat-while相當(dāng)于C語言中的do-while;
這里不用num--,是因為從Swift3開始,去除了自增(++)盆色、自減(--)運算符
*/
var num = 5
while num > 0{
print("num is \(num)")
num -= 1
}//打印了5次
var num = -1
repeat {
print("num is \(num)")
}while num > 0 //打印了1次
2-1、區(qū)間
// 閉區(qū)間運算符: a...b 其實就是 a <= 取值 <= b
let names = ["a","b","c","d"]
for i in 0...3 {
print(name[I])
}// a b c d
for name in names[0...3]{// for-區(qū)間運算符用在數(shù)組上
print(name)
}// a b c d
let range = 1...3
for i in range {
print(name[I])
}// b c d
let a = 1
let b = 2
for i in a...b{// 可以用變量來作為區(qū)間
print(name[I])
}// b c
for i in a...3{
print(name[I])
}// b c d
for var i in 1...3{// i 默認是let, 有需要時可以聲明為var
i += 5
print(i)
}//6 7 8
for _ in 1...3 {// 如果用不到i, 用_占位
print("for")
}//打印了3次
2-2祟剔、半開區(qū)間運算符: a..<b 其實就是 a <= 取值 < b
for i in 1..<5 {
print(i)
}//1 2 3 4
2-3隔躲、單側(cè)區(qū)間: 讓區(qū)間朝一個方向盡可能的遠
for name in names[2...] {
print(name)
}// c d
for name in names[...2] {
print(name)
}// a b c
for name in names[..<2] {
print(name)
}// a b
let range = ...5
range.contains(7)//false
range.contains(4)//true
range.contains(-3)//true
2-4、區(qū)間類型
let range1: ClosedRange<Int> = 1...3
let range2: Range<Int> = 1..<3
let range3: PartialRangeThrough<Int> = ...5
//字符物延、字符串也能使用區(qū)間運算符, 但默認不能用在for-in中
let stringRange1 = "cc"..."ff" //CloseRange<String>
stringRange1.contains("cb")//false
stringRange1.contains("dz")//true
stringRange1.contains("fg")//false
let stringRange2 = "a"..."f"
stringRange2.contains("d")//true
stringRange2.contains("h")//false
// \0到~囊括了所有可能要用到的ASCII字符
let characterRange: CloseRange<Character> = "\0"..."~"
characterRange.contains("G")//true
2-5宣旱、帶間隔的區(qū)間值
let hours = 11
let hourInterval = 2
// tickMark的取值: 從4開始, 累加2, 不超過11
for tickMark in stride(from: 4, through: hours, by: hourInterval){
print(tickMark)
}// 4 6 8 10
3、switch
/*
case叛薯、default后面不能寫大括號{} ;
默認可以不寫break, 并不會貫穿到后面的條件;
使用fallthrough可以實現(xiàn)貫穿效果;
switch必須要保證能處理所有情況, 否則會報錯;
case浑吟、default后面至少要有一條語句, 如果不想做任何事, 加個break即可;
*/
var number = 1
switch number {
case 1:
print("number is 1")
break
case 2:
print("number is 2")
break
default:
print("number is other")
break
}// number is 1
switch number {//默認可以不寫break, 并不會貫穿到后面的條件
case 1:
print("number is 1")
fallthrough//使用fallthrough可以實現(xiàn)貫穿效果
case 2:
print("number is 2")
default:
break//case、default后面至少要有一條語句;
}// number is 1 \n number is 2
/*
switch注意點:
如果能保證已處理所有情況, 也可以不必使用default;
*/
enum Answer {case right, wrong}
let answer = Answer.right
switch answer {
case Answer.right:
print("right")
case Answer.wrong:
print("wrong")
}
switch answer {//由于已確定answer是Answer類型, 因此可以省略Answer
case .right:
print("right")
case .wrong:
print("wrong")
}
3-1耗溜、復(fù)合條件
//switch也支持Character组力、String類型
let string = "Jack"
switch string {
case "Jack", "Rouse"://滿足其中一個條件就執(zhí)行
print("Right person")
default:
break
}//Right person
3-2、區(qū)間匹配抖拴、元組匹配
//可以使用下劃線_忽略某個值
//關(guān)于case匹配問題, 屬于模式匹配的范疇,以后會再次詳細展開講解
let count = 62
switch count {
case 0:
print("none")
case 1..<5: //區(qū)間匹配
print("a few")
case 5..<12:
print("several")
case 12..<100:
print("dozens of")
case 100..<1000:
print("hundreds of")
default:
print("many")
}// dozens of
let point = (1, 1)
switch point {
case (0, 0)://元組匹配
print("the origin")
case (_,0):
print("on the x-axis")
case (0,_):
print("on the y-axis")
case (-2...2, -2...2):
print("inside the box")
default:
print("outside of the box")
}// inside the box
3-3燎字、值綁定
let point = (2, 0)
switch point {
case(let x, 0):
print("on the x-axis with an x value of \(x)")
case(0, let y):
print("on the y-axis with an y value of \(y)")
case let(x, y):
print("somewhere else at (\(x), \(y))")
}// on the x-axis with an x value of 2
3-4、where--過濾不滿足條件的信息
let point = (1, -1)
switch point {
case let (x,y) where x==y: //值綁定后,加條件
print("on the line x==y")
case let (x,y) where x==-y:
print("on the line x==-y")
case let (x,y):
print("(\(x), \(y)) is just some arbitrary point")
}// on the line x == -y
//將所有正數(shù)加起來
var numbers = [10,20,-10,-20,30,-30]
var sum = 0
for num in numbers where num > 0 {//使用where來過濾num
sum += num
}
print(sum) //60
3-5阿宅、標簽語句
outer: for i in 1...4 {// 如果現(xiàn)在內(nèi)層嵌套的for循環(huán)里面寫continue和break想控制外面的for循環(huán),那么加上outer標簽就好了
for k in 1...4 {
if k==3 {
continue outer
}
if i==3 {
break outer
}
print("i == \(i), k == \(k)")
}
}
4候衍、函數(shù)
func pi() -> Double {//不帶參數(shù), 有返回值
return 3.14
}
func sum(v1: Int, v2: Int) -> Int {//帶參數(shù), 有返回值
return v1 + v2
}
sum(v1: 10,v2: 20)
// 形參默認是let, 也只能是let
func sayHello() -> Void {//寫法一:無返回值
print("Hello")
}
func sayHello() -> () {//寫法二:無返回值, 小括號代表空元組
print("Hello")
}
func sayHello() {//寫法三:無返回值
print("Hello")
}
4-1、隱式返回(Implicit Return)
//如果整個函數(shù)體是一個單一表達式,那么函數(shù)會隱式返回這個表達式
func sum(v1: Int, v2: Int) -> Int {
v1 + v2
}
sum(v1: 10, v2: 20)// 30
4-2洒放、返回元組: 實現(xiàn)多返回值
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
4-3脱柱、函數(shù)的文檔注釋
/// 求和[概述]
///
/// 將兩個整數(shù)相加[更詳細的描述]
///
/// - Parameter v1: 第1個整數(shù)
/// - Parameter v2: 第2個整數(shù)
/// - Returns: 2個整數(shù)的和
///
/// - Note:傳入兩個整數(shù)即可[批注]
///
func sum(v1: Int, v2: Int) -> Int {
v1 + v2
}
// option + command + / 快速注釋快捷鍵
// 參考: https://swift.org/documentation/api-design-guidelines/
4-4、參數(shù)標簽(Argument Label)
//可以修改參數(shù)標簽
func goToWork(at time:String) {//調(diào)用時at替代time作為參數(shù)名
print("this time is \(time)")
}
goToWork(at: "00:00")//this time is 00:00
//可以使用下劃線_ 省略參數(shù)標簽
func sum(_ v1: Int, _ v2: Int) -> Int {
v1 + v2
}
sum(10, 20)
4-5拉馋、默認參數(shù)值(Default Parameter Value)
//參數(shù)可以有默認值
func check(name: String = "nobody", age: Int, job: String = "none"){
print("name=\(name), age=\(age), job=\(job)")
}
check(name: "Jack")
check(age: 10)
//C++的默認參數(shù)值有個限制: 必須從右往左設(shè)置. 由于Swift擁有參數(shù)標簽, 因此并沒有此類限制
//但是在省略參數(shù)標簽時, 需要特別注意, 避免出錯
4-6榨为、可變參數(shù)
fuc sum(_ numbers:Int...) -> Int {//...代表可變參數(shù),意味著可以傳很多個Int參數(shù)
var total = 0
for number in numbers {//numbers暫且認定為數(shù)組
total += number
}
return total
}
sum(10, 20, 30, 40)//100
// 一個函數(shù)最多只能有1個可變參數(shù)
// 緊跟在可變參數(shù)后面的參數(shù)不能用省略參數(shù)標簽
func test(_ numbers: Int..., string: String, _ other String){}
test(10, 20, 30, string: "Jack", "Rose")
4-7惨好、輸入輸出參數(shù)(In-Out Parameter)
// 可以用inout定義一個輸入輸出參數(shù): 可以在函數(shù)內(nèi)部修改外部實參的值
func swapValues(_ v1: inout Int, _ v2: inout Int) {
let tmp = v1
v1 = v2
v2 = tmp
}
var num1 = 10
var num2 = 20
swapValues(&num1, &num2)
func swapValues(_ v1: inout Int, _ v2: inout Int) {//交換兩個外部變量的值
(v1, v2) = (v2, v1)//用元組進行交換
}
// 可變參數(shù)不能標記為inout
// inout參數(shù)不能有默認值
// 示例代碼中inout參數(shù)的本質(zhì)是地址傳遞(引用傳遞), 如果傳遞給inout參數(shù)的是計算屬性、有監(jiān)聽器的屬性等內(nèi)容,其本質(zhì)并非引用傳遞,在[屬性]章節(jié)再作詳細講解
// inout參數(shù)只能傳入可以被多次賦值的,也就是說調(diào)用時傳入v1和v2的,是可以被多次賦值的變量var
4-8随闺、函數(shù)重載(Function Overload)
//規(guī)則: 函數(shù)名相同; 參數(shù)個數(shù)不同||參數(shù)類型不同||參數(shù)標簽不同
func sum(v1: Int, v2: Int) -> Int {
v1 + v2
}
func sum(v1: Int, v2: Int, v3: Int) -> Int {
v1 + v2 + v3
}// 參數(shù)個數(shù)不同
func sum(v1: Double, v2: Int) -> Double {
v1 + Double(v2)
}// 參數(shù)類型不同
func sum(a: Int, b: Int) -> Int {
a + b
}//參數(shù)標簽不同
sum(v1: 10, v2: 20)//30
sum(v1: 10, v2: 20, v3: 30)//60
sum(v1: 10.0, v2: 20)//30.0
sum(a: 10, b: 20)//30
/*函數(shù)重載注意點:
1.返回值類型與函數(shù)重載無關(guān);
2.默認參數(shù)值和參數(shù)重載一起使用產(chǎn)生二義性時,編譯器并不會報錯(在C++中會報錯);
3.可變參數(shù)日川、省略參數(shù)標簽、函數(shù)重載一起使用產(chǎn)生二義性時,編譯器有可能會報錯
*/
4-9矩乐、內(nèi)聯(lián)函數(shù)(Inline Function)
//如果開啟了編譯器優(yōu)化(Release模式默認會開啟優(yōu)化), 編譯器會自動將某些函數(shù)變成內(nèi)聯(lián)函數(shù).
//開啟:Build Settings >> Swift Compiler - Code Generation >> Optimization Level >> Debug Optimize for Speed[-O]
//內(nèi)聯(lián)函數(shù)實質(zhì): 將函數(shù)調(diào)用展開成函數(shù)體
//哪些函數(shù)不會被內(nèi)聯(lián)? 1.函數(shù)體比較長的時候 2.包含遞歸調(diào)用 3.包含動態(tài)派發(fā)(動態(tài)綁定)
@inline(never) func test(){//永遠不會被內(nèi)聯(lián)(即使開啟了編譯器優(yōu)化)
print("test")
}
@inline(__always) func test() {//開啟編譯器優(yōu)化后, 即使代碼很長,也會被內(nèi)聯(lián)(遞歸調(diào)用很熟,動態(tài)派發(fā)的函數(shù)除外)
print("test")
}
//在Release模式下,編譯器已經(jīng)開啟優(yōu)化,會自動決定哪些函數(shù)需要內(nèi)聯(lián),因此沒必要使用@inline
4-10龄句、函數(shù)類型(Function Type)
//每一個函數(shù)都有類型的, 函數(shù)類型由形式參數(shù)類型、返回值類型組成
func test(){ }// () -> Void 或者 () -> ()
func sum(a:Int, b:Int) -> Int{
a + b
}// (Int, Int) -> Int
//定義變量
var fn: (Int, Int) -> Int = sum
fn(2, 3) //5 調(diào)用時不需要參數(shù)標簽
//函數(shù)類型作為函數(shù)參數(shù)
func sum(v1: Int, v2: Int) -> Int {
v1 + v2
}
func difference(v1: Int, v2: Int) -> Int {
v1 - v2
}
func printResult(_ mathFn: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result:\(mathFn(a, b))")
}
printResult(sum, 5, 2) // Result:7
printResult(difference, 5, 2) // Result:3
//函數(shù)類型作為函數(shù)返回值
func next(_ input: Int) -> Int {
input + 1
}
func previous(_ input: Int) -> Int {
input - 1
}
func forward(_ forward: Bool) -> (Int) -> Int {
forward? next : previous
}
forward(true)(3) //4
forward(false)(3)//2 //每一個函數(shù)都有類型的, 函數(shù)類型由形式參數(shù)類型散罕、返回值類型組成
func test(){ }// () -> Void 或者 () -> ()
func sum(a:Int, b:Int) -> Int{
a + b
}// (Int, Int) -> Int
//定義變量
var fn: (Int, Int) -> Int = sum
fn(2, 3) //5 調(diào)用時不需要參數(shù)標簽
//函數(shù)類型作為函數(shù)參數(shù)
func sum(v1: Int, v2: Int) -> Int {
v1 + v2
}
func difference(v1: Int, v2: Int) -> Int {
v1 - v2
}
func printResult(_ mathFn: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result:\(mathFn(a, b))")
}
printResult(sum, 5, 2) // Result:7
printResult(difference, 5, 2) // Result:3
//函數(shù)類型作為函數(shù)返回值
func next(_ input: Int) -> Int {
input + 1
}
func previous(_ input: Int) -> Int {
input - 1
}
func forward(_ forward: Bool) -> (Int) -> Int {
forward? next : previous
}
forward(true)(3) //4
forward(false)(3)//2
4-11分歇、typealias用來給類型起別名
typealias Byte = Int8
typealias Short = Int16
typealias Long = Int64
typealias Date = (year: Int, month:Int, day:Int)//用Date代替這個元組
func test(_ date: Date) {
print(date.0)
print(date.year)
}
test((2011, 9, 10))
typealias IntFn = (Int, Int) -> Int //用IntFn代替這種函數(shù)類型
func difference(v1: Int, v2: Int) -> Int {
v1 - v2
}
let fn: IntFn = difference
fn(20, 10)//10
func setFn(_ fn: IntFn){ }
setFn(difference)
func getFn() -> IntFn { difference }
//按照Swift標準庫的定義, Void就是空元組()
public typealias Void = ()
4-12、嵌套函數(shù)(Nested Function)
//將函數(shù)定義在函數(shù)內(nèi)部
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)//4
forward(false)(3)//2
第三章 枚舉
1-1欧漱、枚舉的基本用法
enum Direction {//定義方式一
case north
case south
case east
case west
}
enum Direction {//定義方式二
case north, south, east, west
}
var dir = Direction.west //使用方式一
dir = Direction.east
dir = .north
print(dir) // north
switch Direction {//使用方式二
case .north
print("north")
case .south
print("south")
case .east
print("east")
case .west
print("west")
}
1-2职抡、關(guān)聯(lián)值(Associated Values)
//有時會將枚舉的成員值跟其他類型的關(guān)聯(lián)存儲在一起,會非常有用
enum Score { //示例一
case points(Int)
case grade(Character)
}
var score = Score.points(96)
score = .grade("A")
switch score {
case let .points(i):
print(i,"points")
case let .grade(i):
print("grade", i)
}// grade A
enum Date {//示例二
case digit(year: Int, month: Int, day: Int)
case string(String)
}
var date = Date.digit(year: 2011 month: 9, day:10)
date = .string("2011-09-10")
switch date {
case .digit(let year, let month, let day):
print(year, month, day)
case let .string(value):
print(value)
}
1-3、原始值(Raw Values)
// 枚舉成員可以使用相同類型的默認值預(yù)先關(guān)聯(lián), 這個默認值叫做: 原始值
enum PokerSuit : Character {
case spade = "?"
case heart = "?"
case diamond = "?"
case club = "?"
}
var suit = PokerSuit.spade
print(suit) //spade
print(suit.rawValue)//?
print(PokerSuit.club.rawValue)// ?
enum Grade: String {//枚舉Grade關(guān)聯(lián)的原始值是String類型
case perfect = "A"
case great = "B"
case good = "C"
case bad = "D"
}
print(Grade.perfect.rawValue) // A
print(Grade.great.rawValue) // B
print(Grade.good.rawValue) // C
print(Grade.bad.rawValue) // D
1-4误甚、隱式原始值(Implicitly Assigned Raw Values)
//如果枚舉的原始值類型是Int缚甩、String, Swift會自動分配原始值
enum Direction : String {
case north = "north"
case south = "south"
case east = "east"
case west = "west"
}
enum Direction: String {//等價于
case north, south, east, west
}
print(Direction.north) //north
print(Direction.north.rawValue) //north
enum Season : Int { //自動遞增
case spring, summer, autumn, winter
}
print(Season.spring.rawValue) //0
print(Season.summer.rawValue) //1
print(Season.autumn.rawValue) //2
print(Season.winter.rawValue) //3
enum Season : Int { //自動遞增
case spring = 1, summer, autumn = 4, winter
}
print(Season.spring.rawValue) //1
print(Season.summer.rawValue) //2
print(Season.autumn.rawValue) //4
print(Season.winter.rawValue) //5
1-5、遞歸枚舉
//定義一個枚舉類型,枚舉的成員里面的關(guān)聯(lián)值也用到了這個枚舉類型,就是遞歸枚舉.必須加關(guān)鍵字indirect
indirect enum ArithExpr {//遞歸枚舉寫法一(多用)
case number(Int)
case sum(ArithExpr, ArithExpr)
case difference(ArithExpr, ArithExpr)
}
enum ArithExpr {//遞歸枚舉寫法二
case number(Int)
indirect case sum(ArithExpr, ArithExpr)
indirect case difference(ArithExpr, ArithExpr)
}
let five = ArithExpr.number(5)//獲取枚舉成員變量
let four = ArithExpr.number(4)
let two = ArithExpr.number(2)
let sum = ArithExpr.sum(five, four)
let difference = ArithExpr.difference(sum, two)
func calculate(_ expr: ArithExpr) -> Int {
switch expr {
case let .number(value):
return value
case let .sum(left, right):
return calculate(left) + calculate(right)
case let .difference(left, right):
return calculate(left) - calculate(right)
}
}
calculate(difference)//使用遞歸枚舉
2窑邦、MemoryLayout
可以使用MemoryLayout獲取數(shù)據(jù)類型所占用的內(nèi)存大小
var age = 10
//使用方法一: MemoryLayout支持泛型
MemoryLayout<Int>.size //占8個字節(jié)數(shù)(分配占用的空間大小)
MemoryLayout<Int>.stride //占8個字節(jié)(實際用到的空間大小)
MemoryLayout<Int>.alignment //(alignment:內(nèi)存對齊參數(shù)),占用8個字節(jié)
//使用方法二:
MemoryLayout.size(ofValue: age)
MemoryLayout.stride(ofValue: age)
MemoryLayout.alignment(ofValue: age)
// 可以使用MemoryLayout獲取數(shù)據(jù)類型占用的內(nèi)存大小
enum Password {
case number(Int, Int, Int, Int) //此處是關(guān)聯(lián)值,關(guān)聯(lián)值存儲在枚舉變量內(nèi)存中,所以在枚舉變量內(nèi)存中占用4(關(guān)聯(lián)值)*8(每個int類型占8個字節(jié))=32個字節(jié)
case other //只占用1個字節(jié)
}
var pwd = Password.number(5, 6, 4, 7) // 分配40個字節(jié), 實際占用33
pwd = .other // 分配40個字節(jié), 實際占用33
MemoryLayout<Password>.stride // 40 (分配占用的空間大小)
MemoryLayout<Password>.size // 33 (實際用到的空間大小)
MemoryLayout<Password>.alignment // 8 (對齊參數(shù),此時要求分配空間是8的倍數(shù), 不夠8的倍數(shù)要補齊)
enum Season {
case spring = 1, summer, autumn, winter //此處是原始值,沒有存儲到枚舉變量內(nèi)存中, 底層只存儲了1個字節(jié)來區(qū)分變量
}
MemoryLayout<Int>.size //1
MemoryLayout<Int>.stride //1
MemoryLayout<Int>.alignment//1
/*為什么是1個字節(jié)?
是因為關(guān)聯(lián)值與原始值的區(qū)別:
原始值(定義成員的時候就給它一個默認值),原始值會永遠跟你的成員保存在一起,原始值是固定死的,不允許傳值;
關(guān)聯(lián)值的特點是允許自己傳入不同的值進來.
*/
// Int類型占用8個字節(jié), string類型占用16個字節(jié).
/*關(guān)聯(lián)值和原始值的區(qū)別:
關(guān)聯(lián)值是 枚舉的成員和成員自己的其他類型數(shù)據(jù)關(guān)聯(lián)到一起存儲的.
原始值是 在枚舉定義成員的時候就給它一個默認值.
如果是關(guān)聯(lián)值, 是將傳過來的值, 直接存儲到枚舉變量的內(nèi)存中. 每一個枚舉變量都要有自己的內(nèi)存來存儲這些值.
原始值是固定死的, 跟自己的成員固定綁定在一起, 不允許自定義. 沒有存儲在枚舉變量中.
那么原始值存儲在哪里? 完全可以不存, 比如:
enum Season : Int {
// 0 1 2 3
case spring = 1, summer, autumn, winter
func rawValue()-> Int {
if self == 0 return 1
if self == 1 return 2
...
}
}
*/
總結(jié):
如果枚舉變量是關(guān)聯(lián)值,到時候可以傳入一個具體的值和枚舉進行關(guān)聯(lián),那么傳進去的這些值是直接存儲到枚舉變量內(nèi)存里面;
如果枚舉類型寫個:Int之類的什么類型的,叫做原始值,原始值是跟每一個成員是固定綁在一起的, 但是這些原始值是不會占用你的枚舉變量的內(nèi)存的, 相當(dāng)于我們的原始值不是存儲在枚舉變量的內(nèi)存里面的.
3擅威、可選項(Optional)
//可選項, 一般也叫可選類型, 它允許將值設(shè)置為nil
//在類型名稱后面加個?來定義一個可選項
var name: String? = "Jack"http://默認情況下是不允許你給空值的,只有設(shè)置為可選項才可以設(shè)置為nil
name = nil
var age: Int? //默認就是nil
age = 10
age = nil
var array = [1, 15, 40, 29]
func get(_ index: Int) -> Int? {//表明可以返回nil或Int類型
if(index < 0 || index >= array.count){
return nil
}
return array[index]
}
print(get(1)) // Optional(15)
print(get(-1))// nil
print(get(4))// nil
3-1、強制解包(Forced Unwrapping)
/*
可選項是對其他類型的一層包裝, 可以將它理解為一個盒子,
如果為nil, 那么它是個空盒子,
如果不為nil, 那么盒子里裝的是: 被包裝類型的數(shù)據(jù);
如果要從可選項中取出被包裝的數(shù)據(jù)(將盒子里裝的東西取出來), 需要使用感嘆號!進行強制解包.
*/
var age: Int? = 10
let ageInt: Int = age!
ageInt += 10
//如果對值nil的可選項(空盒子)進行解包, 將會產(chǎn)生運行時錯誤. 所以強制解包時一定要確認它不是nil
3-2、判斷可選值是否包含值
let number = Int("123")
if number != nil {
print("字符串轉(zhuǎn)換整數(shù)成功:\(number!)")
}else{
print("字符串轉(zhuǎn)換整數(shù)失敗")
}// 字符串轉(zhuǎn)換整數(shù)成功:123
3-3、可選項綁定(Optional Binding)
// 可以使用可選項綁定來判斷可選項是否包含值
// 如果包含就自動解包,把值賦給一個臨時的常量(let)或者變量(var),并返回true,否則返回false
if let number = Int("123") {
print("字符串轉(zhuǎn)換成功:\(number)")
// number是強制解包之后的Int值
// number作用域僅限于這個大括號
}else{
print("字符串轉(zhuǎn)換整數(shù)失敗")
}// 字符串轉(zhuǎn)換整數(shù)成功:123
enum Season : Int {
case spring = 1, summer, autumn, winter
}
if let season = Season(rawValue: 6) {
switch season {
case.spring:
print("the season is spring")
default:
print("the season is other")
}
} else {
print("no such season")
}// no such season
// 等價寫法:
if let first = Int("4") {
if let second = Int("42"){
if first < second && second < 100 {
print("\(first) < \(second) < 100")
}
}
}// 4 < 42 < 100
if let first = Int("4"),
let second = Int("42"),
first < second && second < 100 {
print("\(first) < \(second) < 100")
}// 4 < 42 < 100
3-4躯喇、while循環(huán)中使用可選項綁定
// 遍歷數(shù)組, 將遇到的正數(shù)都加起來, 如果遇到負數(shù)或者非數(shù)字, 停止遍歷
var strs = ["10", "20", "abc", "-20", "30"]
var index = 0
var sum = 0
while let num = Int(strs[index]), num > 0 {
sum += num
index += 1
}
print(sum)//30
3-5、空合并運算符??(Nil-Coalescing Operator)
public func ?? <T>(optional:T?,defaultValue: @autoclosure() throws -> T?) rethrows -> T?
public func ?? <T>(optional:T?,defaultValue: @autoclosure() throws -> T) rethrows -> T
a ?? b //a是可選項,??左邊必須放可選項; b是可選項或者不是可選項; b跟a的存儲類型必須相同; 作用:如果a不為nil,就返回a,此時如果b不是可選項,返回a時會自動解包;如果a為nil,就返回b,
let a: Int? = 1
let b: Int? = 2
let c = a ?? b // c是Int? ,Optional(1)
let a: Int? = nil
let b: Int? = 2
let c = a ?? b // c是Int? ,Optional(2)
let a: Int? = nil
let b: Int? = nil
let c = a ?? b // c是Int? ,nil
let a: Int? = 1
let b: Int = 2
let c = a ?? b // c是Int ,1
let a: Int? = nil
let b: Int = 2
let c = a ?? b // c是Int ,2
let a: Int? = nil
let b: Int = 2
//如果不使用??運算符
let c : Int
if let tmp = a {
c = tmp
}else {
c = b
}
// 多個 ?? 一起使用
let a: Int? = 1
let b: Int? = 2
let c = a ?? b ?? 3 // c是Int , 1 , c是什么類型要看最后一個運算符是什么類型.
let a: Int? = nil
let b: Int? = 2
let c = a ?? b ?? 3 // c是Int , 2
let a: Int? = nil
let b: Int? = nil
let c = a ?? b ?? 3 // c是Int , 3
??跟if let配合使用
let a: Int? = nil
let b: Int? = 2
if let c = a ?? b {
print(c)
}//類似于 if a != nil || b != nil
if let c = a, let d = b{
print(c)
print(d)
}//類似于if a != nil && b != nil
3-6宾袜、if語句實現(xiàn)登錄
func login(_ info:[String: String]) {
let username: String
if let tmp = info["username"] {
username = tmp
} else {
print("請輸入用戶名")
return
}
let password: String
if let tmp = info["password"] {
password = tmp
} else {
print("請輸入密碼")
return
}
// if username ....
// if password ....
print("用戶名:\(username)","密碼:\(password)","登錄ing")
}
login(["username" : "jack","password":"123456"])//用戶名:jack 密碼:123456 登錄ing
login(["password":"123456"])// 請輸入用戶名
login(["username" : "jack"])// 請輸入密碼
3-7、字典和數(shù)組的元素的返回值類型
var dict = ["age":10]
var age = dict["abc"]// Int? 字典返回可選類型
var array = [1,2,3]
var num = array[-1]// Int 數(shù)組直接返回值, 需要自己檢查數(shù)組越界
3-8驾窟、guard語句
guard 條件 else {
//do something...
退出當(dāng)前作用域
//return庆猫、break、continue绅络、throw月培、error
}
//當(dāng)guard語句的條件為false時, 就會執(zhí)行大括號里的代碼;
//當(dāng)guard語句的條件為true時, 就會跳過guard語句
//guard語句特別適合用來"提前退出"
//當(dāng)使用guard語句進行可選項綁定時,綁定的常量(let)、變量(var)也能在外層作用域中使用
//用guard簡寫3-7的登錄邏輯:
func login(_ info:[String: String]){
guard let username = info["username"] else{
print("請輸入用戶名")
return
}
guard let password = info["password"] else {
print("請輸入密碼")
return
}
//if username ....
//if password ....
print("用戶名:\(username)","密碼:\(password)","登錄ing")
}
login(["username" : "jack","password":"123456"])//用戶名:jack 密碼:123456 登錄ing
login(["password":"123456"])// 請輸入用戶名
login(["username" : "jack"])// 請輸入密碼
3-9恩急、隱式解包(Implicitly Unwrapped Optional)
// 在某些情況下, 可選項一旦被設(shè)定值之后, 就會一直擁有值
// 在這種情況下, 可以去掉檢查, 也不必每次訪問的時候都進行解包, 因為它能夠確定每次訪問的時候都有值
// 可以在類型后面加個感嘆號 ! 定義一個隱式解包的可選項
let num1: Int! = 10 // 隱式解包的可選項(自動解包), 等價于:let num2: Int = num1!
let num2: Int = num1
if num1 != nil {
print(num1 + 6)// 16
}
if let num3 = num1 {
print(num3)// 10
}
/// 如果可選項是空值那么會報錯
let num1: Int! = nil
let num2: Int = num1// Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file MyPlayground.playground, line
3-10杉畜、字符串插值
// 字符串插值
// 可選項在字符串插值或者直接打印時, 編譯器會發(fā)出警告
var age: Int? = 10
print("My age is \(age)")//My age is Optional(10)
// 至少有3種方法消除警告
print("My age is \(age!)")//My age is 10
print("My age is \(String(describing: age))")//My age is Optional(10)
print("My age is \(age ?? 0)")// My age is 10
3-11、多重可選項
var num1: Int? = 10 //包裝了一個Int類型的可選類型盒子
var num2: Int?? = num1 // 包裝了一個可選類型的可選類型盒子
var num3: Int?? = 10
print(num2 == num3) // true
var aum1: Int? = nil
var aum2: Int?? = aum1
var aum3: Int?? = nil
print(aum2 == aum3) // false
(aum2 ?? 1) ?? 2 // 2
(aum3 ?? 1) ?? 2 // 1
/*
可以使用lldb指令: help frame 查看frame有哪些指令
查看內(nèi)存布局:
frame variable -R 或者 fr v -R 查看內(nèi)存布局的區(qū)別
比如:fr v -R aum1
*/
3-12衷恭、思考下面枚舉變量的內(nèi)存布局
enum TestEnum {
case test1, test2, test3
}
// 1個字節(jié)存儲成員值
// N個字節(jié)存儲關(guān)聯(lián)值(N取占用內(nèi)存最大的關(guān)聯(lián)值), 任何一個case的關(guān)聯(lián)值都共用這N個字節(jié)
// 共用體
// 有時候會將枚舉的成員值跟其他類型的值關(guān)聯(lián)存儲在一起, 會非常有用
//枚舉總共能存放256個字節(jié) 0x00~0xFF
var t = TestEnum.test1 //0
t = .test2 //1
t = .test3 //2
print(MemoryLayout<TestEnum>.size)
print(MemoryLayout<TestEnum>.stride)
print(MemoryLayout<TestEnum>.alignment)
// 查看變量內(nèi)存存儲情況
// 打斷點: Debug >> Debug Workflow >>View Memory 或者右鍵選擇 View Memory of "xxx"
第四章 匯編分析枚舉的內(nèi)存布局
1-1此叠、程序的本質(zhì)
軟件/程序的執(zhí)行過程:
(硬盤)程序/軟件-->(裝載)內(nèi)存--->(讀寫)--->CPU-->(控制)計算機
CPU中分為:
寄存器 --> 用來做:信息存儲
運算器 --> 用來做:信息處理
控制器
通常, CPU會先將內(nèi)存中的數(shù)據(jù)存儲到寄存器中, 然后再對寄存器中的數(shù)據(jù)進行運算
假設(shè)內(nèi)存中有塊紅色內(nèi)存空間的值是3,現(xiàn)在想把它的值加1, 并將結(jié)果存儲到藍色內(nèi)存空間
1.CPU首先會將紅色內(nèi)存空間的值放到rax寄存器中: movq 紅色內(nèi)存空間, %rax //movq是將左邊的值送給到右邊
2.然后讓rax寄存器與1相加: addq $0x1, %rax
3.最后將值賦值給內(nèi)存空間: movq %rax, 藍色內(nèi)存空間
CPU規(guī)定:
1.不允許內(nèi)存間直接交互數(shù)據(jù),必須要通過CPU的寄存器
2.想做運算或計算也要拉倒CPU的寄存器中進行運算, 運算結(jié)束后再送回內(nèi)存
1-2、匯編語言的發(fā)展
機器語言
由0-1組成
匯編語言(Assembly Language)
用符號代替0和1, 比機器語言便于閱讀和記憶
高級語言
C/C++/JavaScript/Python等, 更接近人類自然語言
操作: 將寄存器BX的內(nèi)容送入寄存器AX
機器語言: 1000100111011000
匯編語言: movm %bx, %ax
高級語言: ax = bx; //這里只是舉例,高級語言不能直接操作寄存器.
高級語言-->(編譯)匯編語言<-->(編譯/反編譯)機器語言-->(運行)計算機
匯編語言與機器語言一一對應(yīng), 每一條機器指令都有與之對應(yīng)的匯編指令;
匯編語言可以通過編譯得到機器語言, 機器語言可以通過反匯編得到匯編語言;
高級語言可以通過編譯得到匯編語言/機器語言, 但匯編語言/機器語言幾乎不可能
1-3随珠、匯編語言的種類
匯編語言嚴重依賴于電腦硬件設(shè)備, 相當(dāng)于CPU的架構(gòu)不同用的匯編就不同, 種類有:
8086匯編(16bit) //2個字節(jié)
x86匯編(32bit) //4個字節(jié)
x64匯編(64bit) //8個字節(jié)
ARM匯編(嵌入式灭袁、移動設(shè)備)
...
x86猬错、x64匯編根據(jù)編譯器的不同, 有2種書寫格式:
Intel: Windows派系
AT&T: Unix派系
作為iOS開發(fā)工程師, 最主要的匯編語言是
AT&T匯編 --> iOS模擬器
ARM匯編 --> iOS真機設(shè)備
1-4、常見的匯編指令
項目 | AT&T | Intel | 說明 |
---|---|---|---|
寄存器命名 | %rax | rax | |
操作數(shù)順序 | movq %rax, %rdx | mov rdx, rax | 將rax的值賦值給rdx |
常數(shù)/立即數(shù) | movq 0x10, %rax | mov rax 3 mov rax, 0x10 | 將3賦值給rax 將0x10賦值給rax |
內(nèi)存賦值 | movq $0xa, 0x1ff7(%rip) | mov qword ptr [rip+0x1ff7], 0xa | 將0xa賦值給地址為rip + 0x1ff7的內(nèi)存空間 |
取內(nèi)存地址 | leaq -0x18(%rbp), %rax | lea rax, [rbp - 0x18] | 將rbp - 0x18這個地址賦值給rax |
jmp指令 | jmp *%rdx jmp 0x4001002 jmp *(%rax) | jmp rdx jmp 0x4001002 jmp [rax] | call和jmp寫法類似,區(qū)別: jmp是跳轉(zhuǎn)到某個地址去執(zhí)行代碼,一直到結(jié)束; 而call是跳到這個地址(call后面跟的一般是函數(shù)地址)去執(zhí)行代碼,然后配合ret(return)來配合使用 |
操作數(shù)長度 | movl %eax, %edx movb $0x10, %al leaw 0x10(%dx), %ax | mov edx, eax mov al, 0x10 lea ax, [dx + 0x10] | b = byte(8-bit) s = short(16-bit integer or 32-bit floating point) w = word(16-bit) i = long(32-bit integer or 64-bit floating point) q = quad(64 bit) t = ten bytes(80-bit flating point) |
備注: rip儲存的是指令地址: 即CPU要執(zhí)行的下一條指令地址就存儲在rip中. rip += 正在執(zhí)行指令的長度
1-5茸歧、寄存器
有16個常用的寄存器
%rax倦炒、%rbx、%rcx软瞎、%rdx逢唤、%rsi、%rbp涤浇、%rsp
%r8鳖藕、%r9、%r10只锭、%r11著恩、%r12、%r13纹烹、%r14页滚、%r15
寄存器的具體用途
%rax召边、rdx常用作為函數(shù)返回值使用
%rdi铺呵、%rsi、%rdx隧熙、%rcx片挂、%r8、%r9等寄存器常用于存放函數(shù)參數(shù)
%rsp贞盯、%rbp用于棧操作
rip作為指令指針
存儲著CPU下一條要執(zhí)行的指令的地址
一旦CPU讀取一條指令,rip會自動指向下一條指令(存儲下一套指令的地址)
r開頭: 64bit的寄存器, 只有8個字節(jié)
e開頭: 32bit的寄存器, 占4個字節(jié)
ax,bx,cx : 16bit的寄存器, 占2個字節(jié)
ah al bh bl: 占1個字節(jié)
問: 64位的結(jié)構(gòu)體怎么存儲大于8個字節(jié)的數(shù)據(jù)呢,比如結(jié)構(gòu)體對象?
答: 對象存在內(nèi)存中,結(jié)構(gòu)體不能塞到寄存器里面去的,它就是放在內(nèi)存中
1-6音念、lldb常用指令
讀取寄存器的值:
register read/格式
register read/x
修改寄存器的值:
register write 寄存器名稱 數(shù)值
register write rax 0
讀取內(nèi)存中的值:
x/數(shù)量-格式-字節(jié)大小 內(nèi)存地址
x/3xw 0x0000010
修改內(nèi)存中的值
memory write 內(nèi)存地址 數(shù)值
memory write 0x0000010 10
格式
x是16進制, f是浮點型, d是十進制
字節(jié)大小
b - byte 1字節(jié)
h - half word 2字節(jié)
w - word 4字節(jié)
g - giant word 8字節(jié)
express表達式
可以簡寫: expr表達式
expression $rax
expression $rax = 1
po 表達式
point 表達式
po/x(格式) $rax
thread step-over、next躏敢、n
單步運行, 把子函數(shù)當(dāng)做整體一步執(zhí)行(源碼級別)
thread step-in闷愤、step、s
單步運行, 遇到子函數(shù)會進入子函數(shù)(源碼級別)
thread step-inst-over件余、next讥脐、ni
單步運行,把子函數(shù)當(dāng)做整體一步執(zhí)行(匯編級別)
thread step-inst、stepi啼器、si
單步運行,遇到子函數(shù)會進入子函數(shù)(匯編級別)
thread step-out旬渠、finish
直接執(zhí)行完當(dāng)前函數(shù)所有代碼, 返回到上一個函數(shù)(遇到斷點會卡住)
補充知識點:
指令的內(nèi)存地址 第幾個字節(jié) 匯編指令
0x10000d40 <+1>: movq %rsp, %rbp
imm立即數(shù), 也就是字面量
第五章 匯編分析結(jié)構(gòu)體、類的內(nèi)存布局
1-1端壳、結(jié)構(gòu)體
//在Swift標準庫中, 絕大多數(shù)的公開類型都是結(jié)構(gòu)體, 而枚舉和類只占很小一部分
//比如Bool告丢、Int、Double损谦、String岖免、Array岳颇、Dictionary等常見類型都是結(jié)構(gòu)體.
①struct Date {
② var year: Int = 0
③ var month: Int
④ var day: Int
⑤}
⑥var date = Date(year: 2019, month: 6, day: 23)
//所有的結(jié)構(gòu)體都有一個編譯器自動生成的初始化器(Initializer, 初始化方法、構(gòu)造器觅捆、構(gòu)造方法)
//在第⑥行調(diào)用的, 可以傳入所有成員值,用以初始化所有成員(存儲屬性, Stored Property)
1-2赦役、結(jié)構(gòu)體的初始化器
//編譯器會根據(jù)情況, 可能會為結(jié)構(gòu)體生成多個初始化器(比如給定成員初始值), 宗旨是: 保證所有成員都有初始值.
struct Point {
var x: Int?
var y: Int?
}
var p1 = Point(x:10, y:10)
var p2 = Point(y:10)
var p3 = Point(x:10)
var p4 = Point()
//可選項都有個默認值nil, 因此上面代碼可以編譯通過!!
1-3、自定義初始化器
//一旦在自定義結(jié)構(gòu)體時定義了初始化器, 編譯器就不會再幫它自動生成其他初始化器.
struct Point {
var x: Int = 0
var y: Int = 0
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
var p1 = Point(x: 10, y: 10)
1-4栅炒、窺探初始化器的本質(zhì)
// 以下2端代碼完全等效
struct Point {
var x: Int = 0
var y: Int = 0
}
var p = Point()
struct Point{
var x: Int
var y: Int
init() {
x = 0
y = 0
}
}
var p = Point()
2-1掂摔、類
//類的定義和結(jié)構(gòu)體類似, 但編譯器并沒有為類自動生成可以傳入成員值的初始化器
class Point {
var x: Int = 0
var y: Int = 0
}
let p1 = Point()
//如果類的所有成員都在定義的時候指定了初始值, 編譯器會為類生成無參的初始化器
//成員的初始化是在這個初始化器中完成的
2-2、結(jié)構(gòu)體與類的本質(zhì)區(qū)別
//結(jié)構(gòu)體是值類型(枚舉也是值類型), 類是引用類型(指針類型)
class Size {
var width = 1
var height = 2
}
struct Point {
var x = 3
var y = 4
}
func test() {
var size = Size()
print("size變量的地址",Mems.ptr(ofVal: &size))
print("size變量的內(nèi)存",Mems.memStr(ofVal: &size))
print("size所指向內(nèi)存的地址",Mems.ptr(ofRef: size))
print("size所指向內(nèi)存的內(nèi)容",Mems.memStr(ofRef: size))
var point = Point()
print("point變量的地址",Mems.ptr(ofVal: &point))
print("point變量的內(nèi)存",Mems.memStr(ofVal: &point))
}
// 值類型的特點: 如果值類型是在函數(shù)里面創(chuàng)建的,那么它的內(nèi)存肯定在椨蓿空間里面
// 指針變量在64bit椧依欤空間內(nèi)存中占幾個字節(jié)? 8個字節(jié),存放的是Size對象的內(nèi)存地址.Size對象在堆空間. Size對象在堆空間占用32個字節(jié)內(nèi)存,分別是引用計數(shù)(8個字節(jié))、指針類型信息(8個字節(jié)).
// 上面都是針對64bit環(huán)境
// 匯編中有alloc释移、malloc相關(guān)的信息, 說明系統(tǒng)分配在堆空間.
2-3叭披、對象的堆空間申請過程
//在Swift中, 創(chuàng)建類的實例對象, 要向堆空間申請內(nèi)存, 大概流程如下:
Class._allocating_init()
libswiftCore.dylib:_swift_allocObject_
libswiftCore.dylib:swift_slowAlloc
libsystem_malloc.dylib:malloc
//在Mac、iOS中malloc函數(shù)分配的內(nèi)存大小總是16的倍數(shù)
var ptr = malloc(1) //申請1個字節(jié)的堆空間
print(malloc_size(ptr))// 16, 所指向的堆空間是16個字節(jié)
var ptr = malloc(17) //申請17個字節(jié)的堆空間
print(malloc_size(ptr))// 32, 分配給你的堆空間是32個字節(jié)
class Size {
var width = 1
var height = 2
}
var size = Size()
print(malloc_size(Mems.ptr(ofRef: size)))//size這個指針變量,它指向堆空間是32個字節(jié)
//通過class_getInstanceSize可以得知: 類的對象至少需要占用多少內(nèi)存
class Point {
var x = 11//8
var text = true//1
var y = 22//8
}//實際利用的字節(jié)為33個
var p = Point() // 要經(jīng)過堆空間malloc檢查, 是否為16的倍數(shù)
class_getInstanceSize(type(of:p))//40 內(nèi)存對齊后,對象至少要占多少內(nèi)存
class_getInstanceSize(Point.self)//40 對象占用多少內(nèi)存,內(nèi)存對齊是8
print(Mems.size(ofRef:p))//48 堆空間真實分配內(nèi)存空間的大小,堆空間分配空間的時候,內(nèi)存對齊是16
問:結(jié)構(gòu)體都存在棧里?
答: 結(jié)構(gòu)體內(nèi)存在哪里取決于你在哪里定義的. 如果你的結(jié)構(gòu)體變量是在函數(shù)里面定義的,那么它的內(nèi)存肯定就在椡婊洌空間; 如果你的結(jié)構(gòu)體變量在外部定義的,它的內(nèi)存就在數(shù)據(jù)段,也就是全局區(qū),因為它是個全局變量; 假如結(jié)構(gòu)體是在對象里面,那么這個結(jié)構(gòu)體肯定跟隨對象在堆空間. 同理枚舉.
2-4涩蜘、值類型
值類型賦值給var、let或者給函數(shù)傳參, 是直接將所有內(nèi)容拷貝一份.
類似于對文件進行copy熏纯、paste操作,產(chǎn)生了全新的文件副本.屬于深拷貝(deep copy)
struct Point{
var x: Int
var y: Int
}
func test(){
var p1 = Point(x: 10, y: 20)
var p2 = p1
p2.x = 11
p2.y = 22
//請問p1.x和p1.y是多少? 答: 10 20
}
/*規(guī)律:
內(nèi)存地址格式為: 0x4bdc(%rip), 一般是全局變量, 全局區(qū)(數(shù)據(jù)段)
內(nèi)存地址格式為: -0x78(%rbp), 一般是局部變量, 椡耄空間
內(nèi)存地址格式為: 0x10(%rax), 一般是堆空間
*/
//值類型賦值操作
var s1 = "Jack"
var s2 = s1
s2.append("_Rose")
print(s1)// Jack
print(s2)// Jack_Rose
/*
在Swift標注庫中, 為了提升性能, String、Array樟澜、Dictionary误窖、Set采用了Copy On Write的技術(shù)(也就是說當(dāng)我們修改內(nèi)存的時候,它才會進行深度拷貝操作,否則是淺拷貝).
比如僅當(dāng)有"寫"操作時, 才會真正執(zhí)行拷貝操作
對于標準庫值類型的賦值操作, Swift能確保最佳性能, 所以沒必要為了保證最佳性能來避免賦值.
*/
2-5、引用類型
/*
引用賦值給var秩贰、let或者給函數(shù)傳參, 是將內(nèi)存地址拷貝一份.
類似于制作一個文件的替身(快捷方式霹俺、鏈接),指向的是同一個文件.屬于淺拷貝(shallow copy)
*/
class Size {
var width: Int
var height: Int
init(width:Int, height:Int) {
self.width = width
self.height = height
}
}
func test() {
var s1 = Size(width: 10, height: 20)
var s2 = s1
s2.width = 11
s2.height = 22
//請問s1.width和s2.height是多少? 答:11 22
}
第六、七章 匯編分析閉包本質(zhì)
1-1毒费、嵌套類型
struct Poker {
enum Suit : Character {
case spades = "?", hearts = "?", diamonds="?", clubs = "?"
}
enum Rank : Int {
case two = 2, three, four, five, six, seven, eight, nine, ten
case jack, queen, king, ace
}
}
print(Poker.Suit.hearts.rawValue)
var suit = Poker.Suit.spades
suit = .diamonds
var rank = Poker.Rank.five
rank = .king
1-2丙唧、枚舉、結(jié)構(gòu)體觅玻、類都可以定義方法
一般把定義在枚舉想际、結(jié)構(gòu)體、類內(nèi)部的函數(shù), 叫做方法.
struct Point {
var x = 10
var y = 10
func show(){//方法是不占用實例對象內(nèi)存的, 方法的本質(zhì)就是函數(shù), 函數(shù)串塑、方法都存放在代碼段
var a = 10//a局部變量
print("局部變量(椪恿穑空間)",Mems.ptr(ofVal: &a))
print("x=\(x),y=\(y)")
}
}
let p = Point()//Point()在堆空間; p全局變量,在數(shù)據(jù)段的地方;
p.show() // x=10, y=10
print("全局變量",Mems.ptr(ofVal:&p))
print("堆空間",Mems.ptr(ofVal:p))
2-1、閉包表達式( Closure Expression)
/// 在Swift中, 可以通過func定義一個函數(shù), 也可以通過閉包表達式定義一個函數(shù)
func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2}
// 簡寫一:
var fn = {
(v1: Int, v2: Int) -> Int in
return v1 + v2
}
fn(10, 20)
// 簡寫二:
{
(v1: Int, v2: Int) -> Int in
return v1 + v2
}(10, 20)
/*閉包的格式寫法:
{
(參數(shù)列表) -> 返回值類型 in
函數(shù)體代碼
}
*/
2-2桩匪、閉包表達式的簡寫
func exec(v1: Int, v2: Int, fn:(Int, Int) -> Int) {
print(fn(v1, v2))
}
// 簡寫一:
exec(v1: 10, v2: 20, fn:{
(v1: Int, v2: Int) -> Int in
return v1 + v2
})
// 簡寫二: (省略參數(shù)類型)
exec(v1: 10, v2: 20, fn: {
v1, v2 in return v1 + v2
})
// 簡寫三:
exec(v1: 10, v2: 20, fn: {
v1, v2 in v1 + v2
})
// 簡寫四:
exec(v1: 10, v2: 20, fn: { $0 + $1 })
// 簡寫五:
exec(v1: 10, v2: 20, fn: + )
2-3打瘪、尾隨閉包
/*如果將一個很長的閉包表達式作為函數(shù)的最后一個實參, 使用尾隨閉包可以增加函數(shù)的可讀性
尾隨閉包是一個被書寫在函數(shù)調(diào)用括號外面(后面)的閉包表達式
*/
func exec(v1: Int, v2: Int, fn:(Int, Int) -> Int) {
print(fn(v1, v2))
}
//尾隨閉包寫法:
exec(v1: 10, v2: 20){
$0 + $1
}
/*如果閉包表達式是函數(shù)的唯一實參, 而且使用了尾隨閉包的語法, 那就不需要在函數(shù)名后邊寫圓括號
*/
func exec(fn:(Int, Int) -> Int) {
print(fn(v1, v2))
}
exec(fn: { $0 + $1 })// 普通調(diào)用
exec() { $0 + $1 }// 尾隨閉包寫法
exec { $0 + $1 }// 尾隨閉包簡寫
2-4、示例 - 數(shù)組的排序
func testSort(){
var arr = [10, 1, 4, 20, 99]
arr.sort()
//arr.sort(by:(Int, Int) throws -> Bool)
print(arr)
}
/*解析:
func sort(by areInIncreasingOrder: (Element, Element) -> Bool)
*/
/// 返回true: i1排在i2前面
/// 發(fā)揮false: i1排在i2后面
func cmp(i1: Int, i2: Int) -> Bool {
// 大的排在前面
return i1 > i2
}
var nums = [11, 2, 18, 6]
nums.sort(by: cmp)// [18, 11, 6, 2]
// 簡寫一:
nums.sort(by: {
(i1: Int, i2: Int) -> Bool in
return i1 < i2
})
// 簡寫二:
nums.sort(by: {i1, i2 in return i1 < i2})
// 簡寫三:
nums.sort(by: {i1, i2 in i1 < i2})
// 簡寫四:
nums.sort(by: { $0 < $1 })
// 簡寫五:
nums.sort(by: <)
// 簡寫六:
nums.sort(){ $0 < $1 }
// 簡寫七:
nums.sort { $0 < $1 }
3-1、閉包(Closure)
/*網(wǎng)上有各種關(guān)于閉包的定義, 個人覺得比較嚴謹?shù)亩x是:
一個函數(shù)和它所捕獲的變量/常量環(huán)境組合起來, 稱為閉包
一般指的是定義在函數(shù)內(nèi)部的函數(shù);
一般它捕獲的是外層函數(shù)的局部變量/常量.
*/
typealias Fn = (Int) -> Int //定義類型
/// 閉包寫法一: (通過func)
func getFn() -> Fn{
var num = 0
func plus(_ i: Int) -> Int {
num += i
return num
}
return plus
}// 返回的plus和num形成了閉包
/// 閉包寫法二: (通過閉包表達式)
func getFn() -> Fn{
var num = 0
return {
num += $0
return num
}
}
var fn1 = getFn()
var fn2 = getFn()
print(fn1(1)) // 1
print(fn2(2)) // 2
print(fn1(3)) // 4
print(fn2(4)) // 6
/*可以把閉包想象成一個類的實例對象
內(nèi)存在堆空間
捕獲的局部變量/常量就是對象的成員(存儲屬性)
組成閉包的函數(shù)就是類內(nèi)部定義的方法
*/
class Closure {
var num = 0
func plus(_ i: Int) -> Int {
num += i
return num
}
}
var cs1 = Closure()
var cs2 = Closure()
cs1.plus(1)//1
cs2.plus(2)//2
cs1.plus(3)//4
cs2.plus(4)//6
3-2闺骚、練習(xí)
typealias Fn = (Int) -> (Int, Int)
func getFns() -> (Fn, Fn) {
var num1 = 0
var num2 = 0
func plus (_ i: Int) -> (Int, Int) {
num1 += i
num2 += i << 1 // <<1 等于 *2
return (num1, num2)
}
func minus(_ i: Int) -> (Int, Int) {
num1 -= i
num2 -= i << 1
return (num1, num2)
}
return (plus, minus)
}
let (p, m) = getFns()
p(5) //(5,10)
m(4) //(1,2)
p(3) //(4,8)
m(2) //(2,4)
3-3彩扔、注意
//如果返回值是函數(shù)類型, 那么參數(shù)的修飾要保持統(tǒng)一
func add(_ num: Int) -> (inout Int) -> Void {
func plus(v: inout Int) {//inout(輸入輸出參數(shù)), 與上面要保持統(tǒng)一
v += num
}
return plus
}
var num = 5
add(20)(&num)
print(num)
3-4、自動閉包
// 如果第1個數(shù)大于0, 返回第一個數(shù), 否則返回第2個數(shù)
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
return v1 > 0 ? v1 : v2
}
getFirstPositive(10, 20) //10
getFirstPositive(-2, 20) //20
getFirstPositive(0, -4) //-4
// 改成函數(shù)類型的參數(shù), 可以讓v2延遲加載
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int? {
return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4) { 20 }
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? {
return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4, 20)
getFirstPositive(-4, {30})
// @autoclosure 會自動將20封裝成閉包{ 20 }
// @autoclosure 只支持() -> T 格式的參數(shù)
// @autoclosure 并非只支持最后一個參數(shù)
// 空合并運算符 ?? 使用了 @autoclosure 技術(shù)
// 有@autoclosure僻爽、無@autoclosure, 構(gòu)成了函數(shù)重載
// 為了避免與期望沖突, 使用了@autoclosure的地方最好明確注釋清楚, 這個值會被推遲執(zhí)行
第八章 屬性虫碉、匯編分析inout本質(zhì)
1-1、屬性
/*Swift中跟實例相關(guān)的屬性可以分為2大類
存儲屬性( Stored Property)
>類似于成員變量這個概念
>存儲在實例的內(nèi)存中
>結(jié)構(gòu)體胸梆、類可以定義存儲屬性
>枚舉不可以定義存儲屬性
計算屬性( Computed Property)
>本質(zhì)就是方法(函數(shù))
>不占用實例的內(nèi)存
>枚舉敦捧、結(jié)構(gòu)體、類都可以定義計算屬性
*/
struct Circle {
//存儲屬性
var radius: Double
//計算屬性
var diameter: Double {
set {
radius = newValue / 2
}
get {
radius * 2
}
}
}
var c = Circle(radius: 10)
c.radius = 11
c.diameter = 40
print(c.radius)// 20.0
print(MemoryLayout<Circle>.stride)// 8
1-2碰镜、存儲屬性
/*關(guān)于存儲屬性, Swift有個明確的規(guī)定:
在創(chuàng)建類或結(jié)構(gòu)體的實例時, 必須為所有的存儲屬性設(shè)置一個合適的初始值
>可以在初始化器里面為屬性設(shè)置一個初始值
>可以分配一個默認的屬性值作為屬性定義的一部分
*/
1-3兢卵、計算屬性
/// set傳入的新值默認叫做newValue, 也可以自定義
struct Circle {
var radius: Double
var diameter: Double {
set(newDiameter){
radius = newDiameter / 2
}
get {
radius * 2
}
}
}
/// 只讀計算屬性: 只有g(shù)et, 沒有set
struct Circle {
var radius: Double
var diameter: Double {
get {
radius * 2
}
}
}
//簡寫:
struct Circle {
var radius: Double
var diameter: Double { radius * 2 }
}
/* 定義計算屬性只能用var, 不能用let.
>let代表常量.值是一成不變的
>計算屬性的值是可能發(fā)生變化的(即使是只讀計算屬性)
*/
1-4、枚舉rawValue原理
/*枚舉原始值rawValue的本質(zhì)是: 只讀計算屬性*/
enum TestEnum: Int {
case test1 = 1, test2 = 2, test3 = 3
var rawValue: Int {
switch self {
case .test1:
return 10
case .test2:
return 11
case .test3:
return 12
}
}
}
print(TestEnum.test3.rawValue) //12
1-5绪颖、延遲存儲屬性( Lazy Stored Property)
/*使用lazy可以定義一個延遲存儲屬性, 在第一次用到屬性的時候才會進行初始化*/
class Car {
init(){
print("car init!")
}
func run(){
print("car is running!")
}
}
class Person{
lazy var car = Car()
init(){
print("Persion init!")
}
func goOut(){
car.run()
}
}
let p = Persion()
print("---------")
p.goOut()
class PhotoView {
lazy var image: Image = {
let url = "https://www.520it.com/xx.png"
let data = Data(url: url)
return Image(data: data)
}()//閉包
}
/*
lazy屬性必須是var, 不能是let
let必須在實例的初始化方法完成之前就擁有值
如果多條線程同時第一次訪問lazy屬性
無法保證屬性只被初始化1次
*/
/*延遲存儲屬性注意點:
當(dāng)結(jié)構(gòu)體包含一個延遲屬性時, 只有var才能訪問延遲存儲屬性
因為延遲屬性初始化時需要改變結(jié)構(gòu)體的內(nèi)存
*/
struct Point {
var x = 0
var y = 0
lazy var z = 0
}
let p = Point()//此時必須用var修飾
print(p.z)// 報錯
1-6秽荤、屬性觀察器(Property Observer)
/*可以為非lazy的var存儲屬性設(shè)置屬性觀察器*/
struct Circle {
var radius: Double {//存儲屬性
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, radius)
}
}
init(){
self.radius = 1.0
print("Circle init!")
}
}
var circle = Circle() // Circle init
circle.radius = 10.5 //willSet 10.5 \n didSet 1.0 10.5
print(circle.radius)// 10.5
/*
willSet會傳遞新增, 默認叫newValue
didSet會傳遞舊值, 默認叫oldValue
在初始化器中設(shè)置屬性值不會觸發(fā)willSet和didSet
>在屬性定義的時設(shè)置初始值也不會觸發(fā)willSet和didSet
*/
1-7、全局變量柠横、局部變量
/*屬性觀察器窃款、計算屬性的功能, 同樣也可以應(yīng)用在全局變量、局部變量身上*/
var num: Int {
get {
return 10
}
set {
print("setNum",newValue)
}
}
num = 11 // setNum 11
print(num)// 10
func test() {
var age = 10 {
willSet {
print("willSet", newValue)
}
didSet {
Print("didSet", oldValue, age)
}
}
age = 11
// willSet 11
// didSet 10 11
}
test()
2-1牍氛、inout的再次研究
struct Shape {
var width: Int
var side: Int { //帶有屬性觀察器的儲存屬性
willSet {
print("willSetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
var girth: Int {
set {
width = newValue / side
print("setGirth", newValue)
}
get {
print("getGirth")
return width * side
}
}
func show(){
print("width=\(width),side=\(side),girth=\(girth)")
}
}
func test(_ num: inout Int) { //接收一個地址值,輸入輸出參數(shù)inout本質(zhì)是引用傳遞
print("test")
num = 20
}
var s = Shape(width: 10, side: 4)
test(&s.width)
s.show()
print("-----------")
test(&s.side)
s.show()
print("-----------")
test(&s.girth)
s.show()
print("-----------")
/*inout的本質(zhì)總結(jié):
如果實參有物理內(nèi)存地址, 且沒有設(shè)置屬性觀察器
直接將實參的內(nèi)存地址傳入函數(shù)(實參進行引用傳遞)
如果實參是計算屬性 或者 設(shè)置了屬性的觀察者
采用了Copy In Copy Out的做法:
調(diào)用該函數(shù)時, 先復(fù)制實參的值, 產(chǎn)生副本[get]
將副本的內(nèi)存地址傳入函數(shù)(副本進行引用傳遞),在函數(shù)內(nèi)部可以修改副本的值
函數(shù)返回后, 再將副本的值覆蓋實參的值[set]
總結(jié): inout的本質(zhì)就是引用傳遞(地址傳遞)
*/
3-1晨继、類型屬性(Type Property)
/*嚴格來說, 屬性可以分為:
實例屬性(Instance Property): 只能通過實例去訪問
>存儲實例屬性(Stored Instance Property): 存儲在實例內(nèi)存中, 每個實例都有1份
>計算實例屬性(Computed Instance Property)
類型屬性(Type Property):只能通過類型去訪問
>存儲類型屬性(Stored Type Property): 這個程序運行過程中, 就只有1份內(nèi)存(類似于全局變量)
>計算類型屬性(Computed Type Property)
可以通過static定義類型屬性
如果是類, 可以通過關(guān)鍵字class. 不過關(guān)鍵字class不能修飾類中的存儲屬性
*/
class Shape {
var width: Int = 0 //實例屬性
static var count: Int = 0 // static關(guān)鍵字聲明類型屬性
}
var s = Shape()
s.count = 10//報錯
Shape.count = 10
struct Car {
static var count: Int = 0
init(){
Car.count += 1
}
}
let c1 = Car()
let c2 = Car()
let c3 = Car()
print(Car.count)//3
3-2、類型屬性細節(jié)
/*不同存儲實例屬性, 你必須給存儲類型屬性設(shè)定初始值
>因為類型沒有像實例那樣的init初始化器來初始化存儲屬性
存儲類型屬性默認就是lazy, 會在第一次使用的時候才初始化
>就算被多個線程訪問, 保證只會初始化一次
>存儲類型屬性可以是let
枚舉類型也可以定義類型屬性(存儲類型屬性糜俗、計算類型屬性)
*/
enum Shape {
static var width: Int = 0 //可以定義類型屬性, 不可以定義實例存儲屬性
case s1, s2, s3, s4
}
var s = Shap.s1
/*單例模式*/
public class FileManager {
public static let shared = FileManager()
private init(){
}
func open() {
}
func close() {
}
}
FileManager.shared.open()
FileManager.shared.close()
第九章 匯編分析類型屬性踱稍、方法曲饱、下標悠抹、繼承
1-1、方法(Method)
/*枚舉扩淀、結(jié)構(gòu)體楔敌、類都可以定義實例方法、類型方法
實例方法(Instance Method ) : 通過實例調(diào)用
類型方法(Type Method): 通過類型調(diào)用, 用static或者class關(guān)鍵字定義
*/
class Car {
static var count = 0
init() {
Car.count += 1
}
static func getCount() -> Int { count }
}
let c0 = Car()
let c1 = Car()
let c2 = Car()
print(Car.getCount()) //3
/*
self
在實例方法中代表實例
在類型方法中代表類型
在類型方法中 static func getCount中
count等價于self.count驻谆、Car.self.count卵凑、Car.count
*/
1-2、mutating
/*結(jié)構(gòu)體和枚舉是值類型, 默認情況下, 值類型的屬性不能被自身的實例方法修改
在func關(guān)鍵字前面加mutating可以允許這種修改行為
*/
struct Point{
var x = 0.0, y = 0.0
mutating func moveBy(deltaX: Double, deltaY: Double) {
x += deltaX
y += deltaY
}
}
enum StateSwitch {//狀態(tài)開關(guān)
case low, middle, high
mutating func next() {
switch self {
case .low:
self = .middle
case .middle:
self = .high
case .high:
self = .low
}
}
}
1-3胜臊、@discardableResult
// 在func前面加個@discardableResult, 可以消除: 函數(shù)調(diào)用返回值未被使用的警告??
struct Point{
var x = 0.0, y = 0.0
@discardableRusult mutating func moveX(deltaX: Double) -> Double {
x += deltaX
return x
}
}
var p = Point()
p.moveX(deltaX: 10)
@discardableResult func get() -> Int {
return 10
}
get()
2-1勺卢、下標(subscribe)
/// 使用subscribe可以給任意類型(枚舉、結(jié)構(gòu)體象对、類)增加下標功能, 有些地方也翻譯為: 下標腳本
// subscript的語法類似于實例方法黑忱、計算屬性,本質(zhì)就是方法(函數(shù))
class Point{
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
set {
if index == 0 {
x = newValue
} else if index == 1{
y = newValue
}
}
get {
if index == 0{
return x
} else if index == 1{
return y
}
return 0
}
}
}
var p = Point()
p[0] = 11.1 //下標set方法
p[1] = 22.2
print(p.x)//11.1
print(p.y)//22.2
print(p[0])//11.1 下標get方法
print(p[1])//22.2
/*subscript中定義的返回值類型決定了:
get方法的返回值類型
set方法中newValue的類型
subscript可以接受多個參數(shù),并且類型任意
*/
2-2、下標的細節(jié)
/// subscript可以沒有set方法, 但必須要有g(shù)et方法
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
get {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
}
/// 如果只有g(shù)et方法, 可以省略get
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
if index == 0{
return x
} else if index == 1{
return y
}
return 0
}
}
/// 可以設(shè)置參數(shù)標簽
class Point {
var x = 0.0, y = 0.0
subscript(index i: Int) -> Double {
if i == 0 {
return x
} else if i == 1{
return y
}
return 0
}
}
var p = Point()
p.y = 22.2
print(p[index: 1]) // 22.2
/// 下標可以是類型方法
class Sum {
static subscript(v1: Int, v2: Int) -> Int {
return v1 + v2
}
}
print(Sum[10, 20]) //30
2-3、結(jié)構(gòu)體甫煞、類作為返回值對比
class Point {
var x = 0, y = 0
}
class PointManager {
var point = Point()
subscript(index: Int) -> Point {
get { point }//只有g(shù)et方法, 給x/y賦值時會報錯
}
}
struct Point {//結(jié)構(gòu)體是值類型, 下標不寫set方法會報錯
var x = 0, y = 0
}
class PointManager {
var point = Point()
subscript(index: Int) -> Point{
set { point = newValue }
get { point }
}
}
var pm = PointManager()
pm[0].x = 11//本質(zhì): pm[0] = Point(x: 11, y: pm[0].y)
pm[0].y = 22//本質(zhì): pm[0] = Point(x: pm[0].x, y: 22)
// Point(x: 11, y:22)
print(pm[0])
//Point(x:11, y:22)
print(pm.point)
class Point {//類是引用類型, 下標不寫set方法不會報錯
var x = 0, y = 0
}
class PointManager {
var point = Point()
subscript(index: Int) -> Point {
get { point }//只有g(shù)et方法, 給x/y賦值時不會報錯
}
}
2-4菇曲、接收多個參數(shù)的下標
class Grid {
var data = [
[0,1,2],
[3,4,5],
[6,7,8]
]
subscript(row: Int, column: Int) -> Int {
set {
guard row >= 0 && row < 3 && column >= 0 && column < 3 else{
return
}
data[row][column] = newValue
}
get {
guard row >= 0 && row < 3 && column >=0 && column < 3 else{
return 0
}
return data[row][column]
}
}
}
var grid = Grid()
grid[0 ,1] = 77
grid[1, 2] = 88
grid[2, 0] = 99
print(grid.data)
3-1、繼承(Inheritance)
/// 值類型(枚舉抚吠、結(jié)構(gòu)體)不支持繼承, 只有類支持繼承
/// 沒有父類的類, 稱為基類
//swift并沒有像OC常潮、Java那樣的規(guī)定: 任何類最終都要繼承自某個基類
/// 子類可以重寫父類的下標、方法楷力、屬性, 重寫必須加上override關(guān)鍵字
3-2喊式、內(nèi)存結(jié)構(gòu)
class Animal {
var age = 0
}
class Dog : Animal {
var weight = 0
}
class ErHa : Dog {
var iq = 0
}
let a = Animal() //堆空間的對象, 堆空間的內(nèi)存是16的倍數(shù).
a.age = 10
// 32
print(Mems.size(ofRef: a))
/*
0x00000001000073e0 //這8個字節(jié)存放類型信息
0x0000000000000002 //這8個字節(jié)存放引用計數(shù)相關(guān)信息
0x000000000000000a //這8個字節(jié)存儲age
0x0000000000000000
*/
print(Mems.meStr(ofRef: a))
let d = Dog()
d.age = 10
d.weight = 20
// 32
print(Mems.size(ofRef: d))
/*
0x0000000100007490 //這8個字節(jié)存放類型信息
0x0000000000000002 //這8個字節(jié)存放引用計數(shù)相關(guān)信息
0x000000000000000a //這8個字節(jié)存儲age
0x0000000000000014 //這8個字節(jié)存放weight
*/
print(Mems.memStr(ofRef: d))
3-3、重寫實例方法萧朝、下標
class Animal {
func speak() {
print("Animal speak")
}
subscript(index: Int) -> Int {
return index
}
}
var anim: Animal
anim = Animal()
// Animal speak
anim.speak()
// 6
print(anim[6])
class Cat : Animal {
override func speak(){
super.speak()
print("Cat speak")
}
override subscript(index: Int) -> Int {
return super[index] + 1
}
}
anim = Cat()
// Animal speak
// Cat speak
anim.speak()
// 7
print(anim[6])
3-4垃帅、重寫類型方法、下標
/*
被class修飾的類型方法剪勿、下標, 允許被子類重寫
被static修飾的類型方法贸诚、下標, 不允許被子類重寫
*/
class Animal {
class func speak(){
print("Animal speak")
}
class subscript(index: Int) -> Int{
return index
}
}
class Cat : Animal {
override func speak(){
super.speak()
print("Cat speak")
}
override subscript(index: Int) -> Int {
return super[index] + 1
}
}
3-5、重寫屬性
/*
子類可以將父類的屬性(存儲厕吉、計算)重寫為計算屬性
子類不可以將父類屬性重寫為存儲屬性
只能重寫var屬性, 不能重寫let屬性
重寫時, 屬性名酱固、類型要一致
子類重寫后的屬性權(quán)限, 不能小于父類屬性的權(quán)限
如果父類屬性是只讀的, 那么子類重寫的屬性可以是只讀的、也可以是可讀寫的
如果父類屬性是可讀寫的, 那么子類重寫后的屬性也必須是可讀寫的
*/
class Aimal {
var age = 0 //存儲屬性
}
class Dog : Animal {
override var age = 0 //報錯, 不允許
var weight = 0
}
/// 重寫實例屬性
class Circle {
var radius: Int = 0.0 //直徑 存儲屬性
var diameter: Int { //半徑 計算屬性
set {
print("Circle setDiameter")
radius = newValue / 2
}
get {
pirnt("Cricle getDiameter")
return = radius * 2
}
}
}
var circle: Circle
circle = Circle()
circle.radius = 6
// Circle getDiameter
// 12
print(circle.diameter)
// Circle setDiameter
circle.diameter = 20
// 10
print(circle.radius)
class SunCircle : Circle {
override var radius: Int {//從父類繼承過來的存儲屬性都是有存儲空間的
set {
print("SubCircle setRadius")
super.radius = newValue > 0 ? newVaule : 0
}
get {
print("SubCricle getRadius")
return super.radius
}
}
override var diameter: Int {
set {
print("SubCircle setDiameter")
super.diameter = newVlue > 0 ? newValue : 0
}
get {
print("SubCircle getDiameter")
return super.diameter
}
}
}
var circle = SubCircle()
// SubCircle setRadius
circle.radius = 6
// SubCircle getDiameter
// Circle getDiameter
// SubCircle getRaius
// 12
print(circle.diameter)
// SubCircle setDiameter
// Circle setDiameter
// SubCircle setRadius
circle.diameter = 20
// SubCircle getRadius
// 10
print(circle.radius)
3-6头朱、重寫類型屬性
/*被class修飾的計算類型屬性, 可以被子類重寫; 存儲屬性不可以.
被static修飾的類型屬性(計算运悲、存儲),不可以被子類重寫
*/
class Circle {
static var radius: Int = 0
class var diameter: Int {
set {
print("Circle setDiameter")
radius = newValue / 2
}
get {
print("Circle getDiameter")
return radius * 2
}
}
}
class SubCircle : Circle {
override static var diameter: Int {
set {
print("SubCircle setDiameter")
super.diameter = newValue > 0 ? newValue : 0
}
get {
print("SubCircle getDiameter")
return super.diameter
}
}
}
3-7、屬性觀察器
/// 可以在子類中為父類屬性(除了只讀計算屬性项钮、let屬性)增加屬性觀察器
class Circle {
var radius: Int = 1
}
class SubCircle: Circle {
override var radius: Int {//此處不是重寫計算屬性, 只是添加屬性觀察器
willSet {
print("SubCircle willSetRadius", newValue)
}
didSet {
print("SubCircle didSetRadius", oldValue, radius)//訪問radius的存儲屬性
}
}
}
var circle = SubCircle()
// SubCircle willSetRadius 10
// SubCircle didSetRadius 1 10
circle.radius = 10
class Circle {
var radius: Int = 1 {//存儲屬性
willSet {
print("Circle willSetRadius", newValue)
}
didSet {
print("Circle didSetRadius", oldValue, radius)
}
}
}
class SubCircle: Circle {
override var radius: Int {
willSet {
print("SubCircle willSetRadius", newValue)
}
didSet {
print("SubCircle didSetRadius", oldValue, radius)
}
}
}
var circle = SubCircle()
// SubCricle willSetRadius 10
// Circle willSetRadius 10
// Circle didSetRadius 1 10
// SubCricle didSetRadius 1 10
circle.radius = 10
class Circle {
var radius: Int {// 實例計算屬性
set {
print("Circle setRadius", newValue)
}
get {
print("Circle getRadius")
return 20
}
}
}
class SubCircle: Circle {
override var radius: Int {
willSet {
print("SubCircle willSetRadius", newValue)
}
didSet {
print("SubCircle didSetRadius", oldValue, radius)
}
}
}
class Circle {
class var radius: Int {// 類型計算屬性
set {
print("Circle setRadius", newValue)
}
get {
print("Circle getRadius")
return 20
}
}
}
class SubCircle: Circle {
override static var radius: Int {
willSet {
print("SubCircle willSetRadius", newValue)
}
didSet {
print("SubCircle didSetRadius", oldValue, radius)
}
}
}
// Circle getRadius
// SubCircle willSetRadius 10
// Circle setRadius 10
// Circle getRadius
// SubCircle didSetRadius 20 20
SubCircle.radius = 10
第十章 匯編分析多態(tài)原理班眯、初始化、可選鏈
1-1烁巫、多態(tài)的實現(xiàn)原理
/*多態(tài)的實現(xiàn)原理:
1. OC: Runtime
2. C++: 虛表(虛函數(shù)表)
Swift中沒有runtime, Swift的實現(xiàn)跟虛表有點像, Swift中多態(tài)的實現(xiàn)原理
父類指針指向子類對象
*/
class Animal {
func speak() {
print("Animal speak")
}
func eat() {
print("Animal eat")
}
func sleep() {
print("Animal sleep")
}
}
class Dog : Animal {
func speak() {
print("Dog speak")
}
func eat() {
print("Dog eat")
}
func run() {
print("Dog run")
}
}
var anim: Animal
anim = Animal()
animal.speak()
animal.eat()
animal.sleep()
anim = Dog()//父類指針指向子類對象
anim.speak()
anim.eat()
anim.sleep()
//anim.run()
1-2署隘、多態(tài)的實現(xiàn)原理
/*Swift中多態(tài)的使用原理:
類似于C++的虛表,直接將這個對象將來要調(diào)用的函數(shù)內(nèi)存地址,提前放到類型信息那里, 這些類型信息肯定是編譯完就可以確定的調(diào)用誰,程序運行過程中就去那塊內(nèi)存中找就可以了
*/
2-1、初始化
/*
類亚隙、結(jié)構(gòu)體磁餐、枚舉都可以定義初始化器.
類有2種初始化器: 指定初始化器(designated Initializer)、便捷初始化器(convenience Initializer).
每個類至少有一個指定初始化器, 指定初始化器是類的主要初始化器.
默認初始化器總是類的指定初始化器.
類偏向于少量指定初始化器, 一個類通常只有一個指定初始化器.
初始化器的相互調(diào)用規(guī)則:
指定初始化器必須從它的直系父類調(diào)用指定初始化器;
便捷初始化器必須從相同的類里調(diào)用另一個初始化器;
便捷初始化器最終必須調(diào)用一個指定初始化器.
*/
/// 指定初始化器
init(parameters) {
statements
}
/// 便捷初始化器
convenience init(parameters) {
statements
}
class Size {
// init(){ } 默認初始化器
var width: Int = 0
var height: Int = 0
init(width: Int, height: Int){// 指定初始化器(主要初始化器)
self.width = width
self.height = height
}
convenience init(width: Int, height: Int){// 便捷初始化器
self.init()
self.width = width
self.height = height
}
}
var s = Size(width: 10, height: 20)//調(diào)用初始化器
2-2阿弃、便捷初始化器最終必須調(diào)用一個指定初始化器
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {// 指定初始化器(主要初始化器)
self.width = width
self.height = height
}
convenience init(width: Int) {
self.init(width: width, height: 0)
}
convenience init(height: Int) {
self.init(width: 0, height: height)
}
convenience init() {
self.init(width: 0, height: 0)
}
}
var s1 = Size(width: 10, height: 20)
var s2 = Size(width: 10)
var s3 = Size(height: 10)
var s4 = Size()
2-3诊霹、指定初始化器必須從它的直系父類調(diào)用指定初始化器
class Person {
var age: Int
init(age: Int) {
self.age = age
}
convenience init() {
self.init(age: 0)
}
}
class Student : Person {
var score: Int
init(age: Int, score: Int) {//自己的指定初始化器, 必須指定父類的指定初始化器
// 第一階段: 初始化所有存儲屬性
self.score = score//必須放在調(diào)用父類指定初始化器之前
super.init(age: age)
// 第二階段:實例個性化定制
self.age = age
}
convenience init() {//自己的便捷初始化器, 必須指定自己的指定初始化器
self.init(age: 0, score: 0)
}
convenience init(score: Int) {//自己的便捷初始化器, 必須指定自己的指定初始化器
self.init(age: 0, score: score)
}
}
2-4、兩段式初始化
/*
Swift在編碼安全方面是煞費苦心, 為了保證初始化過程的安全, 設(shè)定了兩段式初始化渣淳、安全檢查
兩段式初始化:
第1階段: 初始化所有存儲屬性
① 外層調(diào)用指定/便捷初始化器
② 分配內(nèi)存給實例, 但未初始化
③ 指定初始化器確保當(dāng)前類定義的存儲屬性都初始化
④ 指定初始化器調(diào)用父類的初始化器, 不斷向上調(diào)用, 形成初始化器鏈
第2階段: 設(shè)置新的存儲屬性值
① 從頂部初始化器往下, 鏈中的每一個指定初始化器都有機會進一步定制實例
② 初始化器現(xiàn)在能夠使用self(訪問脾还、修改它屬性, 調(diào)用它的實例方法等等)
③ 最終,鏈中任何便捷初始化器都有機會定制實例以及使用self
安全檢查:
指定初始化器必須保證在調(diào)用父類初始化器之前, 其所在類定義的所有存儲屬性都要初始化完成;
指定初始化器必須調(diào)用父類初始化器, 然后才能成為繼承的屬性設(shè)置新值;
便捷初始化器必須先調(diào)用同類中的其它初始化器, 然后再為任意屬性設(shè)置新值;
初始化器在第1階段初始化完成之前, 不能調(diào)用任何實例方法、不能讀取任何實例屬性的值,也不能引用self;
直到第1階段結(jié)束, 實例才算完全合法.
*/
2-5入愧、重寫父類的指定初始化器
/// 當(dāng)重寫父類的指定初始化器時,必須加上override(即使子類的實現(xiàn)是便捷初始化器)
class Person {
var age: Int
init(age: Int) {
self.age = age
}
}
class Student : Person {
var score: Int
init(age: Int, score: Int) {
self.score = score
super.init(age: age)
self.age = age
}
// override init(age: Int) { //重寫為指定初始化器
// self.score = 0 //把自己的初始化完成,再調(diào)用父類指定初始化器
// self.init(age: age)
// }
override convenience init(age: Int) {//重寫為便捷初始化器
self.init(age: age, score:0)//調(diào)用自己的指定初始化器
score = 15
}
}
/// 如果子類寫了一個匹配父類便捷初始化器的初始化器, 不用加上override
// 因為父類的便捷初始化器永遠不會通過子類直接調(diào)用, 因此, 嚴格來說, 子類無法重寫父類的便捷初始化器
class Person {
var age: Int
init(age: Int) {
self.age = age
}
convenience init() {
self.init(age: 0)
}
}
class Student : Person {
var score: Int
init(age: Int, score: Int) {
self.score = score
super.init(age: age)
self.age = age
}
init() {//此處不算重寫, 不用加override
self.score = 0
super.init(age: 0)
}
convenience init() {
self.init(age: 0, score: 0)
}
}
2-5鄙漏、自動繼承
/*
① 如果子類沒有自定義任何指定初始化器, 它會自動繼承父類所有的指定初始化器
② 如果子類提供了父類所有指定初始化器的實現(xiàn)(要么通過方式①繼承,要么重寫)
子類自動繼承所有的父類便捷初始化器
③ 就算子類添加了更多的便捷初始化器, 這些規(guī)則仍然適用
④ 子類以便捷初始化器的形式重寫父類的指定初始化器, 也可以作為滿足規(guī)則②的一部分
*/
class Person {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
init() {
self.age = 0
self.name = 0
}
convenience init(age: Int) {
self.init(age: age, name: "")
}
convenience init(name: String) {
self.init(age: 0, name: name)
}
}
class Student : Person {
var no: Int = 0
convenience init(no: Int) {
self.init()
}
}
// 只要我們自定義了指定初始化器, 那么父類的指定初始化器就不能繼承下來.
第十一章 init赛蔫、deinit、可選鏈泥张、協(xié)議呵恢、元類型
1-1、required
/*
用required修飾指定初始化器, 表明其所有子類都必須實現(xiàn)該初始化器(通過繼承或重寫實現(xiàn))
如果子類重寫required初始化器時也必須加上required, 不用加override
*/
class Preson {
required init() { }
init(age: Int)
}
class Student: Preson {
required init() {
super.init()
}
}
1-2媚创、屬性觀察器
/*
父類的屬性在它自己的初始化器中賦值不會觸發(fā)屬性觀察器,但在子類的初始化器中賦值會觸發(fā)屬性觀察器
*/
class Preson {
var age: Int {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, age)
}
}
init() {
self.age = 0
}
}
class Student: Person {
override init() {
super.init()
self.age = 1
}
}
// willSet 1
// didSet 0 1
var stu = Student()
1-3渗钉、可失敗初始化器
// 類、結(jié)構(gòu)體钞钙、枚舉都可以使用init?定義可失敗初始化器
class Person {
var name: String
init?(name: String){
if name.isEmpty {
return nil
}
self.name = name
}
}
//之前接觸過的可失敗初始化器
var num = Int("123")
public init?(_description: String)// Int類型初始化源碼
enum Answer : Int {
vase wrong, right
}
var an = Answer(rawValue: 1) //枚舉的初始化器
/*
不允許同時定義參數(shù)標簽鳄橘、參數(shù)個數(shù)、參數(shù)類型相同的可失敗初始化器和非可失敗初始化器
可以用init!定義隱式解包的可失敗初始化器
可失敗初始化器可調(diào)用非可失敗初始化器, 非可失敗初始化器調(diào)用可失敗初始化器需要進行解包
如果初始化器調(diào)用一個可失敗初始化器導(dǎo)致初始化失敗, 那么整個初始化過程都失敗, 并且之后的代碼都停止執(zhí)行
可以用一個非可失敗的初始化器重寫一個可失敗初始化器, 但反過來是不行的
*/
class Person {
var name: String
/*
init!(name: String){//定義隱式解包的可失敗初始化器
if name.isEmpty {
return nil
}
self.name = name
}
convenience init() {
self.init(name: "")
}
*/
/*
init?(name: String){
if name.isEmpty {
return nil
}
self.name = name
}
convenience init() {
self.init(name: "")!//需強制解包
}
*/
init?(name: String){
if name.isEmpty {
return nil
}
self.name = name
}
convenience init?() {
self.init(name: "")
self.name = "Jack"
// ....
}
}
class Student : Person {
override init(name: String){
// ....
}
}
var p1 = Person(name: "")
print(p1)
var p2 = Person(name: "Jack")
print(p2)
2-1芒炼、反初始化器(deinit)
/*
deinit叫做反初始化器, 類似于C++的析構(gòu)函數(shù)瘫怜、OC中的dealloc方法
當(dāng)類的實例對象被釋放內(nèi)存時,就會調(diào)用實例對象的deinit方法
*/
class Person {
deinit {
print("Preson對象銷毀了")
}
}
class Student : Person {
deinit {
print("Student對象銷毀了")
}
}
var stu: Student? = Student() //ARC
stu = nil
/*deinit不接受任何參數(shù), 不能寫小括號, 不能自行調(diào)用
父類的deinit能被子類繼承
子類的deinit實現(xiàn)執(zhí)行完畢后會調(diào)用父類的deinit
*/
3-1、可選鏈 ( Optional Chaining )
class Car { var price = 0 }
class Dog { var weight = 0 }
class Person {
var name: String = ""
var dog: Dog = Dog()
var car: Car? = Car()
func age() -> Int { 18 }
func eat() { print("Person eat") }
subscript(index: Int) -> Int { index }
}
var person: Person? = Person()
var age1 = person!.age() // Int
var age2 = person?.age() // Int?
var name = person?.name // String?
var index = person?[6] //Int?
func getName() -> String { "jack" }
// 如果person是nil, 不會調(diào)用getName()
person?.name = getName()
/*
如果可選項為nil, 調(diào)用方法本刽、下標鲸湃、屬性失敗, 結(jié)果為nil
如果可選項不為nil, 調(diào)用方法、下標子寓、屬性成功, 結(jié)果會被包裝成可選項
如果結(jié)果本來就是可選項, 不會進行再包裝
*/
if let _ = person?.eat() {// ()?
print("eat調(diào)用成功")
} else {
print("eat調(diào)用失敗")
}
var dog = person?.dog //Dog?
var weigth = person?.dog.weight // Int?
var price = person?.car?.price //Int?
/*多個? 可以鏈接在一起
如果鏈中任何一個節(jié)點是nil, 那么整個鏈就會調(diào)用失敗
*/
3-2暗挑、可選鏈
var scores = ["Jack": [86, 82, 84], "Rose": [79, 94, 81]]
scorces["Jack"]?[0] = 100
scorces["Rose"]?[2] += 10
scorces["Kate"]?[0] = 88
var num1: Int? = 5
num1? = 10 // Optional(10)
var num2: Int? = nil
num2? = 10 // nil
var dict: [String : (Int, Int) -> Int] = [//字符串作為key,函數(shù)作為value
"sum" : (+), // (+)這是語法糖,表示兩個Int相加,返回Int類型
"difference" : (-)// (-)這是語法糖,表示兩個Int相減,返回Int類型
]
var result = dict["sum"]?(10, 20) // Optional(30), Int?
4-1、協(xié)議 ( Protocol )
/*協(xié)議可以用來定義方法斜友、屬性炸裆、下標的聲明, 協(xié)議可以被枚舉、結(jié)構(gòu)體鲜屏、類遵守(多個協(xié)議之間用逗號隔開)*/
Protocol Drawable {
func draw()
var x: Int { get set }
var y: Int { get }
subscript(index: Int) -> Int { get set}
}
protocol Test1 { }
protocol Test2 { }
protocol Test3 { }
class TestClass : Test1, Test2, Test3 { }
/*
協(xié)議中定義方法時不能有默認參數(shù)值
默認情況下, 協(xié)議中定義的內(nèi)容必須全部實現(xiàn)
也有辦法辦到只實現(xiàn)部分內(nèi)容, 后面會談到
*/
4-2烹看、協(xié)議中的屬性
/*
協(xié)議中定義屬性必須用var關(guān)鍵字
實現(xiàn)協(xié)議的屬性權(quán)限要不小于協(xié)議中定義的屬性權(quán)限
協(xié)議定義get、set, 用var存儲屬性或get洛史、set計算屬性去實現(xiàn)
協(xié)議定義get, 用任何屬性都可以實現(xiàn)
*/
protocol Drawable {
func draw()
var x: Int { get set }
var y: Int { get }
subscript(index: Int) -> Int { get set }
}
class Person : Drawable{
var x: Int = 0 // 通過存儲屬性實現(xiàn)協(xié)議
let y: Int = 0
func draw() {
print("Person draw")
}
subscript(index: Int) -> Int {
set { }
get { index }
}
}
class Person : Drawable{
var x: Int {// 通過計算屬性實現(xiàn)協(xié)議
get { 0 }
set { }
}
var y: Int { 0 }
func draw() { print("Person draw") }
subscript(index: Int) -> Int {
set { }
get { index }
}
}
4-3惯殊、static、class
/*為了保證通用, 協(xié)議中必須用static定義類型方法虹菲、類型屬性靠胜、類型下標*/
protocol Drawable {
static func draw()
}
class Person1: Drawable {
class func draw() {
print("Person1 draw")
}
}
class Person2: Drawable {
static func draw() {
print("Person2 draw")
}
}
4-4掉瞳、mutating
/*只有將協(xié)議中的實例方法標記為mutating
才允許結(jié)構(gòu)體毕源、枚舉的具體實現(xiàn)修改自身內(nèi)存
類在實現(xiàn)方法時不用加mutating, 枚舉、結(jié)構(gòu)體才需要加mutating
*/
protocol Drawable {
mutating func draw()
}
class Size : Drawable {
var width: Int = 0
func draw() {
width = 10
}
}
struct Point : Drawable {
var x: Int = 0
mutating func draw() {
x = 10
}
}
4-5陕习、init
/*協(xié)議中還可以定義初始化器init
非final類實現(xiàn)時必須加上required
*/
protocol Drawable {
init(x: Int, y: Int) //定義初始化器
}
class Point : Drawable {// 非final類. 將來可能有子類
required init(x: Int, y: Int) { } //遵守協(xié)議實現(xiàn)方法, required表示它的所有子類都必須遵守這個協(xié)議
}
final class Size : Drawable {// final類, 規(guī)定將來沒有子類
init(x: Int, y: Int) { }
}
/*如果從協(xié)議實現(xiàn)的初始化器, 剛好是重寫了父類的指定初始化器
那么這個初始化必須同時加required霎褐、override
*/
protocol Livable {
init(age: Int)
}
class Person {
init (age: Int) { }
}
class Student : Person, Livable {
required override init(age: Int) {
super.init(age: age)
}
}
4-6、required
/*
用required修飾指定初始化器, 表明其所有子類都必須實現(xiàn)該初始化器(通過繼承或重寫實現(xiàn))
如果子類重寫了required初始化器, 也必須加上required, 不用加override
*/
class Person {
required init() { }
init (age: Int) { }
}
class Student : Person {
required init() {
super.init()
}
}
4-7该镣、init冻璃、init?、init!
/*
協(xié)議中定義的init?、init!, 可以用init省艳、init?娘纷、init!去實現(xiàn)
協(xié)議中定義的init, 可以用init、init!去實現(xiàn)
*/
protocol Livable {
init()
init?(age: Int)
init!(no: Int)
}
class Person : Livable {
required init() { }
// reuqired init!() { }
required init?(age: Int) { }
// required init!(age: Int) { }
// required init(age: Int) { }
required init!(no: Int) { }
// required init!(no: Int) { }
// required init(no: Int) { }
}
4-8跋炕、協(xié)議的繼承
/*一個協(xié)議可以繼承其他協(xié)議*/
protocol Runnable {
func run()
}
protocol Livable : Runnable {
func breath()
}
class Person : Livable {
func breath() { }
func run() { }
}
4-9赖晶、協(xié)議組合
/*協(xié)議組合, 可以包含1個類類型(最多1個), 枚舉和結(jié)構(gòu)體則不能存在其中*/
protocol Livable { }
protocol Runnable { }
class Person { }
// 接收Person或者其子類的實例
func fn0(obj: Person) { }
// 接收遵守Livable協(xié)議的實例
func fn1(obj: Livable) { }
// 接收同時遵守Livable、Runnable協(xié)議的實例
func fn2(obj: Livable & Runnable) { }
// 接收同時遵守Livable辐烂、Runnable協(xié)議, 并且是Person或其子類的實例
func fn3(obj: Person & Livable & Runnable) { }
typealias RealPerson = Person & Livable & Runnable
// 接收同時遵守Livable遏插、Runnable協(xié)議、并且是Person或者其子類的實例
func fn4(obj: RealPerson) { }
4-10纠修、CaseIterable
/*讓枚舉遵守CaseIterable協(xié)議, 可以實現(xiàn)遍歷枚舉值*/
enum Season : CaseIterable {
case spring, summer, autumn, winter
}
let seasons = Season.allCases
print(seasons.count) // 4
for season in seasons {
print(season)
}// spring summer autumn winter
4-11胳嘲、CustomStringConvertible
/*遵守CustomStringConvertible協(xié)議, 可以自定義實例的打印字符串*/
class Person : CustomStringConvertible {
var age: Int
var name: String
init(age: Int, name: String){
self.age = age
self.name = name
}
var description: String { //只讀的計算屬性
"age=\(age), name=\(name)"
}
}
var p = Person(age: 10, name: "Jack")
print(p) //age = 10, name=Jack
5-1、Any扣草、AnyObject
/*Swift提供了2種特殊的類型: Any了牛、AnyObject
Any : 可以代表任意類型 (枚舉、結(jié)構(gòu)體辰妙、類白魂、也包括函數(shù)類型)
AnyObject: 可以代表任意類類型 (在協(xié)議后面寫上: AnyObject代表只有類能遵守這個協(xié)議)
*/
protocol Runnable: AnyObject { }//AnyObject代表類類型的實例, 這樣寫代表只有類才能遵守這個協(xié)議
class Person : Runnable { }
struct Size : Runnable { }// 報錯
var stu: Any = 10
stu = "Jack"
stu = Student()
//創(chuàng)建1個能存放任意類型的數(shù)組
// var data = Array<Any>()
var data = [Any]()
data.append(1)
data.append(3.14)
data.append(Student())
data.append("Jack")
data.append({ 10 })
6-1、is上岗、as?福荸、as!、as
/*is用來判斷是否為某種類型, as用來做強制類型轉(zhuǎn)換*/
protocol Runnable { func run() }
class Person { }
class Student : Person, Runnable {
func run() {
print("Student run")
}
func study() {
print("Student study")
}
}
var stu: Any = 10
print(stu is Int) // true
stu = "Jack"
print(stu is String) //true
stu = Student()
print(stu is Person) //true
print(stu is Student) //true
print(stu is Runnable) //true
var stu: Any = 10
(stu as? Student)?.study() //沒有調(diào)用study
stu = Student()
(stu as? Student)?.study() // Student study
(stu as! Student).study() // Student study
(stu as? Runnable)?.run() // Student run
var data = [Any]()
data.append(Int("123") as Any)
print(data.count)
var d = 10 as Double
print(d) // 10.0
7-1肴掷、 X.self敬锐、 X.Type、AnyClass
/*
X.self是一個元類型(metadata)指針, metadata存放著類型相關(guān)信息;
X.self屬性X.Type類型;
*/
class Person{
}
var p:Person = Person()
var pType: Person.Type = Person.self // 元類型指針(取出Person堆空間的前8個字節(jié)出來, 指向Person元類型的地址值)
var anyType: AnyObject.Type = Person.self
anyType = Student.self
public typealias AnyClass = AnyObject.Type
var anyType2: AnyClass = Person.self
anyType2 = Student.self
var per = Person()
//type(of: x) 接收一個實例,能告訴你這個實例的類型是什么
var perType = type(of: per) // Person.self
print(Person.self == type(of: per)) // true
7-2呆瞻、元類型的應(yīng)用
class Animal { required init() { } }// 必須使用 required 修飾,確保子類一定有init
class Cat : Animal { }
class Dog : Animal { }
class Pig : Animal { }
func create(_ clses: [Animal.Type]) -> [Animal] {
var arr = [Animal]()
for cls in clses {
arr.append(cls.init()) //cls.init()調(diào)用初始化方法,返回實例
}
return arr
}
print(create([Cat.self, Dog.self, Pig.self]))
/*比如:
TabBarContorller
1: HomeVIewController
2: AboutViewController
// oc
NSArray *array = @[HomeViewController.class, AboutViewController.class];
for(cls in array){
[[cls alloc]init];
}
//swift
var array = @[HomeViewController.self, AboutViewController.self];
....
*/
import Foundation
class Person {
var age: Int = 0
}
class Student : Person {
var no: Int = 0
}
//runtime: class_getInstanceSize獲取類創(chuàng)建出實例對象需要多少內(nèi)存
print(class_getInstanceSize(Student.self)) // 32
print(class_getSuperclass(Student.self)!) //Person
print(class_getSuperclass(Person.self)!) //Swift._SwiftObject
/*從結(jié)果可以看得出,Swift還有個隱藏的基類: Swift._SwiftObject
可以參考Swift源碼: https://github.com/apple/swift/blob/master/stdlib/public/runtime/SwiftObject.h
*/
/*有些runtime的函數(shù)還是可以用在純Swift中*/
7-3台夺、Self
/*Self一般用作返回值類型, 限定返回值跟方法調(diào)用者必須是同一類型(也可以作為參數(shù)類型)*/
protocol Runnable {
func test () -> Self
}
class Person : Runnable {
required init() { }//如果Self用在類中, 要求返回時調(diào)用的初始化器是required的
func test() -> Self { type(of: self).init() }
}
class Student : Person { }
var p = Person()
print(p.test())// Test.Person
var stu = Student()
print(stu.test())// Test.Student
7-4、Person和Person.self
class Person {
static var age = 0 //類型屬性類型方法, 用類去調(diào)用
static func run() {}
}
Person.age = 10 //Person代表一個類名
Person.run()
Person.self.age = 10//等同于上面
Person.self.run()// Person.self代表元類型指針
/// 下面的實例方法效果都是一樣的
var p0 = Person()//init()
var p01 = type(of: p0).init()
var p1 = Person.self()//init()
var p2 = Person.init()//init()
var p3 = Person.self.init()//init()
// var pType0 = Person
var pType1 = Person.self
/*AnyClass相當(dāng)于AnyObject.Type*/
func test(_ cls: AnyClass) {
}
test(Person.self)
/*總結(jié):
共同點: 都能去訪問類型屬性和類型方法
區(qū)別: 一個叫元類類型, 類型不同
*/