Block詳解一(底層分析)


本人已遷移博客至掘進,以后會在掘進平台更新最新的文章也會有更多的干貨,歡迎大家關注!!!https://juejin.im/user/588993965333309

 

Block專輯:

Block講解一

MRC-block與ARC-block

  

本篇博客不再講述Block的基本定義使用,最近而是看了很多的block博客講述的太亂太雜,所以抽出時間整理下block的相關底層知識,在講述之前,提出幾個問題,如果都可以回答出來以及知道原理,大神繞過,反之,希望本篇博客對大家面試或者block不熟悉者有所幫助,以后會不斷更新博客,歡迎關注和指正!!!

  1. blcok的原理是怎樣的?本質又是什么?
  2. __block的作用是什么?有什么使用注意點?
  3. block的屬性修飾詞為什么是copy修飾?使用block有哪些使用注意事項?
  4. block在修改NSMutableArray,需要不需要添加__block?

一、 block本質

  • blcok本質是OC對象,它內部也有個isa指針,在OC中有isa指針的對象,可以認定為OC對象
  • block對象是封裝了函數調用以及函數調用環境的OC對象

通過下面例子看下block結構

文件結構

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10; void (^block)(int, int) = ^(int a, int b){ NSLog(@"This is a block --%d",age); NSLog(@"This is a block"); NSLog(@"This is a block"); }; block(10, 20); } return 0; }

通過clang編譯器將OC代碼編譯成C語言代碼,並生成了在后綴名為.cpp的C++文件中,clang命令為

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

然后查看編譯出來的c++ main.cpp文件,和main.m同一個地方,將它移入到項目中,並在Build Phases->Compile Sources中刪除main.cpp,然后就可以編譯成功

 這次開始查看main.cpp的,對比兩個文件,找出main.cpp的對應的main函數

 看到上面main.cpp左邊的調用block,10,20前面有很多的強制類型轉換,最后可以是funcPtr (block,10,20)在.cpp中海油一個__main_block_imp_0地址,查看其地址內容

 

二、block變量捕獲機制

 舉例1: block變量捕獲-auto變量

經常書寫int age = 10,前面都是有默認關鍵字auto的(也可以不書寫,經常這樣的),下面的結果是什么?

int age = 10 等價於 auto int age = 10 (auto自動變量,離開作用域就會銷毀)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void (^block)(void) = ^{
            NSLog(@"age is %d", age);
        };
        age = 20;
        block();
    }
    return 0;
}

猜一下運行結果

 再次運行查看編譯出來的main.cpp,重復上面的步驟,對比main.cpp

 查看__main_block_impl_0的結構,傳入的參數age = 10,block內部新增了一個變量用於存儲age

 block的內部的age = 10 ,並不會改變,所以打印結果為10(當創建block的內容,age = 10已經存在了block中,並不會隨外部改變而改變)

舉例2:block變量捕獲-static變量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        auto int age = 10;
        static int height = 10;
        void (^block)(void) = ^{
            NSLog(@"age is %d, height is %d", age, height);
        };
        age = 20;
        height = 20;
        block();
    }
    return 0;
}

結果為age = 10,height = 20

將其編譯為cpp,對比下 

 查看__main_block_impl_0的代碼結構

發現block捕獲到了age和height,所以block會捕獲到局部變量,而靜態局部變量block存放的是地址,所以未來修改height的值時,取出的是所指向的最新的height值

舉例3:block變量捕獲-全局變量 

int age = 10;
static int height = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^{
            NSLog(@"age is %d, height is %d", age, height);
        };
        age = 20;
        height = 20;
        block();
    }
    return 0;
}

查看.cpp文件有沒有捕獲到全局變量?--直接訪問

 對於上面的block變量捕捉機制,總結如下:

 

 三、block的類型

block的底層結構如下

block有3種類型,可以通過調用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型

 到底什么類型的block屬於__NSGlobalBlock__,__NSStackBlock__, __NSMallocBlock__?

 先不鋪墊那么多,直接給出結論:

 下面來一一驗證結論

 就是像上面提問那樣,按總結的訪問了auto變量應該是NSStackBlock,怎么成為了NSMallocBlock了呢?

這是因為編譯器默認在ARC環境下,應該切換到MRC環境下,看一下真正的block類型,至於ARC的,下篇講述!!!

將編譯器改為去除ARC,在Build Settings -> automatic Reference Counting 中將ARC改為MRC

 改成MRC后再次允許查看結果

 那什么時候是NSMallocBlock呢?

NSStackBlock調用copy變為NSMallocBlock,但是NSGlobalBlock調用了copy依然是NSGlobalBlock,NSMallocBlock調用了copy方法引用計數會+1

 那么在ARC環境下什么時候block會調用copy呢?(從棧空間->堆空間)

在ARC環境下,編譯器會根據情況自動將棧上的block復制到堆上,比如有以下情況:

  • block作為函數返回值時
  • block賦值給__strong指針時
  • block作為Cocoa API中方法名有usingBlock的方法參數時 如:[array enumeratorObjectsUsingBlock]
  • block作為GCD API的方法參數時

 

四、對象類型的auto變量

上面講述auto變量是Int等基本類型,現在改成對象類型,如Person類對象[ARC環境下面]

 1 typedef void(^ZXYBlock)(void);
 2 int main(int argc, const char * argv[]) {
 3     @autoreleasepool {
 4         ZXYBlock block ;
 5         {
 6             Person *person = [[Person alloc]init];
 7             person.age = 10;
 8             block = ^{
 9                 NSLog(@"---------%d", person.age);
10             };
11         }
12         NSLog(@"block執行完");
13     }
14     return 0;
15 }
16 
17 @interface Person : NSObject
18 @property (nonatomic, assign) int age;
19 @end
20 
21 @implementation Person
22 -(void)dealloc {
23     NSLog(@"person對象已釋放");
24 }
25 @end

將上面的代碼打breakPoint斷點在12行處,查看Person對象是否在打括號{}內釋放

發現在打印之前並沒有釋放person對象,猜想block引用了person,導致block執行完之后才被釋放(block當autoReleasePool執行完之后才會被釋放) 查看c++代碼

 查看main函數調用

 通過上面查看結構體struct __main_block_desc 里面多了兩個copy和dispose(相當於retain)對person進行捕捉到age變量,當block不被釋放,person對象也不會被釋放

當斷點改到14行,執行完block時,查看結果

 block被釋放,完成打印釋放

 總結:對象訪問的auto變量

當block內部訪問了對象類型的auto變量時

  • 如果block在棧上,將不會對auto變量產生強引用
  • 如果block被拷貝到堆上
  1. 會調用block內部的copy函數
  2. copy函數內部會調用Block_object_assign 函數
  3. Block_object_assign函數會根據auto變量的修飾符(__strong、__weak、__unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用

  • 如果block從堆上移除
  1. 會調用block內部的dispose函數
  2. dispose函數內部會調用_Block_object_dispose函數
  3. _Block_object_dispose函數會自動釋放引用的auto變量

 

以上就是block詳解一的內容,下一篇講述block剩下的知識點,歡迎關注!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM