iOS底層實(shí)例解析Swift閉包及OC閉包
基礎(chǔ)
Block是?個(gè)自包含的(捕獲了上下?的常量或者是變量的)函數(shù)代碼塊,可以在代碼中被傳遞和使用。
全局和嵌套函數(shù)實(shí)際上也是特殊的閉包,閉包采用如下三種形式之一:
- 全局函數(shù)是一個(gè)有名字但不會(huì)捕獲任何值的閉包
- 嵌套函數(shù)是一個(gè)有名字并可以捕獲其封閉函數(shù)域內(nèi)值的閉包
- 閉包表達(dá)式是一個(gè)利用輕量級(jí)語法所寫的可以捕獲其上下文中變量或常量值的匿名閉包
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修飾的變量
如下簡(jiǎn)單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ī):
- 手動(dòng)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)部存儲(chǔ)了變量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副本,或者簡(jiǎn)單地添加一個(gè)對(duì)現(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í)才會(huì)被copy,在堆上開辟內(nèi)存空間。
循環(huán)引用
解決方案
__weak + __strong
思路: 在block里短暫持有self的生命周期。(weak 自動(dòng)置空)
self.name = @"YK";
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
strongSelf.callFunc();
};
__block
思路: 值拷貝。(手動(dòng)置空)
我們有如下代碼,生成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變量地址,而不是上面講過的直接存儲(chǔ)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)體中存儲(chǔ)的變量也是__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; // 值拷貝存儲(chǔ)
};
總結(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í)就會(huì)將變量值傳入(值拷貝),如果捕獲了加__block 的外部變量,則會(huì)獲取到變量指針對(duì)應(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á)式擁有簡(jiǎn)潔的風(fēng)格,并鼓勵(lì)在常見場(chǎng)景中進(jìn)行語法優(yōu)化,主要優(yōu)化如下:
- 利用上下文推斷參數(shù)類型和返回值類型
- 隱式返回單表達(dá)式閉包(單表達(dá)式閉包可以省略
return關(guān)鍵字) - 參數(shù)名稱縮寫,可以用0,0,0,1表示
- 尾隨閉包語法:如果函數(shù)的最后一個(gè)參數(shù)是閉包,則閉包可以寫在形參小括號(hào)的外面。為了增強(qiáng)函數(shù)的可讀性。
- Swift 的閉包是一個(gè)引用類型,驗(yàn)證如下。我們知道Swift的引用類型在創(chuàng)建時(shí)都會(huì)調(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)修改變量值的話,原始變量也會(huì)被改變。
- 如果捕獲的是指針類型(
Class),無論是否用[],在閉包內(nèi)對(duì)該變量進(jìn)行修改,都會(huì)影響到原始變量
簡(jiǎn)單驗(yàn)證如下:
var variable = 10
let closure = { () -> () in
variable += 1
print("closure \(variable)")
}
closure() // closure 11
print(variable) // 11
可見直接獲取變量的話,會(huì)修改到原始值。
如果改成下面這樣會(huì)編譯報(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閉包的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS如何將UIButton中的圖片與文字上下對(duì)齊詳解
對(duì)于UIButton實(shí)現(xiàn)上顯示圖片,下顯示文字這個(gè)需求估計(jì)各位iOS開發(fā)者們都不陌生,所以下面這篇文章主要給大家介紹了關(guān)于iOS如何將UIButton中圖片與文字上下對(duì)齊的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-10-10
iOS基于UIScrollView實(shí)現(xiàn)滑動(dòng)引導(dǎo)頁
這篇文章主要為大家詳細(xì)介紹了iOS基于UIScrollView實(shí)現(xiàn)滑動(dòng)引導(dǎo)頁的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
iOS實(shí)現(xiàn)簡(jiǎn)單的頭部縮放功能
這篇文章主要介紹了iOS 簡(jiǎn)單的頭部縮放效果,頭部伴隨模糊效果放大縮小,并在一定位置時(shí)懸停充當(dāng)導(dǎo)航欄,本文給大家提供實(shí)現(xiàn)思路,需要的朋友可以參考下2018-08-08
IOS實(shí)現(xiàn)百度地圖自定義大頭針和氣泡樣式
這篇文章主要介紹了 IOS百度地圖自定義大頭針和氣泡的實(shí)例代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2016-12-12
iOS開發(fā)教程之登錄與訪客的邏輯實(shí)現(xiàn)
這篇文章主要給大家介紹了關(guān)于iOS開發(fā)教程之登錄與訪客的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04

