OC底层原理学习笔记(五)- Block的理解

一、block的本质

block本质上也是一个OC对象,它内部也有一个isa指针
block是封装了函数调用以及函数调用环境的OC对象
block内部代码会封装到_block_func_0函数中,函数地址保存在FuncPtr中
执行block内部代码时是通过FuncPtr找到函数地址进行调用

block的底层结构如下:

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;  // 指向要执行的函数地址
};

static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
}

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    //构造函数(类似于OC的init方法),返回结构体对象
    __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的变量捕获机制

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


block变量捕获.jpg

1、block内部访问auto变量时会将auto变量捕获到block内部,block外部修改auto变量的值并不会影响block内部,所以是值捕获

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void (^block)(void) = ^ {
            NSLog(@"age = %d", age);  // age = 10
        };
        age = 20;
        block();
    }
    return 0;
}
// 底层C++代码
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

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

    }
    return 0;
}

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_v9_dtds2kd515b0fcvr9_xqn_mm0000gn_T_main_85e3bc_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)};

2、block访问static变量时会将auto变量捕获到block内部,block外部修改static变量的值会影响block内部,所以是指针捕获

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int age = 10;
        void (^block)(void) = ^ {
            NSLog(@"age = %d", age); // age = 20
        };
        age = 20;
        block();
    }
    return 0;
}
// 底层C++代码
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;
  }
};

3、block访问全局变量时不会将全局变量捕获到block内部,而是直接访问

int age = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^ {
            NSLog(@"age = %d", age); // age = 20
        };
        age = 20;
        block();
    }
    return 0;
}
//底层C++代码
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;
  }
};

4、无参方法内部block访问self会将self捕获到block内部,从底层C++代码可以看出方法test有两个默认参数self、_cmd,它们也是auto类型的局部变量

#import "QLStudent.h"

@implementation QLStudent

- (void)test {
    void(^block)(void) = ^ {
        NSLog(@"===========%p", self);
    };
    block();
}

@end
//底层C++代码
static void _I_QLStudent_test(QLStudent * self, SEL _cmd) {
    void(*block)(void) = ((void (*)())&__QLStudent__test_block_impl_0((void *)__QLStudent__test_block_func_0, &__QLStudent__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

三、block的类型

block类型 环境
NSGlobalBlock 没有访问auto变量
NSStackBlock 访问了auto变量
NSMallocBlock NSStackBlock调用了copy

1、NSGlobalBlock:存放在数据段
NSGlobalBlock调用copy还是NSGlobalBlock

// 没有访问auto变量
void (^block)(void) = ^ {
    NSLog(@"Hello world!");
};
NSLog(@"%@", [block class]);  // __NSGlobalBlock__
NSLog(@"%@", [[block class] superclass]);  // NSBlock
NSLog(@"%@", [[[block class] superclass] superclass]);  // NSObject

2、NSStackBlock:存放在栈区
NSStackBlock调用copy,从栈复制到堆

// 访问了auto变量
int num = 10;
void (^block)(void) = ^ {
    NSLog(@"num = %d", num);
};
NSLog(@"%@", [block class]);  // __NSStackBlock__

3、NSMallocBlock:存放在堆区
NSMallocBlock调用copy,引用计数增加

// NSStackBlock调用了copy
int num = 10;
void (^block)(void) = [^ {
    NSLog(@"num = %d", num);
} copy];
NSLog(@"%@", [block class]);  // __NSMallocBlock__

四、block的copy

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
1、block作为函数的返回值时
2、将block赋值给__strong指针时
3、block作为Cocoa API中方法名含有usingBlock的方法参数时,例如

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

4、block作为GCD API的方法参数时,例如:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 
(int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
});

五、解决block的循环引用

1、用__weak、__unsafe_unretained
__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil

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

__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变

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

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

__block QLPerson *person = [[QLPerson alloc] init];
person.age = 10;
person.block = ^ {
    NSLog(@"age is %d", person.age);
    person = nil;
};
person.block();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1..block的原理是什么?本质是什么? block本质上也是一个OC对象,它内部也有个isa指针,block是...
    T_guo阅读 429评论 0 1
  • 一、Block的本质block本质上也是一个OC对象,它的内部也会有一个isa指针,它是封装了函数调用以及函数调用...
    小豆豆苗阅读 231评论 0 0
  • 1: 什么是block?1.0: Block的语法1.1: block编译转换结构1.2: block实际结构 2...
    iYeso阅读 873评论 0 5
  • static变量会被多加一个* 获取根内存地址NSMallocBlock只需要对NSStackBlock进行co...
    惊蛰_e3ce阅读 622评论 0 0
  • Block 是什么?Block 是一个匿名的函数,但是它能够捕获变量,这是它跟匿名函数的区别。 Block 是如何...
    jeff_guan阅读 430评论 0 0