Block

175 阅读14分钟

Block 是什么?

Block本质上也是一个oc对象,它内部也有一个isa指针。Block 是对一个函数指针及该函数调用所需的上下文环境的封装和实现

1.1 Block 底层窥探

想看 Block 的底层,我们需要将 OC 代码编译成 C++代码去查看。

先创建项目来一段 OC 的代码作为例子:(代码环境为 MRC

//项目环境为MRC
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
   @autoreleasepool {
   
       void (^block1)(void) = ^{
           NSLog(@"Hello");
       };
       
       int a = 10;
       void (^block2)(int) = ^(int x){
           NSLog(@"Hello1 - %d",a); //a为值传递,block直接捕获a的值
       };
       
       __block int b = 20;
       void (^block3)(int) = ^(int x){
           b += x;
           NSLog(@"Hello2 - %d",b); //b为指针传递,block直接捕获b的指针
       };
       block1();
       block2(10);
       block3(10);
       
       NSLog(@"%@ %@ %@ %@", [block1 class], [block2 class],[block3 class], [[^{
           NSLog(@"%d",a);
       } copy]class]);
   }
   return 0;
}

把这个 main.m 文件编译成 C++ 文件:

1.打开终端进入 main.m 文件所在的文件夹
2.在终端输入一下代码:
    clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m

成功之后会在同文件夹下看到 main.cpp 文件,这个文件就是编译过后的 C++ 文件。

那么 main 函数会被编译成:

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

        void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        int a = 10;
        void (*block2)(int) = ((void (*)(int))&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, a));

        __attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
        void (*block3)(int) = ((void (*)(int))&__main_block_impl_2((void *)__main_block_func_2, &__main_block_desc_2_DATA, (__Block_byref_b_0 *)&b, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
        ((void (*)(__block_impl *, int))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2, 10);
        ((void (*)(__block_impl *, int))((__block_impl *)block3)->FuncPtr)((__block_impl *)block3, 10);

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_yd_n5hkyqj559d277b2twh255w80000gn_T_main_b954c6_mi_3, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block1, sel_registerName("class")), ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block2, sel_registerName("class")),((Class (*)(id, SEL))(void *)objc_msgSend)((id)block3, sel_registerName("class")), ((Class (*)(id, SEL))(void *)objc_msgSend)((id)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__main_block_impl_3((void *)__main_block_func_3, &__main_block_desc_3_DATA, a)), sel_registerName("copy")), sel_registerName("class")));
    }
    return 0;
}

可以看到 OC 基本上和 C++ 一一对应的。

先来看上述三个 Block 的定义:

block1:

void (block1)(void) = ((void ()())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

block2:

void (block2)(int) = ((void ()(int))&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, a));

block3:

void (block3)(int) = ((void ()(int))&__main_block_impl_2((void *)__main_block_func_2, &__main_block_desc_2_DATA, (__Block_byref_b_0 *)&b, 570425344));

大致一看有一些共同点,我先分块标出然后一块一块看:

截屏2021-04-01_下午4_31_23.png

__main_block_impl_1() 结构体

这里的例子都拿 __main_block_impl_1 来解析,__main_block_impl_0和__main_block_impl_2都是一样,以此类推。

main.cpp 找到这个结构体的定义:

struct __main_block_impl_1 {
  struct __block_impl impl;
  struct __main_block_desc_1* Desc;
  int a;
  __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _a, int flags=0) : a(_a) {
  //赋值
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__main_block_imp_1 结构体内有一个同名结构体函数 __main_block_imp_1,构造函数中对一些变量进行了 赋值 最终会返回一个结构体。

__main_block_impl_1 结构体内的 __main_block_impl_1()函数需要传入四个参数:

(void *)__main_block_func_1

&__main_block_desc_1_DATA

int _a

int flags=0) : a(_a)

其中,flags 有初始值,可以省略不传, a(_a)则表示传入的 _a 参数会自动赋值给 a 成员,相当于a = _a。

__main_block_impl_1()中的__block_impl结构体

__main__block_impl第一个变量就是 __block_impl 结构体,其结构为

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

可以看到 __block_impl 结构体内部就有一个 isa 指针,由此说明 block 本质上就是一个oc对象。

另外根据下面的代码

