block使用及其底层原理

一 block基本使用
二 block底层结构
三 block变量捕获
四 block的类型
五 block对象类型的自变量内存管理
六 _block讲解
七 block循环引用问题

block本质上也是一个OC对象,它内部也有个isa指针,block是封装了函数调用以及函数调用环境的OC对象,下面我们来看一下它的基本使用

一 block基本使用

它的基本使用我们用一段代码来演示

#include<stdio.h>
#import <Foundation/Foundation.h>

 int main()
{
    //匿名block
    ^{
        NSLog(@"block this is");
    }();
   
    //无参数 无返回值的block
    void (^block1)(void)= ^{
        NSLog(@"无参数,无返回值的block");
    };
    block1();
    
    //有参数无返回值的block
    void (^block2)(int,int) = ^(int a, int b) {
        NSLog(@"%d,%d",a,b);
    };
    block2(11,29);
    
    //有参数有返回值的block
    int (^block3)(int,int) = ^(int a, int b) {
        return  a + b;
       
    };
     NSLog(@"%d",block3(10,20));
}
output:
2019-02-13 15:45:37.091869+0800 test[9816:170053] block this is
2019-02-13 15:45:37.092465+0800 test[9816:170053] 无参数,无返回值的block
2019-02-13 15:45:37.092518+0800 test[9816:170053] 11,29
2019-02-13 15:45:37.092555+0800 test[9816:170053] 30

下图是block的基本定义结构图:


block的定义

block是封装了函数调用以及函数调用环境的OC对象,首先它是的对象,是对象就可以调用方法,当作参数传递等等。一段代码逻辑可以当作参数传递给另一个函数,是不是很赞,但是它底层是怎么实现的呢,让我们一起来看一下

二 block底层结构

首先我们把下面一段示例代码编译成C++来看一下

#include<stdio.h>
#import <Foundation/Foundation.h>
 int main()
{
    //有参数无返回值的block
    void (^block2)(int,int) = ^(int a, int b) {
        NSLog(@"%d,%d",a,b);
    };
    block2(11,29);
}

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

struct __block_impl {
   void *isa;
   int Flags;
   int Reserved;
   void *FuncPtr;
 };
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { //c++构造函数
    impl.isa = &_NSConcreteStackBlock; //block类型
    impl.Flags = flags;
    impl.FuncPtr = fp; //方法地址
    Desc = desc; //描述,block大小等
  }
};
// NSLog(@"%d,%d",a,b);
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_rn_r6_l2xln77j0bv69j2c_5rg00000gp_T_main_6936c3_mi_0,a,b);
    }

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)};
int main()
{
    //定义一个block
    void (*block2)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
    //执行block内部代码
    ((void (*)(__block_impl *, int, int))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2, 11, 29);
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

最后我们精简一下就可以看出block的具体结构了:

struct __block_impl {
   void *isa;
   int Flags;
   int Reserved;
   void *FuncPtr;  //函数地址
 };
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
};
struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
};

通过底层结构我们可以看出block里面有isa指针,有函数,由此可知它是一个对象,block里面的逻辑被封装成一个函数,函数地址放在了*FuncPtr里面.我们也可以通过下面一段代码来验证我们所说的。

#include<stdio.h>
#import <Foundation/Foundation.h>

struct __block_impl {
   void *isa;
   int Flags;
   int Reserved;
   void *FuncPtr;
 };
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
};
struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
};

 int main()
{
    //有参数无返回值的block
    void (^block2)(int,int) = ^(int a, int b) {
        NSLog(@"%d,%d",a,b);
    };
    
    struct __main_block_impl_0 *dblock = (__bridge struct __main_block_impl_0*)block2;
    
    NSLog(@"%@",[block2 superclass]);
    NSLog(@"%@",[[block2 superclass] superclass]);
    NSLog(@"%@",[[[block2 superclass] superclass] superclass]);
}

结果如下:



我们可以看到block最终父类就是NSObject,下面就是block的底层结构图:


block底层结构

好了上面就是block一个底层的大致结构,下面我们更深入的来了解一下。
三 block变量捕获

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制 ,变量我们有:局部变量,全局变量,静态变量,非静态变量。不同的变量block的捕获机制是不一样的,我们先来看下面的一张总结表格

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

下面我们对这三种情况的变量捕获分别讲解

3.1 局部变量自动变量类型的捕获

请看下面一段代码

#include<stdio.h>
#import <Foundation/Foundation.h>
 int main()
{
    int grade = 3; //auto int grade = 3;
    void (^block2)(void) = ^{
        NSLog(@"年级是%d",grade);
    };
    grade = 10;
    block2();
}
output:
2019-02-17 11:29:13.737438+0800 test[61971:1312228] 年级是3
Program ended with exit code: 0

