iOS-block

一. 查看block內部實現

1.編寫block代碼

void (^DemoBlock)(int, int) = ^(int a, int b){
    NSLog(@"%d",a+b);
};
DemoBlock(1,3);

輸出:2019-01-14 13:01:47.104 iOSWorld[1324:103701] 4
  1. 使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc BlockViewController.m命令查看c++源碼,搜索DemoBlock.
void (*DemoBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *, int, int))((__block_impl *)DemoBlock)->FuncPtr)((__block_impl *)DemoBlock, 1, 3);

簡化:((void (*)(int, int))是一個函數指針,也就是把后面的代碼強轉為一個函數指針.刪減后得到(從下往上看)

struct __block_impl {
 void *isa;
 int Flags;
 int Reserved;
 void *FuncPtr;
};

static struct __main_block_desc_0 {
 size_t reserved;//為0
 size_t Block_size;//block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

struct __main_block_impl_0 {
 struct __block_impl impl;
 struct __main_block_desc_0* Desc;
 //__main_block_impl_0和結構體同名, 是一個構造函數,  返回結構體對象
 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
   impl.isa = &_NSConcreteStackBlock;
   impl.Flags = flags;
   impl.FuncPtr = fp;
   Desc = desc;
 }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b){
  NSLog(a+b);
}
//定義DemoBlock,&__main_block_impl_0是一個函數,有兩個參數,第一個為包裝的方法,第二個為描述信息
void (*DemoBlock)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
//執行DemoBlock
DemoBlock->FuncPtr(DemoBlock, 1, 3);

代碼解釋:
1.調用__main_block_impl_0()函數

  • 參數1為__main_block_func_0,也就是我們block里面要做的事情,也就是匿名函數 --> NSLog(@"%d",a+b);
  • 參數2為__main_block_desc_0_DATA,__main_block_desc_0_DATA是一個struct __main_block_desc_0的結構體,結構體參數reserved=0,結構體參數Block_size=sizeof(struct __main_block_impl_0);
  1. 調用后會來到__main_block_impl_0這個結構體的構造函數,將內部impl結構體設置impl.isa = &_NSConcreteStackBlock; impl.Flags = flags;impl.FuncPtr = fp;將內部__main_block_desc_0結構體的Desc = desc;

所以: block是封裝了函數調用以及函數調用環境的對象
底層:block是一個包含了函數指針和變量的結構體

3.分析(注意!!!!!標注的代碼)

  • impl.isa = &_NSConcreteStackBlock;
    block有一個isa指針,說明block也是一個OC對象.
  • impl.FuncPtr = fp
    記錄一下block里面的函數
  • Desc = desc;
    記錄該block的描述

4.拓展

- (void)test
{
   void(^DemoBlock)(void) = ^{
   };
   
   NSLog(@"%@",[DemoBlock class]);
   NSLog(@"%@",[[DemoBlock class] superclass]);
   NSLog(@"%@",[[[DemoBlock class] superclass] superclass]);
   NSLog(@"%@",[[[[DemoBlock class] superclass] superclass] superclass]);
}

輸出:2019-01-14 16:16:15.953 iOSWorld[1542:128655] __NSGlobalBlock__
    2019-01-14 16:16:15.953 iOSWorld[1542:128655] __NSGlobalBlock
    2019-01-14 16:16:15.954 iOSWorld[1542:128655] NSBlock
    2019-01-14 16:16:15.954 iOSWorld[1542:128655] NSObject

block有superclass,說明就是OC對象.

二.block變量捕獲

  • 對基本數據類型的局部變量截獲其值.
  • 對對象類型的局部變量連同所有權修飾符一起截獲,如果以strong修飾,捕獲的時候底層就retain一次,如果是weak修飾的,就不retain
    self也是局部變量,所以會捕獲self
  • 以指針形式截獲靜態局部變量
    雖然static修飾的局部變量也是永駐內存,但因為它是局部的,不像全局變量一樣誰都可以訪問,所以捕獲它的指針才能找到它.
  • 不截獲全局變量、靜態全局變量,編譯的時候就寫死其地址了。

2.1 block捕獲局部變量

demo1

- (void)test
{
   int a = 10;
   void(^DemoBlock)(void) = ^{
       NSLog(@"%d", a);
   };
   a = 20;
   DemoBlock();
}

輸出:2019-01-14 15:00:38.339 iOSWorld[1446:119920] 10

解釋:定義完a=10后,編譯器開始編譯DemoBlock,這時候DemoBlock內部已經捕獲了a的值.
繼續執行a=20,繼續執行DemoBlock()會打印出已經捕獲的a=10,所以打印為10.

總結: 對于局部變量,一出作用域就會被銷毀了,所以block會及時捕獲局部變量并copy一份值到block內部.

2.2 block捕獲全局變量

demo2

int a = 10;
- (void)test
{
   void(^DemoBlock)(void) = ^{
       NSLog(@"%d", a);
   };
   a = 20;
   DemoBlock();
}

輸出:2019-01-14 15:26:38.208 iOSWorld[1478:122759] 20

解釋:int a = 10,接下來編譯DemoBlock,繼續執行a=20,繼續執行DemoBlock()的時候會調用NSLog(@"%d", a),因為a是全局變量,這時候只需要拿到a打印就行了.

總結: 對于全局變量,即使出了作用域也不會被銷毀,所以block不會捕獲全局變量,啥時候用啥時候取就行.下劃線_age其實是self->age,所以會先捕獲self

三.3種block的內存存放區域

block分類
_NSConcreteStackBlock:在棧上創建的Block對象
_NSConcreteMallocBlock:在堆上創建的Block對象
_NSConcreteGlobalBlock:全局數據區的Block對象(data區)

  • 沒有捕獲局部變量的block為GlobalBlock
  • 捕獲了局部變量但是沒有進行block復制則為StackBlock---棧block容易出錯,因為有可能被銷毀了,StackBlock不會強引用對象
  • 其他的block基本都為MallocBlock(堆block)

block復制
block從棧復制到堆的時候,會調用_Block_object_assign()強引用
block在堆上銷毀的時候,會調用_Block_object_dispose()release引用的對象

在ARC有效時,大多數情況下編譯器會進行判斷,自動生成將Block從棧上復制到堆上的代碼,以下幾種情況棧上的Block會自動復制到堆上:

  • 調用Block的copy方法 globalBlock copy仍為globalBlock,stackBlock copy后為mallocBlock,mallocBlock copy后引用計數+1
  • 將Block作為函數返回值時
  • 將Block賦值給__strong修飾的變量時
  • 向Cocoa框架含有usingBlock的方法 或者 GCD的API傳遞Block參數時

所以如果不怕被銷毀,比如該block當函數用一次,那么block不一定非得用copy.
__block可以解除循環引用

四.block修改變量

???為什么不可以在block內部修改局部變量

因為:局部變量出了大括號就會銷毀,有可能你修改的時候它已經是nil了.
但是:我們可以使用__block修飾局部變量.

__block int a = 10;
void (^DemoBlock)(void) = ^(){
    a = 20;
    NSLog(@"%d",a);
};
DemoBlock()
輸出:---------> 20
  1. 使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc main.m命令查看c++源碼,搜索DemoBlock.
void (*DemoBlock)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
  1. 發現__main_block_impl_0實現里面多了一個__Block_byref_a_0開頭的變量
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
}
  1. 看看__Block_byref_a_0,發現它里面有個a,還有個自己類型的__forwarding-->__Block_byref_a_0 *__forwarding
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

