iOS底層實(shí)例解析Swift閉包及OC閉包
基礎(chǔ)
Block是?個(gè)自包含的(捕獲了上下?的常量或者是變量的)函數(shù)代碼塊,可以在代碼中被傳遞和使用。
全局和嵌套函數(shù)實(shí)際上也是特殊的閉包,閉包采用如下三種形式之一:
- 全局函數(shù)是一個(gè)有名字但不會捕獲任何值的閉包
- 嵌套函數(shù)是一個(gè)有名字并可以捕獲其封閉函數(shù)域內(nèi)值的閉包
- 閉包表達(dá)式是一個(gè)利用輕量級語法所寫的可以捕獲其上下文中變量或常量值的匿名閉包
OC-Block
分類
NSGlobalBlock
- 位于全局區(qū)
- 在Block內(nèi)部不使用外部變量,或者只使用靜態(tài)變量和全局變量
NSMallocBlock
- 位于堆區(qū)
- 被強(qiáng)持有
- 在Block內(nèi)部使用局部變量或OC屬性,可以賦值給強(qiáng)引用/copy修飾的變量
NSStackBlock
- 位于棧區(qū)
- 沒有被強(qiáng)持有
- 在Block內(nèi)部使用局部變量或OC屬性,不能賦值給強(qiáng)引用/copy修飾的變量
如下簡單demo code所示
int a = 10; // 局部變量 void(^Global)(void) = ^{ NSLog(@"Global"); }; void(^Malloc)(void) = ^{ NSLog(@"Malloc,%d",a); }; void(^__weak Stack)(void) = ^{ NSLog(@"Stack,%d",a); }; NSLog(@"%@",Global); // <__NSGlobalBlock__: 0x101aa80b0> NSLog(@"%@",Malloc); // <__NSMallocBlock__: 0x600003187900> NSLog(@"%@",Stack); // <__NSStackBlock__: 0x7ff7b12c22f0>
下面重點(diǎn)介紹堆Block。
NSMallocBlock
Block拷貝到堆Block的時(shí)機(jī):
- 手動copy
- Block作為返回值
- 被強(qiáng)引用/copy修飾
- 系統(tǒng)API包含using Block
所以總結(jié)一下堆Block判斷依據(jù):
- Block內(nèi)部有沒有使用外部變量
- 使用的變量類型?局部變量/OC屬性/全局變量/靜態(tài)變量
- 有沒有被強(qiáng)引用/copy修飾
源碼探究
我們創(chuàng)建一個(gè)捕獲了局部變量的block
#import <Foundation/Foundation.h> void test() { int a = 10; void(^Malloc)(void) = ^{ NSLog(@"%d",a); }; }
執(zhí)行clang -rewrite-objc main.m -o main.cpp
命令,查看main.cpp文件可以看到Malloc閉包的結(jié)構(gòu)如下。
struct __test_block_impl_0 { struct __block_impl impl; struct __test_block_desc_0* Desc; // 內(nèi)部存儲了變量a int a; /// 初始化函數(shù)。包含三個(gè)參數(shù) // - Parameters: /// - fp: 函數(shù)指針 /// - desc: 描述 /// - _a: flag __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; // 創(chuàng)建Malloc閉包,傳入?yún)?shù)如下 // fp: (void *)__test_block_func_0 // desc: &__test_block_desc_0_DATA // _a: 變量a的值(值拷貝) void(*Malloc)(void) = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a)); // __test_block_func_0實(shí)現(xiàn)如下 static void __test_block_func_0(struct __test_block_impl_0 *__cself) { int a = __cself->a; // bound by copy NSLog(···); }
打開llvm可以看到,該block原本是在棧上,調(diào)用了objc_retainBlock
方法,而在該方法中實(shí)際調(diào)用了_Block_copy
方法。
在Block.h的源碼中可以找到_Block_copy
方法,其官方注釋是“創(chuàng)建一個(gè)基于堆的Block副本,或者簡單地添加一個(gè)對現(xiàn)有Block的引用。”,從而將這個(gè)棧block拷貝到了堆上,下面我們根據(jù)該方法的源碼來探究一下堆Block的原理。(只截取重點(diǎn)代碼)
void *_Block_copy(const void *arg) { return _Block_copy_internal(arg, true); } static void *_Block_copy_internal(const void *arg, const bool wantsOne) { struct Block_layout *aBlock; ··· // 類型強(qiáng)轉(zhuǎn)為Block_layout aBlock = (struct Block_layout *)arg; ··· // Its a stack block. Make a copy. // 分配內(nèi)存 struct Block_layout *result = malloc(aBlock->descriptor->size); if (!result) return NULL; memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first // reset refcount result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1 // isa重新標(biāo)記為Malloc Block result->isa = _NSConcreteMallocBlock; _Block_call_copy_helper(result, aBlock); return result; }
Block底層結(jié)構(gòu)為Block_layout
struct Block_layout { void *isa; // isa指針 volatile int32_t flags; // contains ref count int32_t reserved; // 保留位 void (*invoke)(void *, ...); // call out funtion struct Block_descriptor_1 *descriptor; };
總結(jié):
Block在運(yùn)行時(shí)才會被copy,在堆上開辟內(nèi)存空間。
循環(huán)引用
解決方案
__weak
+ __strong
思路: 在block里短暫持有self的生命周期。(weak
自動置空)
self.name = @"YK"; __weak typeof(self) weakSelf = self; self.block = ^{ __strong typeof(self) strongSelf = weakSelf; strongSelf.callFunc(); };
__block
思路: 值拷貝。(手動置空)
我們有如下代碼,生成cpp文件看一下
#import <Foundation/Foundation.h> void test() { __block int a = 10; void(^Malloc)(void) = ^{ a++; NSLog(@"%d",a); }; Malloc(); }
// 可以看到傳入的第三個(gè)參數(shù),是__Block_byref_a_0結(jié)構(gòu)體類型的a變量地址,而不是上面講過的直接存儲int類型 void(*Malloc)(void) = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); // __test_block_impl_0結(jié)構(gòu)體中存儲的變量也是__Block_byref_a_0類型 struct __test_block_impl_0 { struct __block_impl impl; struct __test_block_desc_0* Desc; __Block_byref_a_0 *a; // by ref __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; // 初始化__Block_byref_a_0如下 __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0, (__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10}; // __Block_byref_a_0結(jié)構(gòu)體 struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; // 指針指向原始值 int __flags; int __size; int a; // 值拷貝存儲 };
總結(jié) __block
原理
- 創(chuàng)建
__Block_byref_a_0
結(jié)構(gòu)體 - 傳給block指針地址
- block內(nèi)修改的是與原始值同一片的內(nèi)存空間
注意點(diǎn)
根據(jù)上述分析我們可以得出結(jié)論,如果在OC的block中捕獲了沒有加__block
的外部變量,在編譯時(shí)就會將變量值傳入(值拷貝),如果捕獲了加__block
的外部變量,則會獲取到變量指針對應(yīng)的內(nèi)存空間的地址。代碼驗(yàn)證如下
int a = 1; __block int b = 2; void(^Malloc)(void) = ^{ NSLog(@"a,%d",a); NSLog(@"b,%d",b); }; a = 3; b = 4; Malloc(); // 輸出結(jié)果如下 // a,1 // b,4
Swift-Closure
- Swift 的閉包表達(dá)式擁有簡潔的風(fēng)格,并鼓勵(lì)在常見場景中進(jìn)行語法優(yōu)化,主要優(yōu)化如下:
- 利用上下文推斷參數(shù)類型和返回值類型
- 隱式返回單表達(dá)式閉包(單表達(dá)式閉包可以省略
return
關(guān)鍵字) - 參數(shù)名稱縮寫,可以用0,0,0,1表示
- 尾隨閉包語法:如果函數(shù)的最后一個(gè)參數(shù)是閉包,則閉包可以寫在形參小括號的外面。為了增強(qiáng)函數(shù)的可讀性。
- Swift 的閉包是一個(gè)引用類型,驗(yàn)證如下。我們知道Swift的引用類型在創(chuàng)建時(shí)都會調(diào)用
swift_allocObject
方法
// 未調(diào)用swift_allocObject let closure1 = { () -> () in print("closure1") } // 調(diào)用swift_allocObject let a = 10 let closure2 = { () -> () in print("closure2 \(a)") }
捕獲值
- 在閉包中如果通過
[variable1, variabla2]
的形式捕獲外部變量,捕獲到的變量為let
類型,即不可變 - 在閉包中如果直接捕獲外部變量,獲取的是指針,也就是說在閉包內(nèi)修改變量值的話,原始變量也會被改變。
- 如果捕獲的是指針類型(
Class
),無論是否用[],在閉包內(nèi)對該變量進(jìn)行修改,都會影響到原始變量
簡單驗(yàn)證如下:
var variable = 10 let closure = { () -> () in variable += 1 print("closure \(variable)") } closure() // closure 11 print(variable) // 11
可見直接獲取變量的話,會修改到原始值。
如果改成下面這樣會編譯報(bào)錯(cuò)”可變運(yùn)算符的左側(cè)不可變”
var variable = 10 let closure = { [variable] () -> () in variable += 1 print("closure \(variable)") } closure() print(variable)
捕獲指針類型驗(yàn)證
class YKClass { var name = "old" } let demoS = YKStruct() let demoC = YKClass() let closure1 = { [demoC] () -> () in demoC.name = "new" print("closure1 \(demoC.name)") } closure1() // closure1 new print(demoC.name) // new let closure2 = { () -> () in demoC.name = "new2" print("closure2 \(demoC.name)") } closure2() // closure2 new2 print(demoC.name) // new2
以上就是iOS底層實(shí)例解析Swift閉包及OC閉包的詳細(xì)內(nèi)容,更多關(guān)于iOS底層Swift OC閉包的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS基于UIScrollView實(shí)現(xiàn)滑動引導(dǎo)頁
這篇文章主要為大家詳細(xì)介紹了iOS基于UIScrollView實(shí)現(xiàn)滑動引導(dǎo)頁的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01IOS實(shí)現(xiàn)百度地圖自定義大頭針和氣泡樣式
這篇文章主要介紹了 IOS百度地圖自定義大頭針和氣泡的實(shí)例代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2016-12-12iOS開發(fā)教程之登錄與訪客的邏輯實(shí)現(xiàn)
這篇文章主要給大家介紹了關(guān)于iOS開發(fā)教程之登錄與訪客的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04