ios进阶

OC - Block 详解

2020-02-23  本文已影响0人  师大小海腾

目录

  • 1.Block 的基本使用
  • 2.Block 的底层数据结构
  • 3.Block 的变量捕获机制
    3.1 auto 类型的局部变量
    3.2 static 类型的局部变量
    3.3 全局变量
    3.4 对象类型的 auto 变量
    3.5 __block 修饰的变量
     3.5.1 __block 作用
     3.5.2 __block 修饰符
     3.5.3 __block 的内存管理
     3.5.4 __block 的 __forwarding 指针
     3.5.5 对象类型的 auto 变量、__block 变量内存管理区别
     3.5.6 被 __block 修饰的对象类型
  • 4.Block 的类型
  • 5.Block 的 copy
  • 6.Block 的循环引用问题
    6.1 ARC
    6.2 MRC
  • 7.相关面试题

1.Block 的使用

Block 是什么?

块,封装了函数调用以及调用环境的 OC 对象,

Block 的声明

// 1.
@property (nonatomic, copy) void(^myBlock1)(void);
// 2.BlockType:类型别名
typedef void(^BlockType)(void);
@property (nonatomic, copy) BlockType myBlock2;
// 3.
    // 返回值类型(^block变量名)(参数1类型,参数2类型,...)
    void(^block)(void);

Block 的定义

    // ^返回值类型(参数1,参数2,...){};
    // 1.无返回值,无参数
    void(^block1)(void) = ^{
        
    };
    // 2.无返回值,有参数
    void(^block2)(int) = ^(int a){
        
    };
    // 3.有返回值,无参数(不管有没有返回值,定义的返回值类型都可以省略)
    int(^block3)(void) = ^int{
        return 3;
    };
    // 以上Block的定义也可以这样写:
    int(^block4)(void) = ^{
        return 3;
    };
    // 4.有返回值,有参数
    int(^block5)(int) = ^int(int a){
        return 3 * a;
    };

Block 的调用

    // 1.无返回值,无参数
    block1();
    // 2.有返回值,有参数
    int a = block5(2);

使用示例

    int multiplier = 7;
    int (^myBlock)(int) = ^(int num) {
        return num * multiplier;
    };
     
    printf("%d", myBlock(3));
    // prints "21"

Block 的 Code Snippets 快捷方式


2.Block的底层数据结构

Block 的底层数据结构.png

通过 Clang 将以下 Block 代码转换为 C++ 代码,来分析 Block 的底层实现。

// Clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
// main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void(^block)(void) = ^{
            NSLog(@"调用了block");
        };
        block();
    }
    return 0;
}
// main.cpp
struct __main_block_impl_0 {
    struct __block_impl impl; // block的结构体
    struct __main_block_desc_0* Desc; // block的描述对象,描述block的大小等
    /*  构造函数
     ** 返回值:__main_block_impl_0 结构体
     ** 参数一:__main_block_func_0 结构体
     ** 参数二:__main_block_desc_0 结构体的地址
     ** 参数三:flags 标识位
     */
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock; //_NSConcreteStackBlock 表示block存在栈上
        impl.Flags = flags; 
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// __main_block_func_0 封装了block里的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_58a448_mi_0);
}

struct __block_impl {
    void *isa;     // block的类型
    int Flags;     // 标识位
    int Reserved;  // 
    void *FuncPtr; // block的执行函数指针,指向__main_block_func_0
};

static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size; // block本质结构体所占内存空间
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; 
 
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        /*
          void(^block)(void) = ^{
              NSLog(@"调用了block");
          };
         ** 定义block的本质:
         ** 调用__main_block_impl_0()构造函数
         ** 并且给它传了两个参数 __main_block_func_0 和 &__main_block_desc_0_DATA
         ** __main_block_func_0 封装了block里的代码
         ** 拿到函数的返回值,再取返回值的地址 &__main_block_impl_0,
         ** 把这个地址赋值给 block
         */
        void(*block)(void) = ((void (*)())&__main_block_impl_0(
                                                               (void *)__main_block_func_0,
                                                               &__main_block_desc_0_DATA
                                                              ));
        // block();
        /*
         ** 调用block的本质:
         ** 通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数地址,直接调用
         */      
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