我们看一下转译后的C++

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int grade; //grade 变量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _grade, int flags=0) : grade(_grade) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int grade = __cself->grade; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_rn_r6_l2xln77j0bv69j2c_5rg00000gp_T_main_f4c6af_mi_0,grade);
    }

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)};
int main()
{
    int grade = 3;  //定义grade
    void (*block2)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, grade)); //最后一个参数传入了grade变量
    grade = 10;
    ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
}

通过C++代码我们可以看出,block内部也有一个grade变量,在定义block时,直接把外部变量的值传递给block内部的变量,对于这种情况我们总结一下:

局部自动变量的捕获

1 什么时间捕获:定义block时捕获外部局部变量
2 捕获的是什么:捕获的是变量的值
3 一旦捕获,block内部变量跟外部的局部变量就没有关系了,无论外部的局部变量怎么变,都不会影响到block内部的变量,反之毅然。
OK 下面我们接着看局部静态变量。

3.2 局部静态变量的捕获

我们直接把上面的grade变成静态变量来看一下:

#include<stdio.h>
#import <Foundation/Foundation.h>
 int main()
{
    static int grade = 3;
    void (^block2)(void) = ^{
        NSLog(@"年级是%d",grade);
    };
    grade = 10;
    block2();
}
2019-02-17 11:51:40.337578+0800 test[62530:1324223] 年级是10

输出结果是10,可以看到跟自动变量捕获的输出结果不一样,我们再来看一下转译后的C++代码:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *grade; //grade指针变量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_grade, int flags=0) : grade(_grade) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *grade = __cself->grade; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_rn_r6_l2xln77j0bv69j2c_5rg00000gp_T_main_9e8152_mi_0,(*grade));
    }

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)};
int main()
{
    static int grade = 3;
    void (*block2)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &grade));//此处传递的是grade的地址
    grade = 10;
    ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
}

通过C++代码我们可以看到对于静态的局部变量,block捕获的是grade的地址,下面我们来总结一下:

局部静态变量的捕获

1 什么时间捕获:定义block时捕获外部局部变量
2 捕获的是什么:捕获的是变量的地址
3 一旦捕获,block内部变量跟外部的局部变量指向的内存地址都是同一个,无论外部的局部变量怎么变都会影响到block内部的变量,反之毅然。
OK 下面我们接着看全局变量的捕获。

3.3 全局变量的捕获

我们还是上面的代码,我们把grade方法全局区域,下面我们来看下代码:

#include<stdio.h>
#import <Foundation/Foundation.h>
int grade = 3;
int main()
{
    
    void (^block2)(void) = ^{
        NSLog(@"年级是%d",grade);
    };
    grade = 10;
    block2();
}
output:
2019-02-17 12:00:43.662399+0800 test[62766:1329510] 年级是10
Program ended with exit code: 0

我们看到输出结果是10,跟局部静态变量捕获输出结果一样,下面我们来看一下它的底层实现是怎样的,转成C++代码如下:

#pragma clang assume_nonnull end
int grade = 3;   //定义在全局

struct __main_block_impl_0 { //也没有任何内部grade变量
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __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) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_rn_r6_l2xln77j0bv69j2c_5rg00000gp_T_main_b85f84_mi_0,grade);
    }

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)};
int main()
{

    void (*block2)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); //什么都没传
    grade = 10;
    ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
}

通过C++代码我们可以看到对于全局变量block什么也没做,用的时候就是直接访问,下面我们来总结一下:

局部静态变量的捕获
1 什么时间捕获:什么时候都不捕获
2 捕获的是什么:什么也不捕获,用的时候就是直接访问
3 因为是全局变量,大家谁都可以修改访问,所以任何修改都会影响其他的使用者。

OK 这三种变量的捕获情况我们已经讲解完了,那么为什么会有这种差异呢,下面我们来解释一下。

3.4 三种变量捕获的差异原因?
#include<stdio.h>
#import <Foundation/Foundation.h>
int grade = 3;
void (^block)(void);
void blockTest(){
    int age = 10;
    static int sex = 1;
    block = ^{
        NSLog(@"年级是%d,年龄是:%d,性别是:%d",grade,age,sex);
    };
}

int main()
{
    blockTest();
    grade = 3;  //全局变量谁都可以访问
    age = 20;  //blockTest内部定义的,无法直接使用,blockTest调用完此变量就会销毁
    sex = 0;//blockTest内部定义的,无法直接使用,blockTest调用完此变量不会销毁
    block();
}