4.看看__main_block_func_0這個參數

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//創建了一個__Block_byref_a_0類型的和我們定義一樣的同名變量a
  __Block_byref_a_0 *a = __cself->a; 
//將新a的__forwarding里面的a設置為20
(a->__forwarding->a) = 20;
//打印新a的__forwarding里面的a
NSLog(a->__forwarding->a);
}

5.將自己(__Block_byref_a_0)的地址值傳到自己的第二個參數__forwarding里面,也就是說__forwarding指向的還是自己

__Block_byref_a_0 *__forwarding = (__Block_byref_a_0 *)&a

__forwarding存在的意義:不論在棧上還是堆上,都可以順利的訪問同一個__block變量.

總結:__block原理編譯器會將__block變量包裝成一個對象,并把局部變量賦值給該對象,這樣就能修改對象的變量
你以為你修改的還是那個局部變量,殊不知已經是__block自己生成的對象啦

拓展:__block __weak Person *p = [Person new]
上面代碼:生成一個自己的對象block_p包裹了p,
而且block強引用了block_p,但是因為有weak的存在,block_pp是弱引用的.

{
        TestBlock block1;
        {
            __block BlockReferDemoObject *objc = [[BlockReferDemoObject alloc] init];
            block1 = ^{
                NSLog(@"%@", objc);
            };
        }//默認強引用
        block1();
    }
    
    NSLog(@"-----------------");

    {
        TestBlock block2;
        {
            BlockReferDemoObject *objc = [[BlockReferDemoObject alloc] init];
            __block __weak BlockReferDemoObject *weakObjc = objc;
            block2 = ^{
                NSLog(@"%@", weakObjc);
            };
        }//__weak修飾,沒有強引用objc,導致出了大括號就被銷毀了
        block2();
    }

