iOS Blocks 小結(jié)

本文介紹一下 iOS 中 Block 相關(guān)內(nèi)容,總結(jié) Block 相關(guān)的使用方法和注意事項(xiàng)。

Block 概述

Block 也被稱(chēng)作閉包,是不帶有名稱(chēng)的函數(shù)肤京,匿名函數(shù)。相當(dāng)于是一個(gè)代碼塊茅特,把想要執(zhí)行的代碼封裝在代碼塊里忘分,等需要的時(shí)候調(diào)用。

Block 表達(dá)式語(yǔ)法:

返回值類(lèi)型(^變量名)(參數(shù)) = ^返回值類(lèi)型(參數(shù)){ 表達(dá)式 }
int (^myBlock1)(int) = ^(int num) { return num + 1; };
void (^myBlock2)(void) = ^(void){ NSLog(@"無(wú)參數(shù)白修,無(wú)返回值"); };


Block 常見(jiàn)用法

在 OC 中經(jīng)常會(huì)用到 Block妒峦。使用 Block 方便快捷,集中代碼塊熬荆,適用于輕便舟山、簡(jiǎn)潔的回調(diào),如網(wǎng)絡(luò)傳輸?shù)嚷笨摇O旅娼榻B幾種常見(jiàn)的用法:

1累盗、聲明為屬性

可將 block 聲明為某個(gè)類(lèi)的屬性,在別的地方初始化賦值之后突琳,等待觸發(fā)回調(diào)傳值等操作若债。

@property (nonatomic, copy) void(^blockName)(NSInteger type);
someObj.blockName = ^(NSInteger type) {
    //...
};

//如要是覺(jué)得 block 語(yǔ)法書(shū)寫(xiě)別扭、不友好拆融,也可使用 typedef 定義一個(gè) block 類(lèi)型蠢琳,方便使用:
typedef void(^BlockName)(NSInteger);
@property (nonatomic, copy) BlockName blockName;
2啊终、作為方法參數(shù)調(diào)用

當(dāng)有時(shí)調(diào)用一個(gè)耗時(shí)的方法處理,不會(huì)立即返回傲须,需要時(shí)間進(jìn)行處理蓝牲,當(dāng)處理完成后告知調(diào)用者處理完畢,可進(jìn)行后面的操作泰讽,在這樣的情景下就可使用 block例衍,例如網(wǎng)絡(luò)請(qǐng)求回調(diào)。

- (void)doSomethingParameters:(id)parameters completion:(void (^)(NSInteger type))completion {
    //...
    completion(1);
}

[self doSomethingParameters:nil completion:^(NSInteger type) {
    //處理完成已卸,這里可進(jìn)行后續(xù)操作...
}];


Block 的實(shí)質(zhì)

Block 其實(shí)是作為 C語(yǔ)言的代碼來(lái)進(jìn)行處理的佛玄,通過(guò)編譯器將 Block 語(yǔ)法轉(zhuǎn)換為相關(guān)的 C語(yǔ)言代碼,然后進(jìn)行編譯執(zhí)行累澡∶吻溃可以通過(guò) clang 來(lái)將 OC 代碼轉(zhuǎn)換為 C/C++ 代碼,執(zhí)行下面命令:

clang -rewrite-objc main.m

然后生成 main.cpp 文件愧哟“路裕可以看到,簡(jiǎn)單的幾行代碼翅雏,轉(zhuǎn)化為一堆 C語(yǔ)言代碼:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^blk)(void) = ^(){ NSLog(@"Block !"); };
        blk();
    }
    return 0;
}

上面的代碼其實(shí)就是圈驼,將 Block 的匿名函數(shù)作為 C語(yǔ)言的函數(shù)來(lái)處理。
就是使用函數(shù)指針調(diào)用函數(shù)望几,將函數(shù) __main_block_func_0(__cself) 賦值給 impl.FuncPtr, 然后調(diào)用 blk->impl.FuncPtr(blk)萤厅,參數(shù) __cself 就是指向 blk 自身的指針橄抹。

Block 就是 OC 對(duì)象

OC 中由類(lèi)生成對(duì)象,就是由該類(lèi)生成對(duì)象的各個(gè)結(jié)構(gòu)體惕味,通過(guò)對(duì)象的成員變量 isa 指針指向該類(lèi)的結(jié)構(gòu)體實(shí)例 objc_class 繼承自 objc_object楼誓,該結(jié)構(gòu)體實(shí)例持有聲明的成員變量、方法的名稱(chēng)名挥、方法的實(shí)現(xiàn)(函數(shù)指針)疟羹、屬性以及父類(lèi)的指針。具體內(nèi)容請(qǐng)參考之前的文章 Objective-C對(duì)象解析