通过上面的代码,我们可以总结原因如下:
1 因为作用域的原因,全局变量什么地方都可以访问,block内部,函数内部都可以直接访问,所以block没必要去捕获它。
2 而对于局部变量,我们的block可以在很多地方调用,假如我在一个函数内部给它赋值并且这个block使用了局部变量,我在别处要想正常调用,我就需要把这个局部变量捕获到我内部,这时候谁调用我都不会有问题。
3 那为什么自动变量是捕获值,静态变量是捕获地址呢?那是因为自动变量和静态变量的生命周期不同,自动变量函数执行完就销毁了,而静态变量不会销毁。所以我不能捕获自动变量的地址,变量销毁了,我再访问它地址就会出错的。

OK上面就是block变量捕获的相关内容,下面我们接着讲,我们来看一看block的类型。

四 block的类型

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型 ,具体类型如下:

序号 类型 同名类型 如何区分
1 NSGlobalBlock _NSConcreteGlobalBlock 没有访问auto变量
2 NSStackBlock _NSConcreteStackBlock 访问了auto变量
3 NSMallocBlock _NSConcreteMallocBlock NSStackBlock调用了copy

那么这三种block有什么意义呢,为什么要分三种呢,其实跟变量分全局,局部一样,之所以分是哪种,也是它们的生命周期不同,它们的内存管理也不同,下面我们看一张这三种类型的block内存分布情况:

几种类型的block内存分布图

数据段
字符串常量:比如NSString *str = @"helloworld"
已初始化数据:已初始化的全局变量、静态变量等
未初始化数据:未初始化的全局变量、静态变量等
:函数调用开销,比如局部变量。 分配的内存空间地址越来越小 内存程序自动管理

:通过alloc、malloc、calloc等动态分配的空间,分配的内存空间地址越来越大内存需要手动管理
下面我们来具体讲解这三种类型的block

4.1 NSGlobalBlock

没有访问auto变量的block就是NSGlobalBlock,我们代码来看一下

#include<stdio.h>
#import <Foundation/Foundation.h>
int grade = 3;
int main()
{
    static int sex = 1;
    void (^block)(void)= ^{
       NSLog(@"年级是%d,性别是:%d",grade,sex);
    };
    NSLog(@"%@",[block class]);
}
2019-02-17 15:24:29.725967+0800 test[68901:1440931] __NSGlobalBlock__
Program ended with exit code: 0

根据输出结果我们可以看出,不管你访问的是全局变量还是静态局部变量,只要你访问的不是自动变量,它的类型就是NSGlobalBlock的,它的内存分布在数据段。

4.2 NSStackBlock

访问了auto变量 的block就是NSStackBlock类型,我们代码来看一下:

#include<stdio.h>
#import <Foundation/Foundation.h>
int main()
{
     int age = 11;
    void (^block)(void)= ^{
       NSLog(@"年龄是%d",age);
    };
    NSLog(@"%@",[block class]);
}

这里要关掉ARC才能打印出它的真实类型,因为ARC会根据实际情况做一些block类型的转换而MRC则不会。

output:
2019-02-17 15:34:12.390039+0800 test[69157:1447001] __NSStackBlock__
Program ended with exit code: 0

NSStackBlock放在栈区,一旦block超出它的作用域,就会自动销毁,有时候就会出现一些问题,我下面举个🌰

#include<stdio.h>
#import <Foundation/Foundation.h>
void (^block)(void);
void blockTest() {
    int age = 11;
    block = ^{
       NSLog(@"年龄是%d",age);
    };
    NSLog(@"%@",[block class]);
}
int main()
{
    blockTest();
    block();
}
2019-02-17 15:44:09.092300+0800 test[69453:1454830] __NSStackBlock__
2019-02-17 15:44:09.092890+0800 test[69453:1454830] 年龄是-272632344
Program ended with exit code: 0

由输出结果可以看出打印的age是乱码的,为什么呢?那是因为我们这个block是放在栈上的,一旦执行完blockTest(),那么这个block的一些访问的变量就会被释放,此时age就释放了,这时你再调用block,访问的age已经释放了,所以就给你个随机值了,为了解决这种类似问题,那就是下面我们要讲的这种block了。

4.3 NSMallocBlock

NSStackBlock调用了copy就会转换成NSMallocBlock,我们把4.2的示例代码稍微改一下,也是在MRC环境下运行

#include<stdio.h>
#import <Foundation/Foundation.h>
int main()
{
     int age = 11;
    void (^block)(void)= [^{
       NSLog(@"年龄是%d",age);
    } copy];
    NSLog(@"%@",[block class]);
}
2019-02-17 15:37:09.265120+0800 test[69245:1449195] __NSMallocBlock__
Program ended with exit code: 0