__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _a, int flags=0) : a(_a) {
  //赋值
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

__block_impl 结构体中 isa 指针被赋值为 _NSConcreteStackBlock 的地址,可以理解为其类对象地址,block就是 _NSConcreteStackBlock 类型的。

__main_block_func_1函数 封装了 block 的代码块,那么 FuncPtr 则存储着__main_block_func_1 函数的地址。

Desc 指向 __main_block_desc_1 结构体对象,其中存储__main_block_impl_1 结构体所占用的内存。

(void *)__main_block_func_1

cpp 文件中查看定义:


static void __main_block_func_1(struct __main_block_impl_1 *__cself, int x) {
  int a = __cself->a; // 取出 block 中 a 的值

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yd_n5hkyqj559d277b2twh255w80000gn_T_main_b954c6_mi_1,a);
        }

__main_block_func_1 函数中首先 取出 block 中 a 的值,紧接着可以看到NSLog,可以发现这段代码恰恰是我们在block块中写下的代码。

那么 __main_block_func_1 函数中其实存储着我们block中写下的代码。而__main_block_impl_1 函数中传入的是 (void *)__main_block_func_1,也就说将我们写在block块中的代码封装成 __main_block_func_1 函数,并将 __main_block_func_1 函数的地址 传入了__main_block_impl_1 的构造函数中保存在结构体内(作为__main_block_func_1第一个参数使用)。

&__main_block_desc_1_DATA

cpp文件中查看定义:

static struct __main_block_desc_1 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1)};

可以看出 __main_block_desc_1 中存储着两个参数:

size_t reserved

size_t Block_size //存储着 __main_block_impl_1 的占用空间大小

其中,reserved 赋值为0,Block_size存储着 __main_block_impl_1 的占用空间大小。最终将__main_block_desc_1 结构体的地址 传入 __main_block_func_1 中赋值给 desc(__main_block_func_1 的第二个参数)。

a 参数

参数 a 也就是我们定义的局部变量。

因为在 block 块中使用到 a 局部变量,所以在block声明的时候这里才会将 a 作为参数传入,也就说block会捕获局部变量 a, 如果没有在 block 中使用参数a ,这里将只会传入(void *)__main_block_func_1,&__main_block_desc_1_DATA 两个参数。比如上面的 block0的定义,只用到了两个参数__main_block_func_0 和 __main_block_desc_0_DATA。

block0:
(void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)))

1.2 block底层结构图

截屏2021-04-01 下午11.21.54.png

2. Block 类型

前面看源码的时候我们看到 block 的 isa 指针指向了_NSConcreteStackBlock 类对象地址:

impl.isa = &_NSConcreteStackBlock;

那么 block的类型就是_NSConcreteStackBlock 吗?

我们还拿最开始的例子,稍作修改:

 NSLog(@"%@ %@ %@ %@ %@", [block1 class], [block2 class],[block3 class], [[^{
      NSLog(@"%d",a);
 } copy]class],[[block3 copy]class]);

输出为:

2021-04-01 23:37:11.158112+0800 blk[1547:42227] __NSGlobalBlock__ __NSStackBlock__ __NSStackBlock__ __NSMallocBlock__ __NSMallocBlock__

通过输出看到block1的类型为:__NSGlobalBlock__ ,block2的类型为:__NSStackBlock__ ,block3的类型为:__NSStackBlock__ ,[block3 copy] 的类型为:__NSMallocBlock__ ,并不是 C++ 代码源码中 block 的isa指针指向的 _NSConcreteStackBlock 类型地址。我们可以猜测runtime运行时过程中也许对类型进行了转变。最终类型当然以runtime运行时类型也就是我们打印出的类型为准。

2.1 block的三种类型

autu变量:局部变量默认修饰为auto,因此auto关键字可以省略不写。autu变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在,退出该程序块时销毁。

局部变量中的auto变量与static变量是相对而言的。

