# iOS Block的本質(三)

iOS Block的本質(三)

  • 上一篇文章iOS Block的本質(二)中已經介紹過block變量的捕獲,本文繼續探尋block的本質。

1. block對對象變量的捕獲,ARC 環境

  1. block通常使用過程當中都是對對象變量的捕獲,那麼對象變量的捕獲同基本數據類型變量相同嗎?
  2. 查看一下代碼思考:當在block中訪問的爲對象類型時,對象何時會銷燬?html

    // ARC環境下代碼
    typedef void (^Block)(void);
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Block block;
            {
                Person *person = [[Person alloc] init];
                person.age = 10;
    
                block = ^{
                    NSLog(@"------block內部%d",person.age);
                };
            } // 執行完畢,person沒有被釋放
            NSLog(@"--------");
        } // person 釋放
        return 0; 
    }
  3. 大括號執行完畢以後,person依然不會被釋放。上一篇文章提到過,person爲aotu變量,傳入的block的變量一樣爲person,即block有一個強引用引用person,因此block不被銷燬的話,peroson也不會銷燬。ios

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      Person * person;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };

2. block對對象變量的捕獲,ARC 環境

  1. 在MRC環境下即便block還在,person卻被釋放掉了。由於MRC環境下block在棧空間,棧空間對外面的person不會進行強引用。c++

    //MRC環境下代碼
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Block block;
            {
                Person *person = [[Person alloc] init];
                person.age = 10;
                block = ^{
                    NSLog(@"------block內部%d",person.age);
                };
                [person release];
            } // person被釋放
            NSLog(@"--------");
        }
        return 0;
    }
  2. block調用copy操做以後,person不會被釋放。iphone

    block = [^{
       NSLog(@"------block內部%d",person.age);
    } copy];
  3. 上文中也提到過,只須要對棧空間的block進行一次copy操做,將棧空間的block拷貝到堆中,person就不會被釋放,說明堆空間的block可能會對person進行一次retain操做,以保證person不會被銷燬。堆空間的block本身銷燬以後也會對持有的對象進行release操做。函數

  4. 也就是說棧空間上的block不會對對象強引用,堆空間的block有能力持有外部調用的對象,即對對象進行強引用或去除強引用的操做。spa

  5. 在__weak修飾以後,person在做用域執行完畢以後就被銷燬了。指針

    typedef void (^Block)(void);
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Block block;
            {
                Person *person = [[Person alloc] init];
                person.age = 10;
    
                __weak Person *weakPerson = person;
                block = ^{
                    NSLog(@"------block內部%d",waekPerson.age);
                };
            }
            NSLog(@"--------");
        }
        return 0;
    }
  6. 將代碼轉化爲c++來看一下上述代碼之間的差異。
    • 在轉換C++代碼時用__weak修飾變量,須要告知編譯器使用ARC環境及版本號不然會報錯,添加說明-fobjc-arc -fobjc-runtime=ios-8.0.0
    • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
    struct __main_block_impl_0 {
       struct __block_impl impl;
       struct __main_block_desc_0* Desc;
       Person *__weak person;
       __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _person, int flags=0) : person(_weakPerson) {
         impl.isa = &_NSConcreteStackBlock;
         impl.Flags = flags;
         impl.FuncPtr = fp;
         Desc = desc;
       }
     };
  7. 用__weak修飾的變量,在生成的__main_block_impl_0中也是使用__weak修飾捕獲。code

3. __main_block_copy\_0__main_block_dispose_0函數分析:

  1. 當block中捕獲對象類型的變量時,咱們發現block結構體__main_block_impl_0的描述結構體__main_block_desc_0中多了兩個參數copy和dispose函數,查看源碼:
    htm

  2. copy和dispose函數中傳入的都是__main_block_impl_0結構體自己。對象

  3. copy本質就是__main_block_copy_0函數,__main_block_copy_0函數內部調用_Block_object_assign函數,_Block_object_assign中傳入的是person對象的地址,person對象,以及8。

  4. dispose本質就是__main_block_dispose_0函數,__main_block_dispose_0函數內部調用_Block_object_dispose函數,_Block_object_dispose函數傳入的參數是person對象,以及8。