好,4.2我们讲过NSStackBlock存在的问题,那我们看看NSMallocBlock能不能解决这个问题。

#include<stdio.h>
#import <Foundation/Foundation.h>
void (^block)(void);
void blockTest() {
    int age = 11;
    block = [^{
       NSLog(@"年龄是%d",age);
    } copy];
    NSLog(@"%@",[block class]);
}
int main()
{
    blockTest();
    block();
}
2019-02-17 15:55:16.512882+0800 test[69739:1461301] __NSMallocBlock__
2019-02-17 15:55:16.513941+0800 test[69739:1461301] 年龄是11
Program ended with exit code: 0

我们看出年龄能正确输出,为什么呢?那是因为此时的block是放在堆上的,那么它的内存是由我们自己来管理的,只要我们不释放,它上面的内容就一直在,blockTest()函数执行完age并不会释放,它需要我们自己来释放。有这个问题我们也能看出NSStackBlock与NSMallocBlock的区别,就是内存管理方式与变量的生命长短有差异。
NSStackBlock调用copy就转换成NSMallocBlock那其他两个类型的调用copy是什么类型呢,下面我们来看一下关于三种block copy操作的一下细节

4.4 block copy细节

下面是一张总结表格,我们先来看一下:

Block的类型 副本源的配置存储域 复制效果
NSStackBlock 从栈复制到堆
NSGlobalBlock 数据区 什么也不做
NSMallocBlock 引用计数增加

下面是一段演示代码:

#include<stdio.h>
#import <Foundation/Foundation.h>

int main()
{
   
    //NSGlobalBlock
    void (^block)(void)= [^{
       NSLog(@"BLOCK");
    } copy];
    NSLog(@"%@",[block class]);
    NSLog(@"%lu",(unsigned long)[block retainCount]);
   
    
    //NSGlobalBlock
    int age = 11;
    void (^block2)(void)= [^{
       NSLog(@"年龄是%d",age);
    } copy];
    NSLog(@"%@",[block2 class]);
    NSLog(@"%lu",(unsigned long)[block2 retainCount]);
    
    
    //NSMallocBloc
    int age3 = 11;
    void (^block3)(void)= [[^{
        NSLog(@"年龄是%d",age3);
    } copy] copy];
    NSLog(@"%@",[block3 class]);
    NSLog(@"%lu",(unsigned long)[block3 retainCount]);
    [block3 release];
    NSLog(@"%lu",(unsigned long)[block3 retainCount]);
    [block3 release];
    NSLog(@"%lu",(unsigned long)[block3 retainCount]);
    //往下调用就崩溃了
    [block3 release];
    NSLog(@"%lu",(unsigned long)[block3 retainCount]);
}
output:
2019-02-17 16:30:21.931456+0800 test[70715:1484032] __NSGlobalBlock__
2019-02-17 16:30:21.931868+0800 test[70715:1484032] 1
2019-02-17 16:30:21.932009+0800 test[70715:1484032] __NSMallocBlock__
2019-02-17 16:30:21.932049+0800 test[70715:1484032] 1
2019-02-17 16:30:21.932085+0800 test[70715:1484032] __NSMallocBlock__
2019-02-17 16:30:21.932121+0800 test[70715:1484032] 1
2019-02-17 16:30:21.932152+0800 test[70715:1484032] 1
2019-02-17 16:30:21.932180+0800 test[70715:1484032] 1

对于NSMallocBlock的copy我做一下说明:NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数,通过输出结果我们可以知道虽然NSMallocBlock每次的retainCount打印都是1,但是当我释放两次都没问题,第三次就会出错,说明它内部还是有内存管理机制的,之所打印的retainCount为1 ,可能block的retainCount做了特殊处理吧。
所有上面的copy操作都是在MRC情况下的,那么ARC环境下copy操作有哪些细节我们来看一下:

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况(根据上面copy情况表,下面情况都是针对与NSStackBlock的)
1 block作为函数返回值时
#include<stdio.h>
#import <Foundation/Foundation.h>
typedef void (^MyBlock)(void);

MyBlock myblock(){
    int i = 10;
    return ^{
        NSLog(@"block:%d",i);
    };
};
int main()
{
    MyBlock block = myblock();
    block();
    NSLog(@"%@",[block class]);
}
2 将block赋值给__strong指针时
#include<stdio.h>
#import <Foundation/Foundation.h>

