iOS 開發(fā) Block 原理分析

前言

無論在面試還是在工作中,總會碰到 block 是什么?block 循環(huán)引用怎么辦?block 修飾符使用什么?等等這種類似的問題。

一、 什么是 block

1.1、Demo分析

1.1.1、Demo1

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        /// 下面的代碼,就是一個簡單的 block
        ^{
            NSLog(@"Hello Block");
        };
        
    }
    return 0;
}

分析:上面的代碼,就是一個最簡單的 block 但是 NSLog 里面的代碼不會被打印出來,因為這個 block 沒人調(diào)用,所以永遠不會執(zhí)行。

1.1.2、Demo2

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

分析:如上,運行程序,控制臺會打印出 age--18。

1.1.3、分析 block 內(nèi)部實現(xiàn)

  • 通過 clang 編譯可以將 OC 代碼轉(zhuǎn)化成 C++ 代碼,來查看 block 底層的實現(xiàn)原理
  • 在終端上輸入 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 會在main.m 這級目錄下生成一個 main.cpp 文件,就是我們想要的 轉(zhuǎn)化后的代碼
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// 這個就是 block 內(nèi)部的結(jié)構(gòu),
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  // c++ 的構(gòu)造函數(shù)(類似于 OC 的 init 方法),返回一個結(jié)構(gòu)體對象。
  // age(_age) 這句代碼,就是把 _age 賦值給 age 
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// 封裝了 block 執(zhí)行邏輯的函數(shù),傳入到 fp,fp 在賦值到 impl.funcPtr
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_f__7ngz5gzx5sjgs4dlqrh7t58w0000gn_T_main_679898_mi_0, age);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
// 這句代碼對應(yīng)我們上面的 main 函數(shù)
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int age = 18;
            // 定義 block 變量
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
         // 執(zhí)行 block 內(nèi)部的代碼
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

分析上面的代碼,在 main 函數(shù)里我們對比,轉(zhuǎn)化后的 C++ 代碼和 原生的 OC 代碼

圖片.png

__main_block_impl_0 這個結(jié)構(gòu)體就是 block 的本來面目

__main_block_impl_0

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  // c++ 的構(gòu)造函數(shù)(類似于 OC 的 init 方法),返回一個結(jié)構(gòu)體對象。
  // age(_age) 這句代碼,就是把 _age 賦值給 age 
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

上面這個結(jié)構(gòu)體的構(gòu)造函數(shù),傳入的參數(shù):void *fp、struct __main_block_desc_0 *desc、int _age、int flags=0

由于 int flags=0 傳入的是常亮數(shù)值 0 因此可以忽略,int _age 這個參數(shù)是因為外面定義的 局部變量,也可以忽略,所以 這個構(gòu)造函數(shù)的必須參數(shù)有兩個 void *fp、struct __main_block_desc_0 *desc

上面的 C++ 代碼中在 可以看出 void *fp 對應(yīng)的是 &__main_block_impl_0、struct __main_block_desc_0 *desc 對應(yīng)的是 &__main_block_desc_0_DATA ,fp又賦值到了imp里面的 FuncPtr,desc 賦值到 Desc 就是 當前結(jié)構(gòu)體的 Desc。

__main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_f__7ngz5gzx5sjgs4dlqrh7t58w0000gn_T_main_679898_mi_0, age);
        }

上面是 block 執(zhí)行邏輯的函數(shù),傳入到 fp,fp 在賦值到 impl.funcPtr

__block_impl

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

__main_block_desc_0

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

這個結(jié)構(gòu)體存了 block 的大小。

1.1.4、Demo3 捕獲 auto 變量的 block

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

如上,我們修改了局部變量的 age 修改為20,但是運行后,block 里面打印的結(jié)果仍為 18.

  • 這是因為 block 捕獲了 age = 18,進行了值傳遞,相當于直接把 age= 18 賦值給了 age, 無論外面怎么修改,都不會改變 age 的值,
  • C 語言會在我們定義局部變量的時候,自動給我們的屬性加上 auto 修飾