Block 就是 OC 對(duì)象禀倔。 __main_block_impl_0 結(jié)構(gòu)體相當(dāng)于榄融,基于 objc_object 結(jié)構(gòu)體的 OC 對(duì)象的結(jié)構(gòu)體。
值得注意的是救湖,impl.isa = &_NSConcreteStackBlock;
_NSConcreteStackBlock 相當(dāng)于一個(gè)結(jié)構(gòu)體實(shí)例愧杯,將 Block 作為 OC 對(duì)象處理時(shí),關(guān)于該類(lèi)的信息就放在 _NSConcreteStackBlock 中鞋既。

Block 截獲變量

如下面例子力九,定義一個(gè) Block耍铜,在代碼塊里打印變量 a,然后修改后調(diào)用 Block 打印出的變量 a 的值不變跌前。

NSInteger val = 10;
void (^blockName)(void) = ^{
    NSLog(@"val = %ld", val);
};
blockName(); // val = 10
val = 20;
blockName(); // val = 10

為何修改后打印 a 的值不變棕兼,因?yàn)?Block 語(yǔ)法的表達(dá)式使用的是它之前聲明的局部變量 a。Block 表達(dá)式截獲所使用的局部變量的值抵乓,保存了該變量的瞬時(shí)值程储。所以在第二次執(zhí)行 Block 表達(dá)式時(shí),即使已經(jīng)改變了局部變量 a 的值臂寝,也不會(huì)影響 Block 表達(dá)式在執(zhí)行時(shí)所保存的局部變量的瞬時(shí)值章鲤。
這就是 Block 變量截獲局部變量值的特性。

通過(guò)上面的方法轉(zhuǎn)換為 C語(yǔ)言咆贬,可以看到在 Block 中使用外面的變量败徊,變量被作為成員變量追加到 __main_block_impl_0 結(jié)構(gòu)體中了。

從上圖中可看到掏缎,截獲自動(dòng)變量皱蹦,就是在表達(dá)式中所使用的變量被保存到 Block 的結(jié)構(gòu)體中 __cself->val,所以在 Block 之外修改變量 val 的值眷蜈,Block 表達(dá)式里面的 val 值不變沪哺。


另外,在使用 C語(yǔ)言數(shù)組時(shí)要注意酌儒,截獲自動(dòng)變量的方法沒(méi)有實(shí)現(xiàn)對(duì) C 數(shù)組的截獲辜妓,可使用指針來(lái)解決。

如上圖所示忌怎,為何會(huì)報(bào)錯(cuò)呢籍滴?
通過(guò)上面截獲變量的例子可知,變量被賦值給 Block 結(jié)構(gòu)體中的成員變量榴啸,因?yàn)?C語(yǔ)言數(shù)組類(lèi)型變量不能賦值給數(shù)組類(lèi)型變量:char a[10]={'a'}; char b[10]=a; 這樣編譯不能通過(guò)孽惰,所以會(huì)報(bào)錯(cuò)。

__block 說(shuō)明符

在 Block 中只能使用保存的局部變量的瞬時(shí)值鸥印,并不能直接對(duì)其進(jìn)行修改勋功,想要修改需要在局部變量前加 __block 修飾。

__block NSInteger val = 10;
void (^blockName)(void) = ^{
    val = 30;
    NSLog(@"val = %ld", val);
};
blockName(); // val = 30

__block 類(lèi)似于 static库说、auto狂鞋,用于指定將變量值設(shè)置到哪個(gè)存儲(chǔ)域中。auto 表示作為自動(dòng)變量存儲(chǔ)在棧中璃弄,static 表示作為靜態(tài)變量存儲(chǔ)在數(shù)據(jù)區(qū)中要销。

繼續(xù)轉(zhuǎn)換為 C語(yǔ)言來(lái)看一下,如下圖:


可看到夏块,使用在變量前加上 __block 說(shuō)明符后疏咐,代碼增加了很多纤掸,__block 變量變成了__Block_byref_val_0 結(jié)構(gòu)體類(lèi)型的變量。

struct __Block_byref_val_0 {
  void *__isa;
  __Block_byref_val_0 *__forwarding;
  int __flags;
  int __size;
  NSInteger val;
};

__Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 30;

其中結(jié)構(gòu)體的成員變量 *__Block_byref_val_0 __forwarding 是指向自身的指針浑塞,通過(guò)這個(gè)指針來(lái)訪問(wèn)變量 __forwarding->val借跪。為何這樣使用,下面來(lái)介紹酌壕。

Block 存儲(chǔ)域

前面說(shuō)過(guò)掏愁,Block 是作為 OC 對(duì)象處理的,impl.isa = &_NSConcreteStackBlock; 該類(lèi)的信息就放在 _NSConcreteStackBlock 中卵牍。
除了 _NSConcreteStackBlock 類(lèi)型果港,還有其他兩種類(lèi)型 _NSConcreteGlobalBlock 和 _NSConcreteMallocBlock,這三類(lèi)所在內(nèi)存存儲(chǔ)區(qū)域有區(qū)別:

  • _NSConcreteStackBlock 棧區(qū)
  • _NSConcreteGlobalBlock 數(shù)據(jù)區(qū)域
  • _NSConcreteMallocBlock 堆區(qū)
_NSConcreteGlobalBlock

前面出現(xiàn)的 Block 例子糊昙,使用的是 _NSConcreteStackBlock 設(shè)置在棧上辛掠。
在全局變量使用 Block 時(shí),生成的是 _NSConcreteGlobalBlock 類(lèi)對(duì)象释牺,如下:

void (^blockName)(void) = ^{ NSLog(@"block"); };
int main() {
    blockName();
    return 0;
}

因?yàn)樵谑褂萌肿兞康牡胤讲荒苁褂米詣?dòng)變量萝衩,所以就不存在堆自動(dòng)變量進(jìn)行截獲。此種類(lèi)型的 Block 結(jié)構(gòu)體實(shí)例內(nèi)容不依賴(lài)于執(zhí)行的狀態(tài)没咙,所以整個(gè)程序只需一個(gè)實(shí)例猩谊,將 Block 用結(jié)構(gòu)體實(shí)例設(shè)置在與全局變量相同的數(shù)據(jù)區(qū)域中即可。
總結(jié)來(lái)說(shuō)就是祭刚,在全局變量有 Block 語(yǔ)法時(shí)牌捷,Block 語(yǔ)法的表達(dá)式不使用截獲的自動(dòng)變量時(shí),Block 為 _NSConcreteGlobalBlock 類(lèi)對(duì)象袁梗。

_NSConcreteMallocBlock

配置在全局變量上的 Block 在變量作用域外也可以通過(guò)指針訪問(wèn)使用宜鸯,但是配置在棧上的 Block 若其所在變量作用域結(jié)束,該 Block 就被廢棄遮怜,同樣的 __block 變量也配置在棧上,若所在變量作用域結(jié)束鸿市,則該 __block 變量也會(huì)被廢棄锯梁。

因此提供了將 Block 和 __block 變量從棧上復(fù)制到堆上的方法來(lái)解決這個(gè)問(wèn)題,從棧上復(fù)制到堆上焰情,即使變量作用域結(jié)束陌凳,堆上的 Block 還可以繼續(xù)存在。
復(fù)制到堆上的 Block 將 _NSConcreteMallocBlock 類(lèi)對(duì)象寫(xiě)入到 Block 結(jié)構(gòu)體實(shí)例的成員變量 isa: impl.isa = &_NSConcreteMallocBlock;
__block 變量的結(jié)構(gòu)體成員變量 __forwarding 可以實(shí)現(xiàn)無(wú)論 __block 變量配置在棧上内舟,還是堆上都能正確的訪問(wèn) __block 變量合敦。

在 ARC 模式下,編譯器會(huì)判斷验游,自動(dòng)生成將 Block 從棧上復(fù)制到堆上的代碼充岛。但是當(dāng)保檐,向方法或函數(shù)的參數(shù)中傳遞 Block 時(shí),需要手動(dòng)復(fù)制崔梗,如下面的代碼夜只。

- (NSArray *)getBlockArray {
    int val = 10;
    //需要使用 copy 復(fù)制,不然函數(shù)執(zhí)行完成蒜魄,棧上的 Block 被廢棄扔亥,執(zhí)行報(bào)錯(cuò)
    return [NSArray arrayWithObjects:
            [^{NSLog(@"blk0: %d", val);} copy],
            [^{NSLog(@"blk1: %d", val);} copy] , nil];
}

NSArray *tempArray = [self getBlockArray];
void(^blk)(void) = [tempArray objectAtIndex:0];
blk();

如果在方法或函數(shù)中復(fù)制了傳遞過(guò)來(lái)的參數(shù),那么就不必再調(diào)用該方法或函數(shù)前手動(dòng)復(fù)制了谈为,例如旅挤,在方法命中含有 usingBlock 時(shí),[array enumerateObjectsUsingBlock:...]伞鲫,或者 GCD 的 API 中粘茄,不用手動(dòng)復(fù)制。

當(dāng)對(duì) Block 調(diào)用 copy 方法時(shí)榔昔,_NSConcreteStackBlock 類(lèi)會(huì)從棧復(fù)制到堆上驹闰。_NSConcreteGlobalBlock 類(lèi)什么也不做。_NSConcreteMallocBlock 類(lèi)引用計(jì)數(shù)器增加撒会。

什么時(shí)候棧上的 Block 會(huì)復(fù)制到堆上:

  • 調(diào)用 Block 的 copy 實(shí)例方法時(shí)
  • Block 作為函數(shù)返回值返回時(shí)
  • 將 Block 賦值給有 __strong 修飾符 id 類(lèi)型的類(lèi)或 Block類(lèi)型變量時(shí)
  • 在方法名含有 usingBlock 的 Coca 框架方法 或 GCD 的 API 中傳遞 Block 時(shí)

注意下面幾種情況嘹朗,ARC下:


//將 Block 賦值給有 __strong 修飾符 id 類(lèi)型的類(lèi)或 Block類(lèi)型變量時(shí),棧上的 Block 會(huì)復(fù)制到堆上
        NSInteger val = 3;
        NSLog(@"block = %@", ^{ NSLog(@"val = %ld", val); });
        //block = <__NSStackBlock__: 0x7ffeefbff540>
        
        NSInteger i = 6;
        //捕獲變量诵肛,和上面的對(duì)比屹培,這里將 Block 賦值,會(huì)從棧上復(fù)制到堆上
        void (^blockName)(void) = ^{ NSLog(@"i = %ld", i); };
        blockName(); // i = 6
        NSLog(@"block = %@", blockName); 
        //block = <__NSMallocBlock__: 0x10280df70>
        
        __block NSInteger val = 3;
        NSLog(@"block = %@", ^{ val = 30; NSLog(@"val = %ld", val); });
        //block = <__NSStackBlock__: 0x7ffeefbff528>
        
        __block NSInteger i = 6;
        void (^blockName)(void) = ^{ i = 60;  NSLog(@"i = %ld", i); };
        blockName(); // i = 60
        NSLog(@"block = %@", blockName); 
        //block = <__NSMallocBlock__: 0x100704250>

//在沒(méi)有捕獲自動(dòng)變量時(shí)怔檩, Block 結(jié)構(gòu)體實(shí)例是 _NSConcreteGlobalBlock
        NSLog(@"block = %@", ^{ NSLog(@"no val"); });
        //block = <__NSGlobalBlock__: 0x100001028>
        
        static NSInteger val = 3;
        NSLog(@"block = %@", ^{ val = 30; NSLog(@"val = %ld", val); });
        //block = <__NSGlobalBlock__: 0x100001028>
        
        void (^blockName)(void) = ^{ NSLog(@"no val"); };
        blockName();
         NSLog(@"block = %@", blockName);
        //block = <__NSGlobalBlock__: 0x100001048>
        
        static NSInteger i = 6;
        void (^blockName)(void) = ^{ i = 60;  NSLog(@"i = %ld", i); };
        blockName(); // i = 60
        NSLog(@"block = %@", blockName); //block = <__NSGlobalBlock__: 0x100001048>
__block 變量和對(duì)象

當(dāng) Block 從棧復(fù)制到堆時(shí)褪秀, __block 變量也全部被從棧復(fù)制到堆并被 Block 所持有。
若有多個(gè) Block 使用同一個(gè) __block 變量時(shí)薛训,任何一個(gè) Block 從棧復(fù)制到堆時(shí)媒吗,__block 變量也會(huì)從棧復(fù)制到堆,剩下的 Block 從棧復(fù)制到堆乙埃,被復(fù)制的 Block 持有 __block 變量并增加 __block 變量的引用計(jì)數(shù)闸英。
若配置在堆上的 Block 被廢棄,它所使用的 __block 變量也會(huì)被釋放介袜。