4. _Block_object_assign函數調用時機及做用

  1. 當block進行copy操做的時候就會自動調用__main_block_desc_0內部的__main_block_copy_0函數,__main_block_copy_0函數內部會調用_Block_object_assign函數。

  2. _Block_object_assign函數會自動根據__main_block_impl_0結構體內部的person是什麼類型的指針,對person對象產生強引用或者弱引用。能夠理解爲_Block_object_assign函數內部會對person進行引用計數器的操做,若是__main_block_impl_0結構體內person指針是__strong類型,則爲強引用,引用計數+1,若是__main_block_impl_0結構體內person指針是__weak類型,則爲弱引用,引用計數不變。

5. _Block_object_dispose函數調用時機及做用

  1. 當block從堆中移除時就會自動調用__main_block_desc_0中的__main_block_dispose_0函數,__main_block_dispose_0函數內部會調用_Block_object_dispose函數。

  2. _Block_object_dispose會對person對象作釋放操做,相似於release,也就是斷開對person對象的引用,而person到底是否被釋放仍是取決於person對象本身的引用計數。

6. 總結

  1. 一旦block中捕獲的變量爲對象類型,block結構體中的__main_block_desc_0會出兩個參數copy和dispose。由於訪問的是個對象,block但願擁有這個對象,就須要對對象進行引用,也就是進行內存管理的操做。好比說對對象進行retarn操做,所以一旦block捕獲的變量是對象類型就會會自動生成copy和dispose來對內部引用的對象進行內存管理。

  2. 當block內部訪問了對象類型的auto變量時,若是block是在棧上,block內部不會對person產生強引用。不論block結構體內部的變量是__strong修飾仍是__weak修飾,都不會對變量產生強引用。

  3. 若是block被拷貝到堆上。copy函數會調用_Block_object_assign函數,根據auto變量的修飾符(__strong,__weak,unsafe_unretained)作出相應的操做,造成強引用或者弱引用

  4. 若是block從堆中移除,dispose函數會調用_Block_object_dispose函數,自動釋放引用的auto變量。

7. 拓展

  1. 下列代碼person在什麼時候銷燬?在person 類中重寫 delloc 方法

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        Person *person = [[Person alloc] init];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",person);
        });
        NSLog(@"touchBegin----------End");
    }
    
    // Log : 打印結果 : touchBegin----------End
    // Log :打印結果 : <Person: 0x6080000080d0>
    // Log :打印結果 : person 對象調用 delloc
    • 上文提到過ARC環境中,block做爲GCD API的方法參數時會自動進行copy操做,所以block在堆空間,而且使用強引用訪問person對象,所以block內部copy函數會對person進行強引用。當block執行完畢須要被銷燬時,調用dispose函數釋放對person對象的引用,person沒有強指針指向時纔會被銷燬。
  2. 下列代碼person在什麼時候銷燬 ?

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        Person *person = [[Person alloc] init];
    
        __weak Person *weakPerson = person;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakPerson);
        });
        NSLog(@"touchBegin----------End");
    }
    // Log : 打印結果 : touchBegin----------End
    // Log :打印結果 : person 對象調用 delloc
    // Log :打印結果 : null
    // 結果是 person 先銷燬才執行block 打印結果爲 null
    • block中對weakPerson爲__weak弱引用,所以block內部copy函數會對person一樣進行弱引用,當大括號執行完畢時,person對象沒有強指針引用就會被釋放。所以block塊執行的時候打印null。
  3. 經過示例代碼進行總結。

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        Person *person = [[Person alloc] init];
    
        __weak Person *weakPerson = person;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
            NSLog(@"weakPerson ----- %@",weakPerson);
    
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"person ----- %@",person);
            });
        });
        NSLog(@"touchBegin----------End");
    }
    // Log : 打印結果 : touchBegin----------End
    // Log :1s 後 打印結果 : weakPerson -> <Person: 0x608000006050>
    // Log :3s 後 打印結果 : person -> <Person: 0x608000006050>
    // Log :打印結果 : person 對象調用 delloc
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        Person *person = [[Person alloc] init];
    
        __weak Person *waekP = person;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
            NSLog(@"person ----- %@",person);
    
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"weakP ----- %@",waekP);
            });
        });
        NSLog(@"touchBegin----------End");
    }
    // Log : 打印結果 : touchBegin----------End
    // Log :1s 後 打印結果 : person -> <Person: 0x608000006050>
    // Log :打印結果 : person 對象調用 delloc
    // Log :3s 後 打印結果 : weakPerson -> null
相關文章
相關標籤/搜索