一八堡、閉包
-
1.1、閉包表達(dá)式(Closure Expression)
在 Swift 里面可以通過函數(shù)func
定義一個函數(shù)聘芜,也可以通過閉包表達(dá)式定義一個函數(shù)func sum(_ v1:Int,_ v2:Int) -> Int{ return v1+v2 } sum(1,2) // 3
閉包的格式
{ 參數(shù)列表 -> 返回值類型 in 具體的代碼 }
閉包的具體舉例
var fn = { (_ v1:Int,_ v2:Int) -> Int in return v1+v2 } fn(2,3) // 5
提示:上面僅僅是給閉包定義了一個變量秕重,如果不寫變量和下面的意思一樣
{ (_ v1:Int,_ v2:Int) -> Int in return v1+v2 }(2,3)
-
1.2、閉包表達(dá)式的簡寫
基礎(chǔ)的閉包var fn = { (v1:Int,v2:Int) -> Int in return v1+v2 }
簡寫的代碼
func exec(v1:Int,v2:Int,fn:(Int,Int)->Int){ print(fn(v1,v2)) }
提示:傳進(jìn)去一個閉包
調(diào)用 exec 函數(shù)方式如下:等效厉膀,結(jié)果都是 20
方式一exec(1, 2, fn: fn)
方式二
exec(v1: 10, v2: 10) { (v3, v4) -> Int in return v3 + v4 }
方式三
exec(v1: 10, v2: 10, fn: { v3,v4 in return v3+v4 })
方式四
exec(v1: 10, v2: 10, fn: { v3,v4 in v3+v4 })
方式五
exec(v1: 10, v2: 10, fn: {$0 + $1})
方式六
exec(v1: 10, v2: 10, fn: +)
-
1.3溶耘、尾隨閉包
如果將一個很長的閉包表達(dá)式作為函數(shù)的最后一個實(shí)參,使用尾隨閉包可以增強(qiáng)函數(shù)的可讀性
-
尾隨閉包是一個被書寫在函數(shù)調(diào)用括號外面(后面)的閉包表達(dá)式
func exec(v1:Int,v2:Int,fn:(Int,Int)->Int){ print(fn(v1,v2)) } exec(v1: 6, v2: 7, fn: {$0 + $1})
結(jié)果:13服鹅,讓兩個參數(shù)相加凳兵,我們還可以:
-
、*
等等 -
如果閉包表達(dá)式是函數(shù)的唯一實(shí)參企软,而且使用了尾隨閉包的寫法庐扫,那就不需要再函數(shù)名后面寫圓括號
func exec(fn:(Int,Int)->Int){ print(fn(2,5)) }
調(diào)用方式如下,結(jié)果為 7
exec(fn: {$0 + $1}) exec(){$0 + $1} exec{$0 + $1}
-
1.4、數(shù)組的排序
func cmp(i1:Int,i2:Int) -> Bool { // 大的排到前面 return i1 > i2 } // 返回 false: i1 排在 i2 后面形庭,也就是 i1 的值小于 i2 // 返回 true: i1 排在 i2 前面铅辞,也就是 i1 的值大于 i2
-
1.5、忽略參數(shù)
func exec(fn:(Int,Int)->Int){ print(fn(1,5)) } exec{_,_ in 11} // 11
-
1.6萨醒、閉包(Closure):的實(shí)質(zhì)是一段有具體功能的代碼塊斟珊。
- 閉包:一個函數(shù)和他捕獲的變量/常量環(huán)境組合起來,稱為閉包富纸。閉包的核心是在其使用的局部變量/常量 會被額外的復(fù)制或者引用囤踩,使這些變量脫離其作用域后依然有效。
一般指定義在函數(shù)內(nèi)部的函數(shù)
-
一般它捕獲的是外層函數(shù)的局部變量/常量
// 返回的 plus 與 num 形成了閉包 func getFn() -> Fn{ var num = 0 func plus(_ i:Int) -> Int{ num += I return num } return plus } var fn1 = getFn() var fn2 = getFn() fn1(1) fn2(2)
- 可以把閉包想象成一個類的實(shí)例對象
存儲在堆空間
捕獲的局部變量/常量就是對象的成員(存儲屬性)
-
組成閉包的函數(shù)就是類內(nèi)部定義的方法
class Closure{ var num = 2 func plus(_ i:Int) -> Int { num += I return num } } var cs1 = Closure() var cs2 = Closure() cs1.plus(2) // 4 cs2.plus(2) // 4 cs1.plus(3) // 7 cs2.plus(3) // 7
- 閉包:一個函數(shù)和他捕獲的變量/常量環(huán)境組合起來,稱為閉包富纸。閉包的核心是在其使用的局部變量/常量 會被額外的復(fù)制或者引用囤踩,使這些變量脫離其作用域后依然有效。
-
1.7晓褪、自動閉包
自動閉包的條件:自動閉包參數(shù)使用有嚴(yán)格的條件是首先此閉包不能有參數(shù)堵漱,其次在調(diào)用函數(shù)傳參的時,此閉包的實(shí)現(xiàn)只能由一句表達(dá)式組成涣仿,閉包的返回值即為此表達(dá)式的值勤庐,自動閉包由@autoclosure
來聲明,如下 例三-
例一:如果第1個數(shù)大于0好港,返回第一個數(shù)埃元。否則返回第2個數(shù)
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int { return v1 > 0 ? v1 : v2 } getFirstPositive(10, 20)
-
例二:改成函數(shù)類型的參數(shù),可以讓v2延遲加載
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int? { return v1 > 0 ? v1 : v2() } getFirstPositive(-4) { 20 }
-
例三:為了避免與期望沖突媚狰,使用了@autoclosure的地方最好明確注釋清楚:這個值會被推遲執(zhí)行
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? { return v1 > 0 ? v1 : v2() } getFirstPositive(-4, 20)
-
結(jié)論
-
@autoclosure
會自動將 20 封裝成閉包 { 20 } -
@autoclosure
只支持() -> T
格式的參數(shù)@autoclosure
并非只支持最后1個參數(shù) - 空合并運(yùn)算符
??
使用了@autoclosure
技術(shù) - 有
@autoclosure
岛杀、無@autoclosure
,構(gòu)成了 函數(shù)重載
-
-
-
1.8崭孤、逃逸閉包和非逃逸閉包
- 逃逸閉包:是指函數(shù)內(nèi)的閉包在函數(shù)執(zhí)行結(jié)束后在函數(shù)外依然可以使用
- 非逃逸閉包:是指在函數(shù)的聲明周期結(jié)束后类嗤,閉包也將會被銷毀。換句話說辨宠,非逃逸閉包只能在函數(shù)內(nèi)部使用遗锣,在函數(shù)外部不能使用。默認(rèn)情況下函數(shù)中的閉包都為非逃逸閉包嗤形,這樣做的優(yōu)點(diǎn)是可以提高代碼的性能精偿,節(jié)省內(nèi)存消耗,開發(fā)者可以根據(jù)實(shí)際需求將閉包參數(shù)聲明為逃逸閉包赋兵。
提示:
非逃逸閉包也不可以作為返回值返回笔咽,如果這么做,編譯器會拋出一個錯誤霹期。
-
將閉包聲明為非逃逸型叶组,需要使用
@noescape
修飾。需要注意的是历造,在最新的Xcode版本里面已經(jīng)不需要再使用甩十,參數(shù)默認(rèn)都是非逃逸的船庇,如下代碼只有一個閉包的函數(shù),將此閉包聲明為非逃逸的侣监,此閉包既不可最為返回值也不可賦值給外部變量 在xcode10.1中會有警告鸭轮,這個關(guān)鍵字可以忽略
逃逸類型的閉包通常用于異步操作中橄霉,例如一個請求完成后要執(zhí)行閉包回調(diào)窃爷,需要使用逃逸類型。
二酪劫、屬性
-
2.1吞鸭、Swift 里面跟實(shí)例相關(guān)的屬性可以分為2大類
-
存儲屬性(Sored Property)
- 類似于成員變量這個概念
- 存儲在實(shí)例的內(nèi)存中
- 結(jié)構(gòu)體和類可以定義存儲屬性
- 枚舉不可以定義存儲屬性
提示:
-
關(guān)于存儲屬性寺董,在Swift里面有個明確的規(guī)定覆糟,在創(chuàng)建類和結(jié)構(gòu)體的時候,必須為所有的存儲屬性設(shè)置一個合適的初始值遮咖。
struct Circle { /// 存儲屬性 var radius:Double } var circle = Circle(radius: 2)
-
可以在初始化器里面為存儲屬性設(shè)置一個初始值
struct Circle { /// 存儲屬性 var radius:Double init () { radius = 2 } } var circle = Circle()
-
可以分配一個默認(rèn)的屬性值作為屬性定義的一部分滩字。如在定義屬性的時候直接給一個 值,
var radius:Int = 2
struct Circle { /// 存儲屬性 var radius:Double = 2.0 } var circle = Circle()
-
計(jì)算屬性(Computed Property)
- 本質(zhì)就是方法(函數(shù))
- 不占用實(shí)例的內(nèi)存
- 枚舉御吞、結(jié)構(gòu)體麦箍、類 都可以定義計(jì)算屬性
提示:
- set傳入的新值默認(rèn)叫做 newValue,也可以自定義
- 定義計(jì)算屬性只能用 var ,不能用 let(代表常量,值是一成不變的)
- 計(jì)算屬性的值是可能發(fā)生變化的(即使是只讀計(jì)算屬性)
- 有
set
方法的話必須有get
方法陶珠,有get
方法可以沒有set
方法
-
存儲屬性(Sored Property)
-
2.2挟裂、以結(jié)構(gòu)體舉例
struct Circle { /// 存儲屬性 var radius:Double /// 計(jì)算屬性 var diameter:Double { set { radius = newValue / 2.0 } get { radius * 2.0 } } } var circle = Circle(radius: 2) circle.diameter = 20; //結(jié)果是 8 個字節(jié) print("占用的內(nèi)存大小=\(MemoryLayout.stride(ofValue: circle))")
提示:計(jì)算屬性是不占內(nèi)存的,原因是它的set 和 get 類似于兩個函數(shù)揍诽,如下代碼诀蓉,由此可以看出是不占用內(nèi)存的
func setDiameter (newValue:Double) { radius = newValue / 2.0 } func getDiameter (newValue:Double) -> Double { radius * 2.0 }
-
2.3、枚舉原始值 rawvalue 原理
enum TestEnum : Int { case test1 = 1, test2 = 2, test3 = 3 var rawValue: Int { switch self { case .test1: return 8 case .test2: return 8 case .test3: return 10 } } } print(TestEnum.test3.rawValue) // 結(jié)果 10
枚舉原始值
rawValue
的本質(zhì)是:只讀計(jì)算屬性,查看匯編里面只有get
方法 -
2.4暑脆、延遲存儲屬性(Lazy Stored Property)
-
使用
lazy
可以定義一個延遲存儲屬性渠啤,在第一次用到屬性的時候才會進(jìn)行初始化class Car { init() { print("Car init") } func run() { print("Car is running!") } } class Person { lazy var car = Car() init() { print("Person init") } func goOut() { car.run() } } // 使用 let person = Person() print("------") person.goOut()
打印結(jié)果如下:我們可以看到在
let person = Person()
執(zhí)行的時候lazy var car = Car()
沒有立馬執(zhí)行,而是在用到 car 的時候才被調(diào)用的添吗,由此可以看出在存儲屬性前面加上 lazy 是不會立馬執(zhí)行的沥曹,在用到的時候才會被執(zhí)行Person init ------ Car init Car is running!
提示
-
lazy
屬性必須是var
,不能是let -
let
必須在實(shí)例的初始化方法完成之前就擁有值 - 如果 多條線程 同時第一次訪問
lazy
屬性碟联,是無法保證屬性只被初始化1次妓美,這個是線程不安全的
lazy var image:UIImage = { //圖片url let imgUrl = "http://www.uw3c.com/images/index/logo_monkey.png"/ let url : NSURL = NSURL(string:imgUrl)!// 轉(zhuǎn)換網(wǎng)絡(luò)URL let data : NSData = NSData(contentsOf:url as URL)! return UIImage(data: data as Data)! }()
-
-
-
2.5、延遲屬性的注意點(diǎn)
-
當(dāng)一個結(jié)構(gòu)體包含一個延遲屬性的時候鲤孵,只有 var 才能訪問延遲屬性部脚,因?yàn)檠舆t屬性初始化時需要改變結(jié)構(gòu)體的內(nèi)存
解釋:如果實(shí)例定義為 let,那么節(jié)結(jié)構(gòu)體的內(nèi)存就固定了裤纹,不能被修改委刘,那么延遲屬性的本質(zhì)是修改結(jié)構(gòu)體的內(nèi)存丧没,編譯器就會直接報(bào)錯
struct Point1 { var x = 1 var y = 2 lazy var z = 3 } // 使用 let point = Point1() point.z
-
-
2.6、屬性觀察器
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.radius = 10 print(circle.radius) 打印結(jié)果如下 Circle init willSet 10.0 didSet 1.0 10.0 10.0
提示:
-
willSet
會傳遞新值锡移,默認(rèn)值叫newValue
-
didSet
會傳遞舊值呕童,默認(rèn)值叫oldValue
- 在初始化器中設(shè)置屬性值不會觸發(fā)
willSet
和didSet
- 在屬性定義時設(shè)置初始值也不會觸發(fā)
willSet
和didSet
-
-
2.7、全局變量 和 局部變量
屬性觀察器淆珊、計(jì)算屬性的功能夺饲,同樣可以應(yīng)用在全局變量,局部變量身上
-
全局變量
var num:Int { get { return 10 } set { print("setNum",newValue) } } num = 12 // setNum 12 print(num) // 10
-
局部變量
fun test() { var age:Int = 10 { willSet { print("willSet",newValue) } didSet { print("didSet",oldValue,radius) } } age = 11 // 打邮┓:willSet 11 // didSet 10 11 } // 調(diào)用方法 test()
-
2.8往声、inout 本質(zhì)的探究
struct Shape { var width:Int var side:Int { willSet { print("willSet - side",newValue) } didSet { print("willSet - side",oldValue,side) } } var girth:Int { set { width = newValue / side print("setGirth",newValue) } get { print("getGirth") return width * side } } func show() -> Void { print("width = \(width), side = \(side), girth = \(girth)") } } func test(_ num: inout Int) -> Void { 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() 打印結(jié)果是 getGirth width = 20,side = 4,width = 80 ---------- willSet - side 20 willSet - side 4 20 getGirth width = 20,side = 20,width = 400 ---------- getGirth setGirth 20 getGirth width = 1,side = 20,width = 20
- 如果實(shí)參有物理內(nèi)存地址,且沒有設(shè)置屬性觀察器:直接將實(shí)參的內(nèi)存地址傳入函數(shù)(實(shí)參進(jìn)行引用傳遞)
- 如果實(shí)參是計(jì)算屬性 或者 設(shè)置了屬性觀察器:采取了 Copy In Copy Out 的做法
- 調(diào)用函數(shù)時戳吝,先復(fù)制實(shí)參的值浩销,產(chǎn)生副本 【get】
- 將副本的內(nèi)存地址傳入函數(shù) (副本進(jìn)行引用傳遞),在函數(shù)的內(nèi)部可以修改副本的值
- 函數(shù)返回后听哭,再將副本的值覆蓋實(shí)參的值 【set】
- 總結(jié):inout 的本質(zhì)是引用傳遞 (地址傳遞)
-
2.9慢洋、類型屬性(Type Property)
嚴(yán)格來說屬性可以分為:實(shí)例屬性 和 類型屬性-
實(shí)例屬性(Instance Property):只能通過實(shí)例去訪問
- 存儲實(shí)例屬性(Stored Instance Property):存儲在實(shí)例內(nèi)存中,每個實(shí)例都有一份內(nèi)存
- 計(jì)算實(shí)例屬性(Computed Instance Property)
-
類型屬性(Type Property):只能通過類去訪問
存儲類屬性(Stored Instance Property):整個程序運(yùn)行中陆盘,就只有一份內(nèi)存(類似于全局變量)
計(jì)算類屬性(Computed Instance Property)
-
可以通過
static
定義類型屬性普筹,如果是類可以使用關(guān)鍵字class
struct Car { static var count = 0 init() { Car.count += 1 } } let c1 = car() let c2 = car() let c3 = car() print(Car.count) // 結(jié)果是 3
-
總結(jié):類屬性細(xì)節(jié)
- 不同于 存儲實(shí)例屬性,你必須給 存儲類型屬性 設(shè)定初始值隘马,因?yàn)轭愋蜎]有像實(shí)例那樣的init初始化器來初始化存儲屬性
- 存儲類型屬性默認(rèn)就是lazy太防,會在第一次使用的時候才初始化,就算被多個線程同時訪問酸员,保證只會初始化一次
- 存儲類型屬性可以是 let
提示:枚舉類型也可以定義類型屬性(存儲類型屬性蜒车、計(jì)算類型屬性)
-
類存儲屬性的使用-- 單利
class Person { public static let share = Person() // 類存儲屬性,在這個程序中只有一份內(nèi)存 private init() {} }
-
三沸呐、方法
-
3.1醇王、類、枚舉崭添、結(jié)構(gòu)體 都可以定義 實(shí)例方法寓娩、類型方法
實(shí)例方法(Instance Method):通過實(shí)例對象調(diào)用
-
類型方法(Type Method):通過 類型調(diào)用,用 class/static關(guān)鍵字定義
struct Car { static var count = 0 init() { Car.count += 1 } // 在函數(shù)(方法) 前面加上 class/static關(guān)鍵字 就可以變成類方法呼渣,用類名來調(diào)用 static func getCount() -> Int { count } } let c1 = car() let c2 = car() let c3 = car() print(Car.getCount()) // 結(jié)果是 3
提示:self
- 在實(shí)例方法里面代表實(shí)例對象
- 在類方法里面代表類型
上代碼代碼static func getCount()
里面的count
等價于self.count
棘伴、Car.self.count
、Car.count
-
3.2屁置、mutating 的使用
結(jié)構(gòu)體和枚舉都是值類型焊夸,默認(rèn)情況下,值類型的屬性不能被自身的實(shí)例方法修改
-
解決辦法:在 func 前面加上 mutaing 可以允許這種修改行為
struct Point { var x = 0.0,y = 0.0 mutating func moveBy(deltaX:Double,deltaY:Double) { x += deltaX y += deltaY // 等效于上面的代碼 //self = Point2(x: x + deltaX, y: y + deltaY) } } enum StateSwitch { case low,middle,high mutating func next() { switch self { case .low: self = .middle case .middle: self = .high case .high: self = .low } } }
-
3.3蓝角、@discardableResult 消除方法返回值沒使用的警告阱穗,如下
struct Point { var x = 0.0,y = 0.0 @discardableResult mutating func moveBy(deltaX:Double,deltaY:Double) -> Double { x += deltaX y += deltaY return x + y } } var point = Point() point.moveBy(deltaX: 2.0, deltaY: 2.0)
提示
:如果 函數(shù)moveBy
不加@discardableResult
饭冬,point.moveBy(deltaX: 2.0, deltaY: 2.0) 會提提示Result of call to 'moveBy(deltaX:deltaY:)' is unused
四、下標(biāo)(subscrip)
4.1揪阶、使用 subscrip 可以給任意類型(枚舉昌抠、結(jié)構(gòu)體、類)增加下標(biāo)功能鲁僚,有些地方翻譯為:下標(biāo)腳本
-
4.2炊苫、subscrip的語法類似于實(shí)例方法、計(jì)算屬性冰沙,本質(zhì)就是方法 (函數(shù))
struct 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 = Point5() p[0] = 1.1 p[1] = 2.2 print(p.x) // 1.1 print(p.y) // 2.2 print(p[0]) // 1.1 print(p[1]) // 2.2
提示:
- subscript 中定義的返回值類型決定了:get 方法的返回值 和 set 方法中 newValue 的類型
- subscript 可以接受多個參數(shù)侨艾,并且類型任意
-
4.3、subscript 下標(biāo)細(xì)節(jié)
-
細(xì)節(jié)一:subscript 下標(biāo)可以沒有 set 方法拓挥,但必須有 get 方法唠梨,如果只有g(shù)et方法可以把
get
關(guān)鍵字去掉,如下struct 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 } }
-
細(xì)節(jié)二:可以設(shè)置參數(shù)標(biāo)簽
struct 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 = Point6() p.y = 22.0 // 訪問的時候必須加上參數(shù)標(biāo)簽 print(p[index: 1])
-
細(xì)節(jié)三:下標(biāo)可以是 類型方法
class Sum { static subscript(v1:Int,v2:Int) -> Int { return v1 + v2 } } print(Sum[10,20]) // 打印結(jié)果是 30
-
-
4.4撞叽、接收多個參數(shù)的下標(biāo)
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 // 第1行第2列 grid[1, 2] = 88 // 第2行第3列 grid[2, 0] = 99 // 第3行第1列 print(grid.data)
打印結(jié)果如下:
[[0, 77, 2], [3, 4, 88], [99, 7, 8]]