3.Block的变量捕获机制

为了保证 block 内部能够正常访问外部的变量,block 有个变量捕获机制。

3.1 auto 类型的局部变量

auto 自动变量:我们定义出来的变量,默认都是 auto 类型,只是省略了。

    auto int age = 10;

auto 类型的局部变量会捕获到 block 内部,访问方式为值传递

通过 Clang 将以下代码转换为 C++ 代码:

    int age = 10;
    void(^block)(void) = ^{
        NSLog(@"%d",age);
    };
    block();
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int 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;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int age = __cself->age; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_5ed490_mi_0,age);
}

......

    int age = 10;
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

由于是值传递,我们修改外部的age变量的值,不会影响到 block 内部的age变量。

    int age = 10;
    void(^block)(void) = ^{
        NSLog(@"%d",age);
    };
    age = 20;
    block();
    // 10

3.2 static 类型的局部变量

static 类型的局部变量会捕获到 block 内部,访问方式为指针传递

通过 Clang 将以下代码转换为 C++ 代码:

    static int age = 10;
    void(^block)(void) = ^{
        NSLog(@"%d",age);
    };
    block();
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int *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;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int *age = __cself->age; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_a4bc7d_mi_0,(*age));
}

......

    static int age = 10;
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

由于是指针传递,我们修改外部的age变量的值,会影响到 block 内部的age变量。

    static int age = 10;
    void(^block)(void) = ^{
        NSLog(@"%d",age);
    };
    age = 20;
    block();
    // 20

3.3 全局变量

全局变量不会捕获到 block 内部,访问方式为直接访问

通过 Clang 将以下代码转换为 C++ 代码:

int _age = 10;
static int _height = 20;
......
        void(^block)(void) = ^{
            NSLog(@"%d,%d",_age,_height);
        };
        block();
int _age = 10;
static int _height = 20;

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) {
        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_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_12efa5_mi_0,_age,_height);
}

......

    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

为什么局部变量需要捕获,全局变量不用捕获呢?

3.4 对象类型的 auto 变量

当 block 内部访问了对象类型的 auto 变量时:

函数 调用时机
copy 函数 栈上的 block 复制到堆时
dispose 函数 堆上的 block 被废弃时

如下代码,block 保存在堆中,当执行完作用域2的时候,Person 对象并没有被释放,而是在执行完作用域1的时候释放,说明 block 内部对 Person 对象产生了强引用。

typedef void(^MyBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool { //作用域1
        MyBlock block;
        { //作用域2
            Person *p = [Person new];
            p.name = @"zhangsan";      
            block = ^{
                NSLog(@"%@",p.name);
            };
        }
        NSLog(@"-----");
    }
    return 0;
}
// -----
// Person-dealloc

通过 Clang 将以上代码转换为 C++ 代码:

// 弱引用需要运行时的支持,所以需要加上 -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

__main_block_impl_0中生成了一个Person *__strong p指针,指向外面的 person 对象,且是强引用。

typedef void(*MyBlock)(void);

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    Person *__strong p; // 强引用
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_p, int flags=0) : p(_p) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    Person *__strong p = __cself->p; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("name")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->p, 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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        MyBlock block;
        {
            Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
            ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_0);

            block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, 570425344));
        }
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9e5699_mi_2);
    }
    return 0;
}

添加了__weak修饰后,当执行完作用域2的时候,Person 对象就被被释放了。

typedef void(^MyBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool { //作用域1
        MyBlock block;
        { //作用域2
            __weak Person *p = [Person new];
            p.name = @"zhangsan";      
            block = ^{
                NSLog(@"%@",p.name);
            };
        }
        NSLog(@"-----");
    }
    return 0;
}
// Person-dealloc
// -----

同样的,通过 Clang 将以上代码转换为 C++ 代码。
__main_block_impl_0中生成了一个Person *__weak p指针,指向外面的 person 对象,且是弱引用。
说明当 block 内部 访问了对象类型的 auto 变量时,如果 block 被拷贝到堆上,会连同对象的所有权修饰符一起捕获。