1.1.5、Demo4 捕獲 static 變量的 block

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

修改 height 的值,運行代碼,結(jié)果為 age--18,height--180

這是為什么呢,再次編譯運行生成 C++ 代碼

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int age = 18;
        static int height = 170;

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
        age = 20;
        height = 180;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

從上面的 C++ 代碼可以看出傳遞給block 的height 是地址傳遞,指針傳遞

  • block 內(nèi)部捕獲的是 *height 而不是 height。

1.1.6、Demo5 全局變量

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

運行上面的代碼,打印結(jié)果為 age--20,height--180,同樣,我們分析 生成的 C++ 代碼

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        age = 20;
        height = 180;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
  • block 并沒有捕獲 任何全局變量

1.2、總結(jié)

1.2.1、block 的內(nèi)存結(jié)構(gòu)

圖片.png
圖片.png

1.2.2、block 本質(zhì)

  • block 本質(zhì)上也是一個 OC 對象,它內(nèi)部也有一個 isa 指針
  • block 是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的 OC 對象

二、block 的變量捕獲

為了保證 block 內(nèi)部能正常訪問外部的變量,block 有一個變量捕獲機制

  • 如果變量的類型是局部變量,無論是 auto 還是 static 修飾,都會被捕獲到 block 內(nèi)部,但是 auto 變量是值傳遞,static 是指針傳遞。
  • 如果變量類型是全局變量,不會被 block 捕獲到內(nèi)部,直接使用。
變量類型 是否能捕獲到 block 內(nèi)部 訪問方式
局部變量 auto 值傳遞
局部變量 static 指針傳遞
全局變量 直接訪問

為什么 block 要捕獲局部變量的值呢,auto:自動變量,離開作用域就銷毀

三、block 的類型

3.1、block 的對象特性

我們知道 block 的本質(zhì)就是OC 對象,所以O(shè)C對象的一些方法同樣適用于 block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
      void (^block)(void) = ^{
          NSLog(@"Hello");
      };
      
      NSLog(@"%@", [block class]);
      NSLog(@"%@", [[block class] superclass]);
      NSLog(@"%@", [[[block class] superclass] superclass]);
      NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);

    }
    return 0;
}

2020-12-20 16:55:50.239327+0800 MyBlock[18424:5457366] __NSGlobalBlock__
2020-12-20 16:55:50.239799+0800 MyBlock[18424:5457366] __NSGlobalBlock
2020-12-20 16:55:50.239839+0800 MyBlock[18424:5457366] NSBlock
2020-12-20 16:55:50.239871+0800 MyBlock[18424:5457366] NSObject

得出繼承關(guān)系為 NSGlobalBlock -> __NSGlobalBlock -> NSBlock ->NSObject

3.2、block 的類型查詢

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
      int a = 10;
      
      // 堆:動態(tài)分配內(nèi)存,需要程序員申請申請,也需要程序員自己管理內(nèi)存
      void (^block1)(void) = ^{
          NSLog(@"Hello");
      };
      
      int age = 10;
      void (^block2)(void) = ^{
          NSLog(@"Hello - %d", age);
      };
      
      NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
          NSLog(@"%d", age);
      } class]);

    }
    return 0;
}

在ARC 環(huán)境下:
2020-12-20 17:00:31.886757+0800 MyBlock[18515:5460672] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
在 MRC 環(huán)境下:
2020-12-20 17:26:28.303551+0800 MyBlock[19030:5476820] __NSGlobalBlock__ __NSStackBlock__ __NSStackBlock__
在 MRC 環(huán)境下使用 copy 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      int age = 10;
      void (^block2)(void) = [^{
          NSLog(@"Hello - %d", age);
      } copy];
    }
    return 0;
}
同樣在 MRC 環(huán)境下,進行 copy 操作后打印結(jié)果和 ARC 環(huán)境下一樣
2020-12-20 17:27:42.096753+0800 MyBlock[19064:5478650]  __NSMallocBlock__ 

如上,我們看打印結(jié)果得知 block 有 3 種類型,可以通過調(diào)用 class 方法或者 isa 指針查看具體類型,最終都是繼承自 NSBlock 類型

__NSGlobalBlock__(_NSConcreteGlobalBlock)沒有訪問 auto 變量
__NSMallocBlock__(_NSConcreteMallocBlock)__NSStackBlock__ 調(diào)用了 copy
__NSStackBlock__(_NSConcreteStackBlock)訪問了 auto 變量,(在ARC環(huán)境下顯示NSMallocBlock,在 MRC 環(huán)境下,顯示 NSStackBlock

每一種類型的 block 調(diào)用 copy 后的結(jié)果如下:

Block 的類 副本源的配置存儲域 復制效果
_NSConcreteStackBlock 從棧復制到堆
_NSConcreteGlobalBlock 程序的數(shù)據(jù)區(qū)域 什么也不做
_NSConcreteMallocBlock 引用計數(shù) + 1

內(nèi)存分布

圖片.png

四、block 的 copy

4.1、自動復制

在 ARC 環(huán)境下,編譯器會根據(jù)情況自動將棧上的 block 復制到堆上

4.1.1、block 作為函數(shù)返回值

typedef void (^MyBlock)(void);

MyBlock myblock() {
    return ^{
        NSLog(@"我被調(diào)用了");
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        MyBlock block =  myblock();
   
        block();

    }
    return 0;
}

會打印出結(jié)果,如果沒有進行 copy

4.1.2、將 block賦值給 __strong 指針時

typedef void (^MyBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        int age = 10;
        MyBlock block = ^{
            NSLog(@", %d", age);
        };
   
        block();
        NSLog(@"%@", [block class]);

    }
    return 0;
}

打印 [block class] 顯示為NSMallocBlock ,證明是NSStackBlock copy 之后的類型。

4.1.3、block 作為 Cocoa API 中方法名含有 usingBlock的方法參數(shù)時

// 如 NSArray 里面的函數(shù), 此時的 block 都是在堆上的,都是進行了 copy 的
NSArray *arr = @[];
[arr sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
            
}];

4.1.4、block 作為GCD API 的方法參數(shù)時

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
            
});

4.2、block屬性的建議寫法

4.2.1、MRC 環(huán)境下

  • @property (copy, nonatomic) void (^block)(void);

4.2.2、ARC 環(huán)境下

  • @property (strong, nonatomic) void (^block)(void);
  • @property (copy, nonatomic) void (^block)(void);

4.3、對象類型的 auto 變量

4.3.1、當 block內(nèi)部訪問了對象類型的 auto 變量時

#import "MyPerson.h"
typedef void (^MyBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        MyBlock block;
        {
            MyPerson *person = [[MyPerson alloc] init];
            person.age = 18;
            
            block = ^{
                NSLog(@"-----person.age===%d", person.age);
            };
        }
        
        NSLog(@"-----");
    }
    return 0;
}

轉(zhuǎn)化C++ 代碼

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

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}


static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
  • 如果 block 是在棧上
    • 將不會對 auto 變量產(chǎn)生強引用。
  • 如果 block 被拷貝到堆上
    • 會調(diào)用 block 內(nèi)部的 copy 函數(shù)
    • copy 函數(shù)內(nèi)部會調(diào)用 _Block_object_assign 函數(shù)
    • _Block_object_assign 函數(shù)會根據(jù) auto 變量的修飾符(__strong、__weak、__unsafe_unretained)作出相對應(yīng)的操作,形成強引用或者弱引用
  • 如果 block 從堆中移除
    • 會調(diào)用 block 內(nèi)部的dispose 函數(shù)
    • dispose 函數(shù)內(nèi)部會調(diào)用 _Block_object_dispose 函數(shù)
    • _Block_object_dispose 函數(shù)會自動釋放引用的 auto 變量
