面试宝点

Block详解

2020-08-06  本文已影响0人  鼬殿

block的本质

先看block的简单实现

 int age = 20;
 void (^block)(int) =  ^(int a){
      NSLog(@"this is a block! -- %d", age);
 };
 block(10);

转为C++代码

struct __main_block_desc_0 {
    size_t reserved;
*********************************
block结构体占用的内存大小
*********************************
    size_t Block_size; 
};

struct __block_impl {
    void *isa; isa指针
    int Flags;
    int Reserved;
*********************************
block内执行的代码会封装到一个函数中,FuncPtr存放函数的内存地址
*********************************
    void *FuncPtr; 
};

struct __main_block_impl_0 {
    struct __block_impl impl;
*********************************
block的描述
*********************************
  struct __main_block_desc_0* Desc; 
*********************************
持有外部变量 age
*********************************
    int age; 
};


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
*********************************
构造函数(类似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执行逻辑的函数
*********************************
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r_gswfk35n5938fbdhf6s4xw_c0000gp_T_main_22e326_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)};

查看Block的继承关系

*********************************
 __NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
*********************************
 void (^block)(void) = ^{
              NSLog(@"Hello");
          };
 NSLog(@"%@", [block class]);
 NSLog(@"%@", [[block class] superclass]);
 NSLog(@"%@", [[[block class] superclass] superclass]);
 NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
__NSGlobalBlock__
__NSGlobalBlock
NSBlock
NSObject

结论:

block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象

block的变量捕获(capture)

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


验证:

void (^block)(void);

int weight_ = 10;
static int staticWeight_ = 10;

void test()
{
*********************************
    自动变量,离开作用域就销毁, auto默认自带,可以不写
*********************************
    auto int age = 10;
    static int height = 10;
    
    block = ^{
        // age的值捕获进来(capture)
        NSLog(@"age is %d, height is %d, weight_ is %d,staticWeight_ is %d", age, height,weight_,staticWeight_);
    };
    age = 20;
    height = 20;
    weight_ = 20;
    staticWeight_ = 20;
}

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

打印结果

age is 10, height is 20, weight_ is 20,staticWeight_ is 20

转为C++源码如下

void (*block)(void);

int weight_ = 10;
static int staticWeight_ = 10;
*********************************
__test_block_impl_0结构体中并没有捕获全局变量
*********************************
struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  int age;
  int *height;
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  int *height = __cself->height; // bound by copy

*********************************
age是值传递
*height 是指针传递,存储的是外部变量的地址
weight_和staticWeight_参数直接调用全局变量,并没有捕获
*********************************
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r_gswfk35n5938fbdhf6s4xw_c0000gp_T_main_a117a3_mi_0, age, (*height),weight_,staticWeight_);
    }

static struct __test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)};


int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        test();
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return 0;
    }
}

思考:在block里面访问self呢?

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

- (void)test;

@end

#import "Person.h"

@implementation Person

- (void)test
{
    void (^block)(void) = ^{
        NSLog(@"%@---%@----%@",self, self->_name, [self name]);
    };
    block();
}

@end

转为C++代码如下

*********************************
block捕获了self
*********************************
struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  Person *self;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
  Person *self = __cself->self; // bound by copy
*********************************
block通过捕获的self调用self的成员变量和方法
*********************************
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r_gswfk35n5938fbdhf6s4xw_c0000gp_T_Person_606d56_mi_0,self, (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)), ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")));
    }
static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __Person__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
  void (*dispose)(struct __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};