前面提到 __block 變量的結(jié)構(gòu)體成員變量 __forwarding 可以實(shí)現(xiàn)無(wú)論 __block 變量配置在棧上甫何,還是堆上都能正確的訪問(wèn) __block 變量。如下例子:

__block NSInteger val = 10;
void (^blockName)(void) = [^{val = 30; NSLog(@"val = %ld", val); } copy];
        
blockName(); // val = 30
NSLog(@" %ld ", val); //30
        
val = 20;
NSLog(@" %ld ", val); //20

__block 變量從棧上復(fù)制到堆上遇伞,此時(shí)會(huì)將成員變量 __forwarding 的值替換為復(fù)制到堆上的 __block 變量結(jié)構(gòu)體實(shí)例的地址辙喂。val.__forwarding 使用的是同一個(gè)在堆上的值,從而能保證正確的訪問(wèn)同一個(gè) __block 變量。

上圖中有兩個(gè)函數(shù)巍耗,__main_block_copy_0 和 __main_block_dispose_0秋麸,在 Block 復(fù)制到堆上和從堆上釋放時(shí)被調(diào)用。將使用到的 __block 變量或者截獲的對(duì)象復(fù)制給 Block 結(jié)構(gòu)體的成員變量芍锦,持有對(duì)象竹勉。這樣截獲的對(duì)象就能夠超出其變量作用域而存在。通過(guò)參數(shù) BLOCK_FIELD_IS_BYREF 和 BLOCK_FIELD_IS_OBJECT 來(lái)區(qū)分函數(shù)對(duì)象類(lèi)型是 __block 變量還是對(duì)象娄琉。

id array = [NSMutableArray array];
void (^blk)(void) = ^(){
    //截獲對(duì)象
    [array addObject:@(1)];
    NSLog(@"Block ! %ld", [array count]);
};
blk();


Block 的循環(huán)引用問(wèn)題

如在 block 中使用了對(duì)象次乓, block 會(huì)對(duì)使用的對(duì)象進(jìn)行持有,如該對(duì)象同時(shí)持有該 block 則會(huì)造成循環(huán)引用的問(wèn)題孽水,互相持有不能釋放票腰。


self.blockName = ^() {
    [self.delegate doSomething];
};

//解決方法,在 ARC 下使用 __weak 進(jìn)行修飾女气。這樣 block 就不持有 self杏慰,避免循環(huán)引用。
__weak __typeof(self) weakSelf = self;
self.blockName = ^() {
    [weakSelf.delegate doSomething];
};


//下面代碼會(huì)有內(nèi)存泄漏嗎
- (void)viewDidLoad {
    [super viewDidLoad];
    NSNotificationCenter *__weak center = [NSNotificationCenter defaultCenter];
    id __block token = [center addObserverForName:UIApplicationDidEnterBackgroundNotification
                                           object:nil
                                            queue:[NSOperationQueue mainQueue]
                                       usingBlock:^(NSNotification * _Nonnull note) {
        [self doSomething];
        [center removeObserver:token];
        token = nil;
    }];
}

- (void)doSomething {
}

- (void)dealloc {
    NSLog(@"%s", __FUNCTION__);
}

//__block token炼鞠,如果不加 __block 在Block里的 [center removeObserver:token]; token 為空缘滥。
//因?yàn)?token 在執(zhí)行完后才返回值,所以一開(kāi)始捕獲到的谒主,是返回之前的沒(méi)有被初始化的朝扼。
//加上 __block 是通過(guò)指針 __forwarding->token 取值卖漫,能夠正確訪問(wèn)到骡技。
//上面如果 block 沒(méi)有執(zhí)行,則會(huì)內(nèi)存泄漏蔓钟。center 持有 token观游,token 持有 block搂捧,block 持有 self 也持有 token,
//token 不釋放懂缕,self 不會(huì)釋放

//最簡(jiǎn)單的解決方法就是
__weak typeof(self) wkSelf = self;
id __block __weak wkToken = [wkCenter addObserverForName:UIApplicationDidEnterBackgroundNotification
                                      object:nil
                                       queue:[NSOperationQueue mainQueue]
                                  usingBlock:^(NSNotification * _Nonnull note) {
   [wkSelf doSomething];
   [wkCenter removeObserver:wkToken];
}];


__weak允跑、 __strong 的使用