函數(shù) 調(diào)用時機
copy 函數(shù) 棧上的 block 復制到堆時
dispose 函數(shù) 堆上的 block 被廢棄時

把上面的 person 使用 __weak 修飾

__weak MyPerson *weakPerson = person;
block = ^{
    NSLog(@"-----person.age===%d", weakPerson.age);
};

使用 __weak 修飾時,在使用clang轉(zhuǎn)換OC為C++代碼時,可能會遇到以下問題

/var/folders/f_/7ngz5gzx5sjgs4dlqrh7t58w0000gn/T/main-643d6e.mi:28880:28: error:
      cannot create __weak reference because the current deployment target does
      not support weak references
            __attribute__((objc_ownership(weak))) MyPerson *weakPerson = person;
                           ^
1 error generated.

針對 cannot create __weak reference in file using manual reference 這個問題,解決方案:支持ARC、指定運行時系統(tǒng)版本,比如增加 -fobjc-arc -fobjc-runtime=ios-8.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;
  MyPerson *__weak weakPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

五、__block 修飾符

執(zhí)行 下面的代碼,顯然會報錯,從上面的代碼中得知,block 中的 age 是block內(nèi)部的,想要main 函數(shù)中的 age 是不可能的。

typedef void (^MyBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        int age = 14;
        
        MyBlock block = ^{
            age = 18;//報錯 Variable is not assignable (missing __block type specifier)
            NSLog(@"%d", age);
        };
    }
    return 0;
}

如果 block 想要修改內(nèi)部的變量,可以使用 static 或者 全局變量,但是,

  • 這種做法會一直占用內(nèi)存的空間
  • 使用 static 修飾,會修改 block的類型,使用 static 修飾后,block 的類型變成了 ”NSGlobalBlock“,之前為”NSMallocBlock

5.1、使用__block 修飾,修改變量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 14;
        MyBlock block = ^{
            age = 18;
            NSLog(@"%d", age);
        };
        block();
                NSLog(@"%@", [block class]);
    }
    return 0;
}

使用 __block 修飾后,block 的類型 依舊為 ”NSMallocBlock“,使用后發(fā)現(xiàn) block 內(nèi)部 有一個 __Block_byref_age_0 引用這 age這個指針,__Block_byref_age_0 里面有一個 age 變量,我們修改 age 其實就是修改 __Block_byref_age_0 里面的 age這個值。__forwarding 這個指針時指向自己的一個 指針。

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref

            (age->__forwarding->age) = 20; // 真正修改 age 的地方
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_f__7ngz5gzx5sjgs4dlqrh7t58w0000gn_T_main_f41d15_mi_0, (age->__forwarding->age));
        }

5.2、__block 修飾符 原理

  • __block 可以用于解決 block 內(nèi)部無法修改 auto 變量值得問題
  • __block 不能修飾全局變量、靜態(tài)變量
  • 使用 ____block 修飾時,編譯器會將 ____block 變量包裝成一個對象。

5.3、___block 的內(nèi)存管理

  • 當 block 在棧上時,并不對 __block 變量產(chǎn)生強引用
  • 當 block 在堆上時
    • 會調(diào)用 block 內(nèi)部的 copy 函數(shù)
    • copy 函數(shù)內(nèi)部會調(diào)用 _Block_object_assign 函數(shù)
    • _Block_object_assign 函數(shù)會對 __block 變量形成強引用
圖片.png
  • 當block 從堆中移除時
    • 會調(diào)用 block 內(nèi)部的dispose 函數(shù)
    • dispose 函數(shù)內(nèi)部會調(diào)用 _Block_object_dispose 函數(shù)
    • _Block_object_dispose 函數(shù)會自動釋放引用的 __block 變量