*********************************
OC转C默认传递两个参数self 和 SEL _cmd
参数就是局部变量,所以block会捕获self
*********************************
static void _I_Person_test(Person * self, SEL _cmd) {
    void (*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

block的类型

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )



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

验证:

将工程置于MRC下(在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上)


void test()
{
    // Global:没有访问auto变量
    void (^block1)(void) = ^{
    };
    NSLog(@"没有访问auto变量block类型---------%@",block1);
    NSLog(@"copy后---------%@",[block1 copy]);
    // Stack:访问了auto变量
    int age = 10;
    void (^block2)(void) = ^{
        NSLog(@"age---------%d", age);
    };
    NSLog(@"访问auto变量block类型---------%@", block2);
    NSLog(@"copy后-%@", [block2 copy]);
}

调用

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

结果如下:

没有访问auto变量block类型---------<__NSGlobalBlock__: 0x1000020a8>
copy后---------<__NSGlobalBlock__: 0x1000020a8>

访问auto变量block类型---------<__NSStackBlock__: 0x7ffeefbff3e0>
copy后-<__NSMallocBlock__: 0x10051b360>

block的copy

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

typedef void (^Block)(void);

Block myblock()
{
    int age = 10;
    return ^{
        NSLog(@"---------%d",age);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block = myblock();
        block();
        NSLog(@"%@", [block class]);
    }
    return 0;
}
---------10
__NSMallocBlock__
typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        Block block = ^{
            NSLog(@"---------%d", age);
        };
        NSLog(@"%@", [block class]);
    }
    return 0;
}
__NSMallocBlock__
NSArray *arr = @[];
[arr sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        }];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        });

block作为属性变量的写法

MRC下block属性的建议写法

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

ARC下block属性的建议写法

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

对象类型的auto变量

验证:(MRC环境下)

#import <Foundation/Foundation.h>

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

#import "Person.h"

@implementation Person

- (void)dealloc
{
    [super dealloc];
    NSLog(@"Person - dealloc");
}

@end

调用auto修辞的person对象,并设置断点

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

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            block = ^{
                NSLog(@"---------%d", person.age);
            };
            NSLog(@"%@",[block class]);
            [person release];
        }
      *******************************
        断点处
      *******************************
        NSLog(@"------");
    }
    return 0;
}

打印如下

__NSStackBlock__
Person - dealloc

可以看到block未释放时,person对象已经被提前释放,说明block并没有对person产生强引用

  1. 会调用block内部的copy函数
  2. copy函数内部会调用_Block_object_assign函数
  3. _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

验证:(ARC环境下)

#import <Foundation/Foundation.h>

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

#import "Person.h"

@implementation Person

- (void)dealloc
{
//    [super dealloc];
    NSLog(@"Person - dealloc");
}

@end

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

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
//            __weak Person *weakPerson = person;
            block = ^{
                NSLog(@"---------%d", person.age);
            };
            NSLog(@"%@",[block class]);
        }
      *******************************
        断点处
      *******************************
        NSLog(@"------");
    }
    return 0;
}

打印如下

__NSMallocBlock__

可以看到block未释放时,person对象也没有释放

源码解读:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
*********************************
如果auto对象没有用_weak修辞,默认就是__strong
*********************************
  Person *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
*********************************
封装了block执行逻辑的函数
*********************************
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Person *__strong person = __cself->person; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r_gswfk35n5938fbdhf6s4xw_c0000gp_T_main_07fbbe_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
            }
*********************************
根据auto对象类型变量的修饰符(__strong、__weak、__unsafe_unretained)
做出相应的操作,形成强引用(retain)或者弱引用
*********************************
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*/);}
*********************************
释放引用的auto对象类型变量
*********************************
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
*********************************
block的描述,一旦访问的是对象类型,就会多了__main_block_copy_0,
和 __main_block_dispose_0函数,对对象类型变量的引用和释放
*********************************
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};

思考,如果用

 __weak Person *weakPerson = person;

呢?
结果就是

__NSMallocBlock__
Person - dealloc
  1. 会调用block内部的dispose函数
  2. dispose函数内部会调用_Block_object_dispose函数
  3. _Block_object_dispose函数会自动释放引用的auto变量(release)
#import <Foundation/Foundation.h>
#import "Person.h"

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
           // __weak Person *weakPerson = person;
            block = ^{
                NSLog(@"---------%d", person.age);
            };
            NSLog(@"%@",[block class]);
        }
        NSLog(@"------");
    }
    *******************************
        断点处
    *******************************
    return 0;
}

结果如下:

__NSMallocBlock__
------
Person - dealloc

源码解读在上面第2步

__weak问题解决

在使用clang转换OC为C++代码时,可能会遇到以下问题

cannot create __weak reference in file using manual reference

解决方案:支持ARC、指定运行时系统版本,比如

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

__block修饰符