......
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    Person *__weak p; //弱引用
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _p, int flags=0) : p(_p) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    Person *__weak p = __cself->p; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_c61841_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("name")));
}
......

3.5 __block 修饰的变量

3.5.1 __block 作用

默认情况下 block 是不能修改外面的 auto 变量的,解决办法?

3.5.2 __block 修饰符

使用示例

    __block int age = 10;
    void(^block)(void) = ^{
        age = 20;
        NSLog(@"block-%d",age);
    };
    block();
    NSLog(@"%d",age);
    // block-20
    // 20

通过 Clang 将以上代码转换为 C++ 代码。

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;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9578d0_mi_0,(age->__forwarding->age));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_9578d0_mi_1,(age.__forwarding->age));
    }
    return 0;
}

3.5.3 __block 的内存管理

3.5.4 __block 的 __forwarding 指针

__block 的__forwarding指针存在的意义?
为什么要通过 age 结构体里的__forwarding指针拿到 age 变量的值,而不直接 age 结构体拿到 age 变量的值呢?

__block 的__forwarding是指向自己本身的指针,为了不论在任何内存位置,都可以顺利的访问同一个 __block 变量。

这样不管我们访问的是栈上还是堆上的 __block 变量结构体,只要是通过__forwarding指针访问,都是访问到堆上的 __block 变量结构体;给 age 赋值,就肯定会赋值给堆上的那个 __block 变量中的 age。

__forwarding.png

3.5.5 对象类型的 auto 变量、__block 变量内存管理区别

对象类型的 auto 变量
(假设变量名叫做p)
__block 变量
(假设变量名叫做a)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/); _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign函数会根据 auto 变量的修饰符(__strong__weak__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用 _Block_object_assign函数会对 __block 变量形成强引用(retain)
对象类型的 auto 变量
(假设变量名叫做p)
__block 变量
(假设变量名叫做a)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/); _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

3.5.6 被 __block 修饰的对象类型

4.Block 的类型

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

block类型 描述 环境
__ NSGlobalBlock __
( _NSConcreteGlobalBlock )
全局block,保存在数据段 没有访问auto变量
__ NSStackBlock __
( _NSConcreteStackBlock )
栈block,保存在栈区 访问了auto变量
__ NSMallocBlock __
( _NSConcreteMallocBlock )
堆block,保存在堆区 __ NSStackBlock __调用了copy

打印各种 block 的类型,以及遍历 block 的父类类型,如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void(^block1)(void) = ^{
            NSLog(@"hello");
        };
        /*  block2
         ** 在ARC下会自动copy,从栈复制到堆,所以为__NSMallocBlock__类型
         ** 在MRC下为__NSStackBlock__类型,需要手动调用copy方法才会变为__NSMallocBlock__类型
         **     同时,在不需要该block的时候需要手动调用release方法
         */ 
        int age = 10;
        void(^block2)(void) = ^{
            NSLog(@"%d",age);
        };
        
        NSLog(@"%@,%@,%@", [block1 class], [block2 class], [^{
            NSLog(@"%d",age);
        } class]);
        
        Class class = [block1 class];
        while (class) {
            NSLog(@"%@",class);
            class = [class superclass];
        }
    }
    return 0;
}
// __NSGlobalBlock__,__NSMallocBlock__,__NSStackBlock__
// __NSGlobalBlock__
// __NSGlobalBlock
// NSBlock
// NSObject

每一种类型的 block 调用 copy 后的结果如下所示:

block类型 副本源的配置存储区 复制效果
_NSConcreteGlobalBlock 程序的数据段区 什么也不做
_NSConcreteStackBlock 从栈复制到堆
_NSConcreteMallocBlock 引用计数增加

__ NSStackBlock __ 存在的问题:

以下是在 MRC 环境下,block 类型为__NSStackBlock__
当 test() 函数执行完毕,栈上的东西可能会被销毁,数据就会变成垃圾数据。尽管 block 还能正常调用,但是输出的 age 的值发生了错乱。

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

解决办法:调用copy方法,将栈 block 复制到堆。

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

5.Block 的 copy

ARC 环境下,编译器会根据情况自动将栈上的 block 复制到堆上,比如以下几种情况:

block 作为属性的写法:

// MRC
@property (nonatomic, copy) void(^block)(void);
// ARC
@property (nonatomic, copy) void(^block)(void);
@property (nonatomic, strong) void(^block)(void);

6.Block 的循环引用问题

为什么 block 会产生循环引用?

6.1 ARC

    __weak typeof(self) weakSelf = self;
    self.block = ^{
        NSLog(@"%p",weakSelf);
    };
    __unsafe_unretained id weakSelf = self;
    self.block = ^{
        NSLog(@"%p",weakSelf);
    };
    __block id weakSelf = self;
    self.block = ^{
        NSLog(@"%p",weakSelf);
        weakSelf = nil;
    };
    self.block();
image.png

6.2 MRC

    __block id weakSelf = self;
    self.block = ^{
        NSLog(@"%p",weakSelf);
    };

7.相关面试题

Q:block 的本质是什么?

封装了函数调用以及调用环境的 OC 对象。

Q:block 的属性修饰词为什么是 copy?使用 block 有哪些使用注意?

block 一旦没有进行 copy 操作,就不会在堆上。
使用注意:循环引用问题。

Q:block在给 NSMutableArray 添加或移除对象,需不需要添加 __block?

不需要。

Q:block 的变量捕获机制

block 的变量捕获机制,是为了保证 block 内部能够正常访问外部的变量。

Q:为什么局部变量需要捕获,全局变量不用捕获呢?

Q:self 会不会捕获到 block 内部?

会捕获。
OC 方法都有两个隐式参数,方法调用者self和方法名_cmd
参数也是一种局部变量。

Q:_name 会不会捕获到 block 内部?

会捕获。
不是将_name变量进行捕获,而是直接将self捕获到 block 内部,因为_name是 Person 类的成员变量,_name来自当前的对象/方法调用者self(self->_name)
如果使用self.name即调用selfgetter方法,即给self对象发送一条消息,那还是要访问到selfself是局部变量,不是全局变量,所以self会捕获到 block 内部。

Q:__ NSStackBlock __ 存在的问题:

如果没有将 block 从栈上 copy 到堆上,那我们访问栈上的 block 的话,可能会由于变量作用域结束导致栈上的 block 以及 __block 变量被销毁,而造成内存崩溃。或者数据可能会变成垃圾数据,尽管将来 block 还能正常调用,但是它捕获的变量的值已经错乱了。
解决办法:将 block 的内存放堆里,意味着它就不会自动销毁,而是由我们程序员来决定什么时候销毁它。

Q:默认情况下 block 是不能修改外面的 auto 变量的,解决办法?

Q:__block 修饰符使用注意点:

在 MRC 下使用 __block 修饰对象类型,在 block 内部不会对该对象进行 retain 操作,所以在 MRC 环境下可以通过 __block 解决循环引用的问题。

Q:__block 的 __forwarding 指针存在的意义?为什么要通过 age 结构体里的 __forwarding 指针拿到 age 变量的值,而不直接 age 结构体拿到 age 变量的值呢?

见 3.5.4 __block 的 __forwarding 指针。

Q:为什么通过 __weak 去修饰成员变量或对象就可以达到规避循环引用的目的呢?

block 对于对象类型的局部变量连同所有权修饰符一起截获,所以如果我们在外部定义的对象是 __weak 所有权修饰符的,那么在 block 中所产生的结构体里所持有的变量也是 __weak 类型的。

Q:解决在 block 内部通过弱指针访问对象成员时编译器报错的问题:

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

Q:以下代码的打印结果是?

    __block int multiplier = 6;
    int (^block)(int) = ^(int num) {
        return num * multiplier;
    };
    multiplier = 4;
    NSLog(@"%d",block(2));
    // 8

Q:以下代码有问题吗?

    __block id weakSelf = self;
    self.block = ^{
        NSLog(@"%p",weakSelf);
    };
    __block id weakSelf = self;
    self.block = ^{
        NSLog(@"%p",weakSelf);
        weakSelf = nil;
    };
    self.block();

缺点:必须要调用 block,而且 block 里要将指针置为 nil。如果一直不调用 block,对象就会一直保存在内存中,造成内存泄漏。

上一篇下一篇

猜你喜欢

热点阅读