聲明一個(gè)對(duì)象:

id __strong obj = [[NSObject alloc] init];

//編譯器會(huì)轉(zhuǎn)換為下面的代碼
id __attribute__((objc_ownership(strong))) obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));

//上面的代碼其實(shí)就是下面的函數(shù)調(diào)用
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj,selector(init));
objc_release(obj);
__weak id weakObj = obj;

//轉(zhuǎn)換為
__attribute__((objc_ownership(weak))) id weakObj = obj;

//相關(guān)的調(diào)用
id weakObj ;
objc_initWeak(&weakObj,obj);
objc_destoryWeak(&weakObj);


id objc_initWeak(id *object, id value) {   
    *object = nil; 
    return objc_storeWeak(object, value);
}

void objc_destroyWeak(id *object) { 
    objc_storeWeak(object, nil);
}

有關(guān)底層實(shí)現(xiàn)可查看 clang 文檔 http://clang.llvm.org/docs/AutomaticReferenceCounting.html

weak 表是用Hash table實(shí)現(xiàn)的, objc_storeWeak 函數(shù)就把第一個(gè)入?yún)⒌淖兞康刂纷?cè)到weak表中搪柑,然后根據(jù)第二個(gè)入?yún)?lái)決定是否移除吮蛹。如果第二個(gè)參數(shù)為0,那么就把 __weak變量從weak表中刪除記錄拌屏,并從引用計(jì)數(shù)表中刪除對(duì)應(yīng)的鍵值記錄。

如果 __weak 引用的原對(duì)象如果被釋放了术荤,那么對(duì)應(yīng)的 __weak 對(duì)象就會(huì)被指為nil倚喂。就是通過(guò) objc_storeWeak 函數(shù)這些函數(shù)來(lái)實(shí)現(xiàn)的。


我們已經(jīng)知道使用 weakSelf 來(lái)解決循環(huán)引用的問(wèn)題,為何有的還需要在 block 里使用 strongSelf ?

__weak __typeof(self) weakSelf = self;
self.blockName = ^() {
        __strong typeof(weakSelf) strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [strongSelf.delegate doSomething];
        });
};

有的情況下 block 會(huì)延遲調(diào)用端圈,在 block 未調(diào)用前 self 可能 已經(jīng)釋放掉了焦读,這時(shí)再在 block 使用 weakSelf ,weakSelf 為空了舱权。在 block 里面使用的 __strong 修飾的 weakSelf 是為了在函數(shù)生命周期中防止 self 提前釋放矗晃。strongSelf 是一個(gè)自動(dòng)變量當(dāng) block 執(zhí)行完畢就會(huì)釋放自動(dòng)變量 strongSelf ,不會(huì)對(duì) self 進(jìn)行一直進(jìn)行強(qiáng)引用宴倍。

總結(jié)來(lái)說(shuō)就是张症,weakSelf 是為了 block 不持有 self,避免循環(huán)引用鸵贬。strongSelf 防止 self 提前釋放俗他。


Reference

Objective-C高級(jí)編程
深入研究Block

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市阔逼,隨后出現(xiàn)的幾起案子兆衅,更是在濱河造成了極大的恐慌,老刑警劉巖嗜浮,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羡亩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡危融,警方通過(guò)查閱死者的電腦和手機(jī)畏铆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)专挪,“玉大人及志,你說(shuō)我怎么就攤上這事≌唬” “怎么了速侈?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)迫卢。 經(jīng)常有香客問(wèn)我倚搬,道長(zhǎng),這世上最難降的妖魔是什么乾蛤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任每界,我火速辦了婚禮,結(jié)果婚禮上家卖,老公的妹妹穿的比我還像新娘眨层。我一直安慰自己,他們只是感情好上荡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布趴樱。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叁征。 梳的紋絲不亂的頭發(fā)上纳账,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音捺疼,去河邊找鬼疏虫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛啤呼,可吹牛的內(nèi)容都是我干的卧秘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼媳友,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼斯议!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起醇锚,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤哼御,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后焊唬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體恋昼,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年赶促,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了液肌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鸥滨,死狀恐怖嗦哆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情婿滓,我是刑警寧澤老速,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站凸主,受9級(jí)特大地震影響橘券,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卿吐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一旁舰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嗡官,春花似錦箭窜、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)芥丧。三九已至,卻和暖如春坊罢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背擅耽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工活孩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乖仇。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓憾儒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親乃沙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子起趾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355