__block可以用于解决block内部无法修改auto变量值的问题
__block不能修饰全局变量、静态变量(static

typedef void (^Block)(void);

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

转为底层C++代码

***************************
编译器把__block变量age包装成一个对象,转变C++ 就是__Block_byref_age_0结构体
__forwarding指针指向__Block_byref_age_0结构体
***************************
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
***************************
捕获的age的内存地址和外部变量age的内存地址是一样的
***************************
 int age;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
***************************
block内部持有的 __Block_byref_age_0 类型的age指针指向上面的结构体
这里是强引用关系
***************************
  __Block_byref_age_0 *age; // by ref
  ...
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref
***************************
block内部修改age变量的值会先找到__Block_byref_age_0结构体
->__forwarding指针->age
***************************
            (age->__forwarding->age) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r_gswfk35n5938fbdhf6s4xw_c0000gp_T_main_3520b8_mi_0, (age->__forwarding->age));
        }


int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {
                                                          (void*)0,
                                                          ***************************
                                                          对应的是__Block_byref_age_0 *__forwarding;
                                                          说明*_forwarding接收的是age的内存地址
                                                          ***************************
                                                          (__Block_byref_age_0 *)&age, 
                                                          0, 
                                                          sizeof(__Block_byref_age_0), 
                                                          10
                                                          };
       ...
    }
    return 0;
}

编译器会将__block变量包装成一个对象

__block的内存管理

  1. 会调用block内部的copy函数
  2. copy函数内部会调用_Block_object_assign函数
  3. _Block_object_assign函数会对__block变量形成强引用(retain)
  1. 会调用block内部的dispose函数
  2. dispose函数内部会调用_Block_object_dispose函数
  3. _Block_object_dispose函数会自动释放引用的__block变量(release)

__block的__forwarding指针

对象类型的auto变量、__block变量

_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*/);
_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修饰的对象类型

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

typedef void (^Block) (void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block Person *person = [[Person alloc] init];
        Block block = ^{
            NSLog(@"%p", person);
        };
        block();
    }
    return 0;
}

转为C++底层源码

*************************
__Block修辞的person对象转成C++变成了__Block_byref_person_0结构体
*************************
struct __Block_byref_person_0 {
  void *__isa; 指针占用8个字节内存空间
__Block_byref_person_0 *__forwarding; 8
 int __flags; 4
 int __size; 4
*************************
 __Block_byref_person_0结构体中持有person对象
 copy和dispose函数是对person对象的引用和释放
*************************
 void (*__Block_byref_id_object_copy)(void*, void*); 8
 void (*__Block_byref_id_object_dispose)(void*); 8
*************************
__Block_byref_person_0结构体内存地址+40就是person的内存地址
在MRC情况下不会对person强引用
*************************
 Person *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);
}

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

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*/);}

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

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
*************************
对__Block_byref_person_0 *person的内存管理操作
*************************
  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};

结论:

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

循环引用问题

#import <Foundation/Foundation.h>

typedef void (^Block) (void);

@interface Person : NSObject
@property (copy, nonatomic) Block block;

- (void)test;

@end

#import "Person.h"

@implementation Person

- (void)dealloc
{
********************
MRC打开 //[super dealloc];
********************
    NSLog(@"%s", __func__);
}

- (void)test
{
    self.block = ^{
        NSLog(@"%@", self);
    };
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        [person test];
    }
**********************
设置断点
**********************
    NSLog(@"*****");
    return 0;
}

可以看到在block函数执行完毕后,dealloc函数并没有执行,person对象没有释放

关于block持有self的问题,上面从C++源码角度已经解读就不在说了

解决循环引用问题 - ARC

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

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

__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block Person *person = [[Person alloc] init];
        person.block = ^{
            NSLog(@"%@", person);
            person = nil;
        };
        person.block();
    }
    NSLog(@"*****");
    return 0;
}

解决循环引用问题 - MRC

 __unsafe_unretained typeof(self) weakSelf = self;
self.block = [^{
      //__strong typeof(weakSelf) strongSelf = weakSelf;
      NSLog(@"%@", weakSelf);
} copy];
[self release];
__block typeof(self) weakSelf = self;
self.block = [^{
//__strong typeof(weakSelf) strongSelf = weakSelf;
     NSLog(@"%@", weakSelf);
} copy];
[self release];
上一篇下一篇

猜你喜欢

热点阅读