int main()
{
    int i = 10;
   __strong void (^myBlock)(void) =  ^{
       NSLog(@"block:%d",i);
       
   };
    NSLog(@"%@",[myBlock class]);
}
3 block作为Cocoa API中方法名含有usingBlock的方法参数时
#include<stdio.h>
#import <Foundation/Foundation.h>

int main()
{
    NSArray *array = @[@1,@2,@5];
    [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
    }];
}
4 block作为GCD API的方法参数时
#include<stdio.h>
#import <Foundation/Foundation.h>
int main()
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
    });
}

ARC下block属性的建议写法 :
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
MRC下block属性的建议写法 :
@property (copy, nonatomic) void (^block)(void);
好了到此block的一些基本结构类型我们就讲完了,接下来我们看一下block一些特殊关键字,以及内存管理方面的内容

五 block对象类型的自变量内存管理

block对象类型的自变量内存管理就是block引用了局部变量,而这个局部变量是个对象,之前我们讲变量捕获讲的都是基本数据类型,并不是对象,那么如果变量是个对象block是如何管理这个对象内存的呢,我们来看一下。

当block内部访问了对象类型的auto变量时
1 如果block是在栈上,将不会对auto变量产生强引用
#import <Foundation/Foundation.h>

@interface Person : NSObject
@property(assign,nonatomic) int age;

@end

#import "Person.h"
#import <objc/runtime.h>
@implementation Person

- (void)dealloc
{
    NSLog(@"%s",__func__);
}
@end
#include<stdio.h>
#import <Foundation/Foundation.h>
#import "Person.h"
int main()
{
    
    {
        Person *person = [[Person alloc] init];
        person.age = 10;
        NSLog(@"%@", [^{
            NSLog(@"%d",person.age);
        } class]);
        
    }
    NSLog(@"-------");
}
2019-02-17 18:47:15.194739+0800 test[74366:1566585] __NSStackBlock__
2019-02-17 18:47:15.195209+0800 test[74366:1566585] -[Person dealloc]
2019-02-17 18:47:29.249951+0800 test[74366:1566585] -------

只要MyBlock在栈空间,那么当它离开作用域,它与所引用的对象就会断开连接。

2 如果block被拷贝到堆上
  • 会调用block内部的copy函数
  • copy函数内部会调用_Block_object_assign函数
  • _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
#include<stdio.h>
#import <Foundation/Foundation.h>
#import "Person.h"
typedef void (^MyBlock)(void);
int main()
{
     MyBlock myBlock;
        {
            __strong Person *person = [[Person alloc] init];
            person.age = 10;
//             Person *weakPerson = person;
            myBlock = ^{
                NSLog(@"%d",person.age);
            };
        }
    NSLog(@"%@",[myBlock class]);
        myBlock();
    NSLog(@"-------");
}
2019-02-17 20:37:07.693841+0800 test[77005:1623211] __NSMallocBlock__
2019-02-17 20:37:07.694227+0800 test[77005:1623211] 10
2019-02-17 20:37:09.914947+0800 test[77005:1623211] -------
2019-02-17 20:37:09.915220+0800 test[77005:1623211] -[Person dealloc]
typedef void (*MyBlock)(void);

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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Person *person = __cself->person; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_rn_r6_l2xln77j0bv69j2c_5rg00000gp_T_main_76f5bc_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
            }
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};
int main()
{
     MyBlock myBlock;
        {
            __attribute__((objc_ownership(strong))) Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
            ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 10);

            myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
        }
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_rn_r6_l2xln77j0bv69j2c_5rg00000gp_T_main_76f5bc_mi_1,((Class (*)(id, SEL))(void *)objc_msgSend)((id)myBlock, sel_registerName("class")));
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_rn_r6_l2xln77j0bv69j2c_5rg00000gp_T_main_76f5bc_mi_2);
}
3 如果block从堆上移除
  • 会调用block内部的dispose函数
  • dispose函数内部会调用_Block_object_dispose函数
  • _Block_object_dispose函数会自动释放引用的auto变量(release)
从上面截取的......

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局部变量是对象的内存管理问题我们就讲完了,下面做两题练练。

问题: p 会在什么时候释放
@implementation ViewController
- (void)viewDidLoad {
    
    [super viewDidLoad];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    People *p = [[People alloc] init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",p);
    });
}

@end

答:p会在定时器启动3秒后执行完block里的内容后释放,为什么在三秒后,前面我们知道,对于GCD的block,会对对象进行强引用所以定时器不执行对象就不会释放。

问题: p 会在什么时候释放
#import "ViewController.h"
#import "People.h"
@interface ViewController ()
@property(strong,nonatomic) People *people;
@end
@implementation ViewController
- (void)viewDidLoad {
    
    [super viewDidLoad];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    People *p = [[People alloc] init];
    __weak People *weakPeople = p;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",weakPeople);
    });
}