下面在看一组代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 1. 内部没有调用外部变量的block  __NSGlobalBlock__
        void (^block1)(void) = ^{
            NSLog(@"block1");
        };
        // 2. 内部调用外部auto变量的block     __NSStackBlock__
        int a = 10;
        void (^block2)(int) = ^(int x){
            NSLog(@"block2 - %d",a); //a为值传递,block直接捕获a的值
        };
        // 3. 内部调用外部static变量的block     __NSGlobalBlock__
        static int m = 30;
        void (^block3)(void) = ^{
            NSLog(@"block3 - %d",m); //m为指针传递,block直接捕获m的指针
        };
        
        block1();
        block2(10);
        block3();
        
        // 3. 调用block 的copy           __NSMallocBlock__
        NSLog(@"block1类型%@ \n\nblock2类型%@ \n\nblock3类型%@ \n\nblock直接copy类型%@ \n\n[block1 copy]类型%@ \n\n[block2 copy]类型%@ \n\n[block3 copy]类型%@", [block1 class], [block2 class],[block3 class], [[^{
            NSLog(@"%d",a);
        } copy]class],[[block1 copy]class],[[block2 copy] class],[[block3 copy] class]);
    }
    return 0;
}

控制台输出为:

block1类型__NSGlobalBlock__ 

block2类型__NSStackBlock__ 

block3类型__NSGlobalBlock__ 

block直接copy类型__NSMallocBlock__ 

[block1 copy]类型__NSGlobalBlock__ 

[block2 copy]类型__NSMallocBlock__ 

[block3 copy]类型__NSGlobalBlock__

那么可以归纳为以下三类情况:

block类型环境存储区域
__NSGlobalBlock__没有访问auto变量或者访问了static变量数据段(已经初始化的全局变量、静态变量,程序运行结束时回收)
__NSStackBlock__访问了auto变量栈区
__NSMallocBlock____NSStackBlock__调用copy后堆区

另外:

__NSGlobalBlock__ 类型的block 在调用了 copy以后,还是__NSGlobalBlock__ 类型。

2.2 对 block 使用 copy

先看一个例子:

//整个项目处于MRC环境
#import <Foundation/Foundation.h>
void (^testBlock)(void);

void creatABlock() {
    //创建一个 __NSStackBlock__
    int a = 10;
    testBlock = ^{
        NSLog(@"a == %d",a);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        creatABlock();
        testBlock();
    }
    return 0;
}

程序输出:

2021-04-02 11:26:05.272599+0800 blk[2318:68865] a == -272632568

输出的并不是 a == 10,为什么会输出 a == -272632568?

因为上述代码中创建的block是 __NSStackBlock__ 类型的,因此block是存储在栈中的,那么当 creatABlock() 函数执行完毕之后,栈内存中block所占用的内存已经被系统回收,但是当通过 testBlock() 去执行 block 的时候,由于block所占用的内存已经被系统回收,所以有可能出现错乱的数据。

那么应该如何避免这种情况?

可以通过 copy__NSStackBlock__ 类型的block 转化__NSMallocBlock__ 类型的block,将block存储在堆中,以下是修改后的代码。

//整个项目处于MRC环境
#import <Foundation/Foundation.h>
void (^testBlock)(void);

void creatABlock() {
    //创建一个 __NSStackBlock__ 并通过 copy 使其转换成 __NSMallocBlock 类型
    int a = 10;
    testBlock = [^{
        NSLog(@"a == %d",a);
    } copy];
    [testBlock release];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        creatABlock();
        testBlock();
    }
    return 0;
}

控制台输出:

2021-04-02 11:33:48.081374+0800 blk[2410:72797] a == 10

在平时开发过程中MRC环境下经常需要使用copy来保存block,将栈上的block拷贝到堆中,即使栈上的block被销毁,堆上的block也不会被销毁,需要我们自己调用release操作来销毁。而在ARC环境下系统会自动调用copy操作,使block不会被销毁,总之,对 block 调用 copy 操作,目的就是使得这样在作用域外调用该block程序不出现问题。在ARC下对Block使用关键字 copy 是MRC 保留下来的一个传统,在ARC下,使用 strong 和 copy 都可以。

ARC环境下自动将block进行一次copy操作情形

1. block作为Cocoa API中方法名含有usingBlock的方法参数时:

NSArray *array = @[@1,@4,@5];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            // code
}];

2. block作为GCD API的方法参数时

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
            
});    
       
        
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //code to be executed after a specified delay
});


3. block作为函数返回值时

//整个项目处于ARC环境
#import <Foundation/Foundation.h>
typedef void (^TestBlock)(void);

TestBlock creatABlock() {
    //创建一个 __NSStackBlock__
    int a = 10;
    TestBlock block = ^{
        NSLog(@"a == %d",a);
    };
    return block;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        TestBlock block2 = creatABlock();
        block2();
    }
    return 0;
}

3.Block 变量的捕获

再来看一段代码:

/整个项目处于MRC环境
#import <Foundation/Foundation.h>
int c = 30; //全局变量
static int d = 40;//全局静态变量
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10; //局部auto 变量
        static int b = 20;  //局部static静态变量
        void(^block1)(void) = ^void {
            NSLog(@"block1 a = %d",a);
        };
        
        void(^block2)(void) = ^void(void) {
            NSLog(@"block2 b = %d",b);
        };
        
        void(^block3)(void) = ^void(void) {
            NSLog(@"block2 a + b  = %d",a + b);
        };
        
        void(^block4)(void) = ^void(void) {
            NSLog(@"block3 c = %d",c);
        };
        
        void(^block5)(void) = ^void(void) {
            NSLog(@"block4 d = %d",d);
        };
        block1();
        block2();
        block3();
        block4();
        block5();
    }
    return 0;
}

控制台输出:

2021-04-02 13:20:30.659076+0800 blk[3651:121229] block1 a = 10
2021-04-02 13:20:30.659532+0800 blk[3651:121229] block2 b = 20
2021-04-02 13:20:30.659675+0800 blk[3651:121229] block2 a + b  = 30
2021-04-02 13:20:30.659721+0800 blk[3651:121229] block3 c = 30
2021-04-02 13:20:30.659754+0800 blk[3651:121229] block4 d = 40

同样的我们编译成 cpp 文件可以看到:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 10;
        static int b = 20;
        void(*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a)); //直接捕获a的值

        void(*block2)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, &b));//捕获 b 的地址

        void(*block3)(void) = ((void (*)())&__main_block_impl_2((void *)__main_block_func_2, &__main_block_desc_2_DATA, a, &b)); //捕获a的值和b的地址

        void(*block4)(void) = ((void (*)())&__main_block_impl_3((void *)__main_block_func_3, &__main_block_desc_3_DATA)); //没有捕获任何值

        void(*block5)(void) = ((void (*)())&__main_block_impl_4((void *)__main_block_func_4, &__main_block_desc_4_DATA)); ////没有捕获任何值
        ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
        ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
        ((void (*)(__block_impl *))((__block_impl *)block3)->FuncPtr)((__block_impl *)block3);
        ((void (*)(__block_impl *))((__block_impl *)block4)->FuncPtr)((__block_impl *)block4);
        ((void (*)(__block_impl *))((__block_impl *)block5)->FuncPtr)((__block_impl *)block5);
    }
    return 0;
}

局部 auto 变量

截屏2021-04-02_下午1_22_43.png

自动变量a会被捕获到 block 内部,也就是说 block 内部会专门新增加一个参数来存储变量a 的值。 auto只存在于局部变量中,访问方式为 值传递,通过上述对 a 参数的解释我们也可以确定确实是值传递。

如果我们在block1()调用之前对a进行赋值,例如:

...
a = 20;
block1();
...

输出a的值还是10,因为auto 变量a的值被 block1直接捕获,捕获a的值,因此无法再后面再获取a新赋的值。

注:

冒号后面跟的是赋值,这种写法是C++的特性。
A( int aa, int bb ):a(aa),b(bb) {
}
相当于
A( int aa, int bb ) {
    a=aa;
    b=bb;
}

局部 static 变量

static block.png

static修饰的局部变量,在程序生命周期内不会被销毁,通过上面的图可以看出 b 传递的是*b 也就是说直接通过 值传递 把内存地址传进去进行修改了。当然我们如果在block2调用之前对 b = 50 进行修改,由于b的地址变动,内部存储的值发生了改变,那么由于block2捕获的是b的地址,自然获取到的b的值是改动后的值也就是 b = 50。

block3再次印证了 auto变量是值传递,static 变量是指针传递。

截屏2021-04-02_下午1_23_1433333.png

如果我们在block2()调用之前对b进行赋值,例如:

...
b = 30;
block2();
...

输出 b 的值会是30,因为 static 变量 b 的值通过指针(地址)传递的方式被 block2 捕获,因此虽然对 b 赋予了新值,但是由于 b 的地址未变,所以会获取到 b 的新值。

全局变量 (无论是不是static)

截屏2021-04-02_下午1_23_3999999.png

截屏2021-04-02_下午1_23_49ddddd.png

从 cpp 文件可以看出来,并没有捕获全局变量c和d,访问的时候,是直接去访问的,根本不需要捕获。

