Block

在iOS中烤低,block編程使用得很頻繁,我們不僅要會用block氯窍,更需要理解block的底層實現(xiàn)原理。筆者在面試中蹲堂,block問題是必問的荞驴。

什么是block

block是iOS中對閉包的實現(xiàn),什么是閉包呢贯城?閉包(英語:Closure)熊楼,又稱詞法閉包(Lexical Closure)或函數(shù)閉包(function closures),是在支持頭等函數(shù)的編程語言中實現(xiàn)詞法綁定的一種技術。閉包在實現(xiàn)上是一個結構體鲫骗,它存儲了一個函數(shù)(通常是其入口地址)和一個關聯(lián)的環(huán)境(相當于一個符號查找表)犬耻。環(huán)境里是若干對符號和值的對應關系,它既要包括約束變量(該函數(shù)內(nèi)部綁定的符號)执泰,也要包括自由變量(在函數(shù)外部定義但在函數(shù)內(nèi)被引用)枕磁,有些函數(shù)也可能沒有自由變量。

block類型

block是一個OC對象术吝,block類型有NSStackBlock计济、NSMallocBlockNSGlobalBlock排苍、沦寂,分別分配在棧、堆淘衙、全局存儲區(qū)域中传藏。他們都繼承于NSObject。下面代碼證明打印了NSGlobalBlock的繼承鏈

void (^block)(void) =  ^{
        NSLog(@"akon");
    };

    NSLog(@"block.class = %@", [block class]);
    NSLog(@"block.class.superclass = %@", [[block class] superclass]);
    NSLog(@"block.class.superclass.superclass = %@", [[[block class] superclass] superclass]);
    NSLog(@"block.class.superclass.superclass.superclass = %@", [[[[block class] superclass] superclass] superclass]);

運行結果為:
2020-11-13 18:39:02.919351+0800 BlockTestDemo[86009:2083840] block.class = __NSGlobalBlock__
2020-11-13 18:39:02.919562+0800 BlockTestDemo[86009:2083840] block.class.superclass = NSBlock
2020-11-13 18:39:02.919713+0800 BlockTestDemo[86009:2083840] block.class.superclass.superclass = NSObject
2020-11-13 18:39:02.923424+0800 BlockTestDemo[86009:2083840] block.class.superclass.superclass.superclass = (null)

下面表格列出了MRC和ARC環(huán)境下block類型

MRC下block類型

類型 環(huán)境
NSGlobalBlock 只訪問了靜態(tài)變量(包括全局靜態(tài)變量和局部靜態(tài)變量)和全局變量
NSStackBlock 沒訪問靜態(tài)變量和全局變量
NSMallocBlock NSStackBlock調(diào)用了copy

執(zhí)行如下代碼彤守,打印結果符合預期

 __weak typeof(self)weakSelf = self;

    static int a = 0;
    void (^block1)(void) =  ^{
        a = 1;
        b = 1; //b為全局變量

    };

    __block int c = 0;
    void (^block2)(void) =  ^{
        NSLog(@"age:%d", weakSelf.age);
        c = 1;
    };

    NSLog(@"block1.class = %@", [block1 class]);
    NSLog(@"block2.class = %@", [block2 class]);
    NSLog(@"block2 copy.class = %@", [[block2 copy] class]);

運行結果如下:
2020-11-14 22:45:54.457496+0800 BlockTestDemo[13178:426318] block1.class = __NSGlobalBlock__
2020-11-14 22:45:54.457616+0800 BlockTestDemo[13178:426318] block2.class = __NSStackBlock__
2020-11-14 22:45:54.457720+0800 BlockTestDemo[13178:426318] block2 copy.class = __NSMallocBlock__

ARC下block類型

類型 環(huán)境
NSGlobalBlock 只訪問了靜態(tài)變量(包括全局靜態(tài)變量和局部靜態(tài)變量)和全局變量
NSMallocBlock 沒訪問靜態(tài)變量和全局變量

運行上面的代碼毯侦,結果如下:

2020-11-14 22:45:54.457052+0800 BlockTestDemo[13178:426318] block1.class = __NSGlobalBlock__
2020-11-14 22:45:54.457211+0800 BlockTestDemo[13178:426318] block2.class = __NSMallocBlock__
2020-11-14 22:45:54.457356+0800 BlockTestDemo[13178:426318] block2 copy.class = __NSMallocBlock__