@end

答:p会立马释放 __weak是弱引用
问题: p 会在什么时候释放

#import "ViewController.h"
#import "People.h"
@interface ViewController ()
@property(strong,nonatomic) People *people;
@end
@implementation ViewController
- (void)viewDidLoad {
    
    [super viewDidLoad];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    People *p = [[People alloc] init];
    __weak People *weakPeople = p;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",weakPeople);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
               NSLog(@"%@",p);
           });
    });
}
答:6秒后释放,整体里面强引用优先。

OK 到此block应用对象类型内存管理问题我们讲到这,我们接下来看下面的内容

六 _block讲解

如果想要在block内部修改auto变量值怎么办呢?那么这个_block就派上用场了。

  • __block可以用于解决block内部无法修改auto变量值的问题(注意变量值这个概念,如果是修改指针对象里的内容就不叫修改变量值)

  • __block不能修饰全局变量、静态变量(static)(全局变量任何地方都可以改,静态变量block里保存的是变量地址值也可以改,所以就没必要用__block修饰)
    下面我们来看一下 __block基本使用

6.1 __block基本使用
#include<stdio.h>
#import <Foundation/Foundation.h>
typedef void (^MyBlock)(void);
int main()
{
     MyBlock myBlock;
     __block int grade = 10;
     myBlock = ^{
         grade = 20;
       NSLog(@"%d",grade);
     };
 NSLog(@"%d",&grade); //grade的地址值,不是它包装对象的地址值
     myBlock();
}
2019-02-18 09:31:35.247437+0800 test[99712:2302267] 20

使用起来很简单,下面我们来看一下它底层是如何实现的,我们把上面的代码转译成c++代码:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

#pragma clang assume_nonnull end
typedef void (*MyBlock)(void);
struct __Block_byref_grade_0 {
  void *__isa;
__Block_byref_grade_0 *__forwarding;
 int __flags;
 int __size;
 int grade;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_grade_0 *grade; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_grade_0 *_grade, int flags=0) : grade(_grade->__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_grade_0 *grade = __cself->grade; // bound by ref

         (grade->__forwarding->grade) = 20;
       NSLog((NSString *)&__NSConstantStringImpl__var_folders_rn_r6_l2xln77j0bv69j2c_5rg00000gp_T_main_785a23_mi_0,(grade->__forwarding->grade));
     }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->grade, (void*)src->grade, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->grade, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main()
{
     MyBlock myBlock;
     __attribute__((__blocks__(byref))) __Block_byref_grade_0 grade = {(void*)0,(__Block_byref_grade_0 *)&grade, 0, sizeof(__Block_byref_grade_0), 10};
     myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_grade_0 *)&grade, 570425344));
     ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}

跟以前的局部自动变量捕获我们发现有下面的不一样的地方

struct __Block_byref_grade_0 {
  void *__isa;  //isa指针
__Block_byref_grade_0 *__forwarding; //前置指针,指向自己
 int __flags;
 int __size;  //结构体大小
 int grade;//grade的值
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_grade_0 *grade; // by ref
};

int main()
{
     MyBlock myBlock;
//把gradle包装成一个对象
     __Block_byref_grade_0 grade = {&grade,  //传入了变量的地址
 0, 
sizeof(__Block_byref_grade_0), 10};

     myBlock = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &grade, 570425344)); 把grade对象地址值传给block
  
   myBlock->FuncPtr(myBlock);
}

//调用修改使用  grade对象的->__forwarding指针->grade变量
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_grade_0 *grade = __cself->grade; // bound by ref

         (grade->__forwarding->grade) = 20;
       NSLog((NSString *)&__NSConstantStringImpl__var_folders_rn_r6_l2xln77j0bv69j2c_5rg00000gp_T_main_785a23_mi_0,(grade->__forwarding->grade));
     }

所以:编译器会将__block变量包装成一个对象 __Block_byref_grade_0把这个对象的地址给block,block通过包装的对象修改这个变量值,包装对象结构图如下:


既然是包装成对象那么就会有内存管理问题,下面我们来看一下

6.2 __block 内存管理

我们还是拿出上面c++代码的一部分来说明

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->grade, (void*)src->grade, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->grade, 8/*BLOCK_FIELD_IS_BYREF*/);}

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*);
}
  • 当block在栈上时,并不会对__block变量产生强引用

  • 当block被copy到堆时
    会调用block内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会对__block变量形成强引用(retain)

    block栈拷贝到堆

如果有两个block在栈上,都引用了同一个__block变量,那么当它们拷贝到堆上时候,block 和__block都会拷贝一份到堆上,当然了__block是共用的它只copy一次,堆上的也都是强引用。

  • 当block从堆中移除时
    会调用block内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放引用的__block变量(release)



    释放的时候,当所有block都不引用__block,__block才会销毁。我们可以看出__block的实现内存管理跟block引用对象类型的的局部变量内存管理很像,看下面代码

#include<stdio.h>
#import <Foundation/Foundation.h>
typedef void (^MyBlock)(void);
int main()
{
     MyBlock myBlock;
     __block int grade = 10;
    NSObject *obj = [[NSObject alloc] init];
     myBlock = ^{
         grade = 20;
       NSLog(@"%d",grade);
       NSLog(@"%@",obj);
     };
     myBlock();
}

它们唯一不同的就是__block修饰的变量是强引用,而obj 可以根据__strong,__weak,来决定是强引用还是弱引用,下面我们来总结下这两种变量的内存管理情况,

对象类型的auto变量、__block变量总结
  1. 当block在栈上时,对它们都不会产生强引用
  2. 当block拷贝到堆上时,都会通过copy函数来处理它们
    __block变量(假设变量名叫做a)
    _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

对象类型的auto变量(假设变量名叫做p)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

  1. 当block从堆上移除时,都会通过dispose函数来释放它们
    __block变量(假设变量名叫做a)
    _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

对象类型的auto变量(假设变量名叫做p)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

好了上面我们讲的__block 修饰的是基本类型的变量,那么当__block修饰的是对象类型的变量,内存管理又是怎么样的呢,我们来看一下

6.3 __block 对象类型内存管理

先看段代码:

#include<stdio.h>
#import <Foundation/Foundation.h>
#import "Person.h"
typedef void (^MyBlock)(void);
int main()
{
     MyBlock myBlock;
     __block __strong Person *person = [[Person alloc] init];
     __block __weak Person *weakPerson = person;
     myBlock = ^{
       NSLog(@"%@,%@",person,weakPerson);
     };
     myBlock();
}
#import <Foundation/Foundation.h>

@interface Person : NSObject
@property(assign,nonatomic) int age;

@end

#import "Person.h"
#import <objc/runtime.h>
@implementation Person

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

编译成的C++代码如下:

#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

typedef void (*MyBlock)(void);
struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__strong person;
};
struct __Block_byref_weakPerson_1 {
  void *__isa;
__Block_byref_weakPerson_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__weak weakPerson;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_person_0 *person; // by ref
  __Block_byref_weakPerson_1 *weakPerson; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, __Block_byref_weakPerson_1 *_weakPerson, int flags=0) : person(_person->__forwarding), weakPerson(_weakPerson->__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_person_0 *person = __cself->person; // bound by ref
  __Block_byref_weakPerson_1 *weakPerson = __cself->weakPerson; // bound by ref

       NSLog((NSString *)&__NSConstantStringImpl__var_folders_rn_r6_l2xln77j0bv69j2c_5rg00000gp_T_main_802ae9_mi_0,(person->__forwarding->person),(weakPerson->__forwarding->weakPerson));
     }
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, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main()
{
     MyBlock myBlock;
     __attribute__((__blocks__(byref))) __attribute__((objc_ownership(strong))) __Block_byref_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"))};
     __attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __Block_byref_weakPerson_1 weakPerson = {(void*)0,(__Block_byref_weakPerson_1 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, (person.__forwarding->person)};
     myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, (__Block_byref_weakPerson_1 *)&weakPerson, 570425344));
     ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}

我们来精简一下

struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__strong person;
};
struct __Block_byref_weakPerson_1 {
  void *__isa;
__Block_byref_weakPerson_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__weak weakPerson;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_person_0 *person; // by ref
  __Block_byref_weakPerson_1 *weakPerson; // by ref
};

我们可以看到__block 对于对象类型也会进行包装,但是包装的对象多了两个方法void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*);,由此我们可以看到block引用的局部对象变量的内存是由__block的包装对象来管理的,根据上面的代码,我们整理出一个对象结构图如下:

__strong person

__weak person

那他们的内存管理是怎样的呢?
我们看下面两行代码

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

1.当__block变量在栈上时,不会对指向的对象产生强引用

2 .当__block变量被copy到堆时
会调用__block变量内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)

  1. 如果__block变量从堆上移除
    会调用__block变量内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放指向的对象(release)
    好了上面就是__block对象类型内存管理的一些讲解,最后针对于__block其他一些细节我们简单讲一下
6.4 __block的__forwarding指针