圖片.png

5.4、__block 的 forwarding 指針

[圖片上傳失敗...(image-a5af69-1608559220453)]

5.5、對象類型的 auto 變量、__block 變量

  • 當 block 在棧上時,對它們都不會產(chǎn)生強引用

  • 當 block 拷貝到堆上時,都會通過 copy 函數(shù)來處理它們

    • __block變量(假設(shè)變量名叫做a)
    • _Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);
  • 對象類型的 auto 變量(假設(shè)變量名叫做p)

    • _Block_object_assign((void)&dst->p, (void)src->p, 3/BLOCK_FIELD_IS_OBJECT/);
  • 當 block 從堆上移除時,都會通過 dispose 函數(shù)來釋放它們

    • __block變量(假設(shè)變量名叫做a)
    • _Block_object_dispose((void)src->a, 8/BLOCK_FIELD_IS_BYREF*/);
  • 對象類型的auto變量(假設(shè)變量名叫做p)

    • _Block_object_dispose((void)src->p, 3/BLOCK_FIELD_IS_OBJECT*/);
  • 對象 BLOCK_FIELD_IS_OBJECT
    __block 變量 BLOCK_FIELD_IS_BYREF

六、被__block 修飾的對象類型

  • 當__block變量在棧上時,不會對指向的對象產(chǎn)生強引用
  • 當__block變量被copy到堆時
    • 會調(diào)用__block變量內(nèi)部的copy函數(shù)
    • copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
    • _Block_object_assign函數(shù)會根據(jù)所指向?qū)ο蟮男揎椃╛_strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強引用(retain)或者弱引用(注意:這里僅限于ARC時會retain,MRC時不會retain)
  • 如果__block變量從堆上移除
    • 會調(diào)用__block變量內(nèi)部的dispose函數(shù)
    • dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
    • _Block_object_dispose函數(shù)會自動釋放指向的對象(release)

六、循環(huán)引用

6.1、什么是循環(huán)引用

圖片.png
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MyPerson *p = [[MyPerson alloc] init];
        p.age = 18;
        p.block = ^{
            NSLog(@"age is %d", p.age);
        };
        
        NSLog(@"-------");
        
    }
    return 0;
}

// MyPerson

typedef void (^MyBlock) (void);

@interface MyPerson : NSObject

@property (copy, nonatomic) MyBlock block;
@property (assign, nonatomic) int age;

@end
  

上面的代碼產(chǎn)生了循環(huán)引用,block 里面的代碼 無法執(zhí)行

6.2、解決循環(huán)引用

6.2.1、ARC 環(huán)境

1、使用 __weak

  • 不會產(chǎn)生強引用,指向的對象銷毀時,會自動讓指針置為nil
圖片.png
MyPerson *p = [[MyPerson alloc] init];
__weak typeof(p) weakP = p;
p.age = 18;
p.block = ^{
    NSLog(@"age is %d", weakP.age);
};

2、使用 __unsafe_unretained

  • 不會產(chǎn)生強引用,不安全,指向的對象銷毀時,指針存儲的地址值不變
 MyPerson *p = [[MyPerson alloc] init];
__unsafe_unretained typeof(p) weakP = p;
p.age = 18;
p.block = ^{
    NSLog(@"age is %d", weakP.age);
};

3、使用 __block

  • 使用 __block 必須 調(diào)用 block,并把對象置為 nil
圖片.png
__block MyPerson *p = [[MyPerson alloc] init];
        
p.age = 18;
p.block = ^{
    NSLog(@"age is %d", p.age);
        p = nil;
    };
p.block();

6.2.2、MRC 環(huán)境

由于 MRC 環(huán)境不支持 __weak,所以只有兩種情況

1、__unsafe_unretained

  • 和 ARC 一樣

2、__block

  • 不需要置為 nil 和 手動調(diào)用 block
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容