随时修改 c 和 d 的值后调用 block4() 和 block5(),block都会输出最新的值。

总结

变量类型是否捕获到block内部访问方式
auto变量值传递
static变量指针传递
全局直接访问

4. 如何修改变量

使用__block 修改局部变量

先来看一段代码:

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

    }
    return 0;
}

这样写的结果是直接报错,提示Variable is not assignable (missing __block type specifier) ---- 变量不可分配

提示用 __block 来修饰auto变量,那么修改代码为:

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block auto int a = 10;
        void(^block)(void) = ^void(void) {
            a = 20;
            NSLog(@"a = %d",a);
        };
        block();

    }
    return 0;
}

输出为:

2021-04-02 16:19:21.547753+0800 blk[5683:205901] a = 20

可以看到变量 a 被重新修改为 20 。

static修改局部变量为静态局部变量

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int a = 10;
        void(^block)(void) = ^void(void) {
            a = 20;
            NSLog(@"a = %d",a);
        };
        block();

    }
    return 0;
}

输出为:

2021-04-02 16:34:25.757481+0800 blk[5907:214686] a = 20

使用全局变量

#import <Foundation/Foundation.h>
int a = 10;//使用全局变量
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^void(void) {
            a = 20;
            NSLog(@"a = %d",a);
        };
        block();

    }
    return 0;
}

输出为:

2021-04-02 16:35:31.515647+0800 blk[5930:215580] a = 20

5.循环引用

将环境切换为ARC,创建一个 Person类:

Person.h 文件
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
@property(nonatomic, copy) void (^testBlock)(void);
@property (assign, nonatomic) int age;
@end


Person.m 文件

#import "Person.h"

@implementation Person
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

main.h 文件

#import <Foundation/Foundation.h>
#import "Person.h"
int a = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.age = 10;
        person.testBlock = ^{
            NSLog(@"person.age--- %d",weakPerson.age);
        };
    }
        return 0;
}

调用发现程序运行时,Person 的 dealloc 方法并没有执行,说明 person 在大括号结束之后,person依然没有被释放,产生了循环引用。

为什么会产生循环引用?

因为在 Person类中testBlock 是其属性,通过 copy 持有,而 testBlock内部有个强指针指向person,person 和 testBlock互相持有,造成了循环引用。

将上述代码通过 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m 编译为 cpp 文件可见:

截屏2021-04-03_上午12_00_222222.png

那么怎样能解除循环引用呢?

5.1 使用 __weak 解决循环引用

我们将上面的代码稍作修改为:

nt main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.age = 10;
        __weak Person *weakPerson = person;
        person.testBlock = ^{
            NSLog(@"person.age--- %d",weakPerson.age);
        };
    }
        return 0;
}

继续编译为 cpp 文件为:

截屏2021-04-03_上午12_24_1555555.png

程序运行控制台输出:

2021-04-03 00:54:38.432005+0800 blk[2928:80464] -[Person dealloc]

可以看出通过 __weak 打破了循环引用,Person成功释放,调用了 dealloc。

5.2 其他可以解决循环引用的方法还有:

1、__unsafe_unretained解决循环引用 :

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.age = 10;
        __unsafe_unretained Person *weakPerson = person;

        person.testBlock = ^{
            NSLog(@"person.age--- %d",weakPerson.age);
        };
        NSLog(@"--------");

    }
    return 0;
}

虽然__unsafe_unretained可以解决循环引用,但是最好不要用,因为

  • __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
  • __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变。

2、__block解决循环引用 :

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       __block Person *person = [[Person alloc] init];
        person.age = 10;
        person.testBlock = ^{
            NSLog(@"person.age--- %d",person.age);
            //这一句不能少
            person = nil;
        };
        // 必须调用一次
        person.block();
        NSLog(@"--------");
    }
    return 0;
}

上面的代码中,也是可以解决循环引用的。但是需要注意的是,person.block();必须调用一次,为了执行person = nil;

5.3 __weak 和 __strong

__weak typeof(self) weakSelf = self;
person.block = ^{
    __strong typeof(weakSelf) myself = weakSelf;
    NSLog(@"age is %d", myself->_age);
};

在 block 内部重新使用 __strong 修饰 self 变量 是为了在 block 内部有一个强指针指向 weakSelf 避免在 block 调用的时候 weakSelf 已经被销毁