ARC下自動copy

  • 我們看到block2為NSMallocBlock,這是因為編譯器做了優(yōu)化具垫,在ARC下除了NSGlobalBlock_就是NSMallocBlock侈离,沒有NSStackBlock;在MRC NSMallocBlock生成的條件是對block調(diào)用了copy操作筝蚕。
  • 在ARC環(huán)境下卦碾,編譯器會根據(jù)情況自動將棧上的block復制到堆上,copy的情況如下:
    1饰及、block作為函數(shù)返回值時
    2、 將block賦值給_strong指針時
    3康震、block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時
    4燎含、block作為GCD API的方法參數(shù)時
    在ARC中對NSStackBlock調(diào)用copy變成NSMallocBlockNSMallocBlock調(diào)用copy還是NSMallocBlock腿短,引用計數(shù)+1屏箍,NSGlobalBlock
    調(diào)用copy啥都不做。
  • copy底層原理
    1橘忱、通過_Block_object_assign來對OC對象進行強引用或弱引用
    2赴魁、通過_Block_object_dispose對OC進行清理

block數(shù)據(jù)結構和變量捕獲

在Block內(nèi)部無法修改外部數(shù)據(jù)的原因是在Block中訪問外部變量時,都會對其進行一份拷貝,需要注意這里的拷貝是直接拷貝,如果你在Block內(nèi)部和外部對變量對象進行打印,則可以看到其地址是不同的。Objective-C提供了_block關鍵字,使用它可以直接訪問原始變量钝诚。

變量捕獲

可以按照上面分析思路颖御,得出結論

變量類型 捕獲到block內(nèi)部 變量類型
局部非OC變量 值傳遞
局部變量 static、OC對象 指針傳遞
全局變量 × 直接訪問

可以看到全局變量凝颇,b
lock內(nèi)部不會直接捕獲潘拱,其他變量會捕獲疹鳄。

__block變量

__block作用

  • __block只能修飾非靜態(tài)局部變量,不能修飾靜態(tài)變量和全局變量芦岂,否則編譯器報錯瘪弓。
  • 當需要在block內(nèi)部修改一個局部變量時,需要加__block ,否則禽最,編譯不過腺怯。下面的代碼,編譯報錯:Variable is not assignable (missing __block type specifier)川无。加上__block編譯通過呛占,name會變成lbj
 NSString* name = @"akon";
    void (^block)(void) =  ^{
        name = @"lbj";
     };

    block();

底層實現(xiàn)

  • 類似剛才的轉(zhuǎn)成cpp思路,分析得出結論如下圖舀透∷ㄆ保總結就是對于__block變量,底層會封裝成一個對象愕够,其中通過__forwarding指向自己走贪,來訪問真實的變量。


    image
  • 為什么要通過__forwarding訪問惑芭?
    這是因為坠狡,如果__block變量在棧上,就可以直接訪問遂跟,但是如果已經(jīng)拷貝到了堆上逃沿,訪問的時候,還去訪問棧上的幻锁,就會出問題凯亮,所以,先根據(jù)__forwarding找到堆上的地址哄尔,然后再取值

循環(huán)引用

循環(huán)引用原因

當對象A和對象B互相引用時會造成循環(huán)引用假消。

循環(huán)引用解決方案

竟然對象A和對象B互相引用會造成循環(huán)引用,那就要斷開這個循環(huán)引用岭接,可以通過__weak或者__unsafe_unretained富拗,這兩者的區(qū)別是__unsafe_unretained當引用對象變?yōu)閚il時__unsafe_unretained對象不會自動置為nil,導致變?yōu)橐爸羔樏鳎俅问褂脮罎ⅰ?/p>

常見循環(huán)引用及解決

1) 在VC的cellForRowAtIndexPath方法中cell的block直接引用self或者直接以_形式引用屬性造成循環(huán)引用啃沪。

 cell.clickBlock = ^{
        self.name = @"akon";
    };

cell.clickBlock = ^{
        _name = @"akon";
    };

解決方案:把self改成weakSelf;

__weak typeof(self)weakSelf = self;
    cell.clickBlock = ^{
        weakSelf.name = @"akon";
    };

注意有的時候我們會在block里面寫成__strong typeof(weakSelf) strongSelf = weakSelf窄锅,然后再用strongSelf調(diào)用方案创千,這樣做的原因是防止在block執(zhí)行過程中weakSelf突然變成nil。
2)在cell的block中直接引用VC的成員變量造成循環(huán)引用。

//假設 _age為VC的成員變量
@interface TestVC(){

    int _age;

}
cell.clickBlock = ^{
       _age = 18;
    };

解決方案有兩種:

  • 用weak-strong dance
__weak typeof(self)weakSelf = self;
cell.clickBlock = ^{
      __strong typeof(weakSelf) strongSelf = weakSelf;
       strongSelf->age = 18;
    };

  • 把成員變量改成屬性
//假設 _age為VC的成員變量
@interface TestVC()

@property(nonatomic, assign)int age;

@end

__weak typeof(self)weakSelf = self;
cell.clickBlock = ^{
       weakSelf.age = 18;
    };

3)delegate屬性聲明為strong签餐,造成循環(huán)引用寓涨。

