Swift語法知識匯總(上)

導(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ù)類型:
Swift常見數(shù)據(jù)類型.png
整數(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 3 %rax movq0x10, %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ū)別: 一個叫元類類型, 類型不同
*/
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末痴脾,一起剝皮案震驚了整個濱河市颤介,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赞赖,老刑警劉巖滚朵,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異前域,居然都是意外死亡辕近,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門匿垄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來移宅,“玉大人归粉,你說我怎么就攤上這事÷┓澹” “怎么了糠悼?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長浅乔。 經(jīng)常有香客問我绢掰,道長,這世上最難降的妖魔是什么童擎? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任滴劲,我火速辦了婚禮,結(jié)果婚禮上顾复,老公的妹妹穿的比我還像新娘班挖。我一直安慰自己,他們只是感情好芯砸,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布萧芙。 她就那樣靜靜地躺著,像睡著了一般假丧。 火紅的嫁衣襯著肌膚如雪双揪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天包帚,我揣著相機與錄音渔期,去河邊找鬼。 笑死渴邦,一個胖子當(dāng)著我的面吹牛疯趟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谋梭,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼信峻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瓮床?” 一聲冷哼從身側(cè)響起盹舞,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎隘庄,沒想到半個月后踢步,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡峭沦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年贾虽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吼鱼。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡蓬豁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出菇肃,到底是詐尸還是另有隱情地粪,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布琐谤,位于F島的核電站蟆技,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏斗忌。R本人自食惡果不足惜质礼,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望织阳。 院中可真熱鬧眶蕉,春花似錦、人聲如沸唧躲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弄痹。三九已至饭入,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肛真,已是汗流浹背谐丢。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蚓让,地道東北人庇谆。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像凭疮,于是被迫代替她去往敵國和親饭耳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348