五.block的內存管理

  • stackBlock對所有的局部變量都是弱引用
  • mallocBlock :
    1.對__block或者__Strong使用_Block_object_assign()進行強引用.
    2.對__weak使用_Block_object_assign()進行弱引用.
    當block移除時,使用_Block_object_dispose()來釋放局部變量.

六.解決循環引用的新方法

將在Block內要使用到的對象(一般為self對象),以Block參數的形式傳入,Block就不會捕獲該對象,而將其作為參數使用,其生命周期系統的棧自動管理,不造成內存泄露。
即原來使用__weak的寫法:

__weak typeof(self) weakSelf = self;
self.blk = ^{
    __strong typeof(self) strongSelf = weakSelf;
    NSLog(@"Use Property:%@", strongSelf.name);
    //……
};
self.blk();

改為Block傳參寫法后:

self.blk = ^(UIViewController *vc) {
    NSLog(@"Use Property:%@", vc.name);
};
self.blk(self);

__weak typeof(self)      weakSelf = self;
__weak UIViewController *weakSelf = self;

使用__block也能解除循環引用

    __block BlockReferDemoObject *objc = [BlockReferDemoObject new];
    {
        objc.demoBlock = ^{
            NSLog(@"%@", objc);
            objc = nil;
        };
        objc.demoBlock();
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容

  • Block基礎回顧 1.什么是Block? 帶有局部變量的匿名函數(名字不重要,知道怎么用就行),差不多就與C語言...
    Bugfix閱讀 6,792評論 5 61
  • 在了解Block之前,我們有必要先了解一下一些基礎知識。我們都知道,Objective-C是由C語言擴展而來的。在...
    SmithJackyson閱讀 717評論 0 8
  • 前言 ios4.0系統已開始支持block,在編程過程中,block被Obj-C看成是對象,它封裝了一段代碼,這段...
    GitHubPorter閱讀 813評論 2 4
  • 《Objective-C高級編程》這本書就講了三個東西:自動引用計數、block、GCD,偏向于從原理上對這些內容...
    WeiHing閱讀 9,867評論 10 69
  • 《小強升職記》是我讀的第一本關于時間管理的書籍,它不像其他同類書籍講述大段大段的方法論,而是通過故事的形式,潛移默...
    石子鑫閱讀 393評論 2 0