@interface TestView : UIView

@property(nonatomic, strong)id<TestViewDelegate> delegate;

@end

@interface TestVC()<TestViewDelegate>

@property (nonatomic, strong)TestView* testView;

@end

 testView.delegate = self; //造成循環(huán)引用

解決方案:delegate聲明為weak

@interface TestView : UIView

@property(nonatomic, weak)id<TestViewDelegate> delegate;

@end

4)在block里面調(diào)用super,造成循環(huán)引用氯檐。

cell.clickBlock = ^{
       [super goback]; //造成循環(huán)應用
    };

解決方案戒良,封裝goback調(diào)用

__weak typeof(self)weakSelf = self;
cell.clickBlock = ^{
       [weakSelf _callSuperBack];
    };

- (void) _callSuperBack{
    [self goback];
}

5)block聲明為strong
解決方案:聲明為copy
6)NSTimer使用后不invalidate造成循環(huán)引用。
解決方案:

  • NSTimer用完后invalidate冠摄;
  • NSTimer分類封裝
+ (NSTimer *)ak_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                         block:(void(^)(void))block
                                       repeats:(BOOL)repeats{

    return [self scheduledTimerWithTimeInterval:interval
                                         target:self
                                       selector:@selector(ak_blockInvoke:)
                                       userInfo:[block copy]
                                        repeats:repeats];
}

+ (void)ak_blockInvoke:(NSTimer*)timer{

    void (^block)(void) = timer.userInfo;
    if (block) {
        block();
    }
}

--

怎么檢測循環(huán)引用

  • 靜態(tài)代碼分析糯崎。 通過Xcode->Product->Anaylze分析結果來處理;
  • 動態(tài)分析河泳。用MLeaksFinder(只能檢測OC泄露)或者Instrument或者OOMDetector(能檢測OC與C++泄露)沃呢。
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拆挥,隨后出現(xiàn)的幾起案子薄霜,更是在濱河造成了極大的恐慌,老刑警劉巖纸兔,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惰瓜,死亡現(xiàn)場離奇詭異,居然都是意外死亡汉矿,警方通過查閱死者的電腦和手機崎坊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洲拇,“玉大人奈揍,你說我怎么就攤上這事「承” “怎么了男翰?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長纽乱。 經(jīng)常有香客問我蛾绎,道長,這世上最難降的妖魔是什么迫淹? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任秘通,我火速辦了婚禮为严,結果婚禮上敛熬,老公的妹妹穿的比我還像新娘。我一直安慰自己第股,他們只是感情好应民,可當我...
    茶點故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般诲锹。 火紅的嫁衣襯著肌膚如雪繁仁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天归园,我揣著相機與錄音黄虱,去河邊找鬼。 笑死庸诱,一個胖子當著我的面吹牛捻浦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播桥爽,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼朱灿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钠四?” 一聲冷哼從身側(cè)響起盗扒,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缀去,沒想到半個月后侣灶,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡朵耕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年炫隶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阎曹。...
    茶點故事閱讀 40,435評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡伪阶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出处嫌,到底是詐尸還是另有隱情栅贴,我是刑警寧澤,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布熏迹,位于F島的核電站檐薯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏注暗。R本人自食惡果不足惜坛缕,卻給世界環(huán)境...
    茶點故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望捆昏。 院中可真熱鬧赚楚,春花似錦、人聲如沸骗卜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至举户,卻和暖如春烤宙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俭嘁。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工躺枕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人供填。 一個月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓屯远,卻偏偏與公主長得像,于是被迫代替她去往敵國和親捕虽。 傳聞我的和親對象是個殘疾皇子慨丐,可洞房花燭夜當晚...
    茶點故事閱讀 45,442評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 在iOS中,block編程使用得很頻繁泄私,我們不僅要會用block房揭,更需要理解block的底層實現(xiàn)原理。筆者在面試中...
    哎呦哎呦小葵花閱讀 218評論 0 0
  • 什么是block block是iOS中對閉包的實現(xiàn)晌端,什么是閉包呢捅暴?閉包(英語:Closure),又稱詞法閉包(Le...
    詩舞灬櫻花閱讀 98評論 0 0
  • 一 block基本使用 二 block底層結構 三 block變量捕獲 四 block的類型 五 block對象類...
    當前明月閱讀 1,220評論 3 4
  • block本質(zhì)block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對象(它內(nèi)部也有個isa指針) 函數(shù)調(diào)用環(huán)境:函數(shù)調(diào)...
    陳盼同學閱讀 360評論 0 0
  • iOS開發(fā)中block是比較常用也是比較好用的語法咧纠,平時開發(fā)中我們都用的很溜蓬痒,但它的底層是如何實現(xiàn)的呢?__blo...
    _小沫閱讀 415評論 0 2