上面6.1C++有段代码我们来看一下

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_grade_0 *grade = __cself->grade; // bound by ref

         (grade->__forwarding->grade) = 20;
       NSLog((NSString *)&__NSConstantStringImpl__var_folders_rn_r6_l2xln77j0bv69j2c_5rg00000gp_T_main_785a23_mi_0,(grade->__forwarding->grade));
     }

我们在block内部修改变量的时候为什么要通过__forwarding指针呢,直接通过grade->grade = 20,不更简单么,之所以这样设计那就牵扯到block拷贝的问题了,我们看下面一张图:


当block在栈上的时候,__forwarding指向的是栈上的__block结构体,当block复制到堆上的时候__forwarding指向的是堆上的__block结构体这样就能保证当block拷贝到堆上时你能访问到堆上的变量(比如访问堆上的grade变量,否则不这样干grade结构体怎么访问堆上的grade变量呢,一旦拷贝到堆上__forwarding指向就发生改变,这样才能保证你能访问到堆上的变量)
OK到此block基本内容就讲的差不多了,最后我们再讲解下block的循环引用问题。

七 block循环引用问题

我们来看一下循环引用的例子:

#import <Foundation/Foundation.h>
typedef void (^MyBlock)(void);

@interface Person : NSObject
@property(copy,nonatomic) MyBlock block;
@property(copy,nonatomic) NSString *name;

@end
#import "Person.h"
#import <objc/runtime.h>
@implementation Person

- (void)dealloc
{
    NSLog(@"%s",__func__);
}
@end
#include<stdio.h>
#import <Foundation/Foundation.h>
#import "Person.h"

int main()
{
    Person *person = [[Person alloc] init];
    person.name = @"张三";
    person.block = ^{
        NSLog(@"%@",person.name);
    };
    person.block();
}

2019-02-18 12:54:36.587115+0800 test[5464:2439742] 张三
Program ended with exit code: 0

我们可以看到代码执行完,我们的person对象也没有释放,我们的person里有一个成员变量是block,这个block又引用了person的一个成员变量,你引用我我引用你,就形成了一个闭环的引用,释放的时候你等我释放,我等你释放,结果谁也无法释放,这就造成了循环引用问题,这个问题在平时开发中还是很常见的,怎么解决这个问题呢,我们接下来往下看。

ARC下解决循环引用问题

1 使用__weak

#include<stdio.h>
#import <Foundation/Foundation.h>
#import "Person.h"

int main()
{
    Person *person = [[Person alloc] init];
    person.name = @"张三";
    __weak Person *weakPerson = person;
    person.block = ^{
        NSLog(@"%@",weakPerson.name);
    };
    person.block();
}
2019-02-18 13:02:20.494467+0800 test[5663:2445602] 张三
2019-02-18 13:02:20.494898+0800 test[5663:2445602] -[Person dealloc]
Program ended with exit code: 0

2 用__block解决(必须要调用block)

#include<stdio.h>
#import <Foundation/Foundation.h>
#import "Person.h"

int main()
{
    Person *person = [[Person alloc] init];
    person.name = @"张三";
    __block Person *weakPerson = person;
    person.block = ^{
        NSLog(@"%@",weakPerson.name);
        weakPerson = nil;
    };
    person.block();
}
原理图

这种方案要执行block才不会有循环引用问题,如果你不执行,就会存在循环引用问题,不太建议使用。

MRC下解决循环引用问题

1 用__unsafe_unretained解决

#import "Person.h"
#import <objc/runtime.h>
@implementation Person

- (void)dealloc
{
    [super dealloc];
    NSLog(@"%s",__func__);
}
@end


#include<stdio.h>
#import <Foundation/Foundation.h>
#import "Person.h"

int main()
{
    Person *person = [[Person alloc] init];
    person.name = @"张三";
    __unsafe_unretained Person *weakPerson = person;
    person.block = ^{
        NSLog(@"%@",weakPerson.name);
    };
    person.block();
    [person release];//不加__unsafe_unretained即使调用release person也不会释放
}

MRC不支持,要使用弱引用指针只能用__unsafe_unretained,不会产生强引用但是不安全。
2 使用__block

#include<stdio.h>
#import <Foundation/Foundation.h>
#import "Person.h"

int main()
{
    Person *person = [[Person alloc] init];
    person.name = @"张三";
    __block Person *weakPerson = person;
    person.block = ^{
        NSLog(@"%@",weakPerson.name);
    };
    person.block();
    [person release];
}

上面6.3讲过__block调用_Block_object_assign不会产生强引用。

这几个常用的还是使用__weak来解决循环引用问题,主要目的就是打断循环闭环,无论破坏哪一条引用都可以。
OK 到此block相关的内容我们就讲完了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容