block的原理探索-内存管理

/ 0

对象类型的auto变量

block 内部访问了对象类型的 auto 变量。

如果 block 是在栈上,将不会对 auto 变量产生强引用。

如果 block 被拷贝到堆上,会调用 block 内部的 copy 函数。

copy 函数内部会调用 _Block_object_assign 函数,然后根据 auto 变量的修饰符(__strong__weak__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。

如果 block 从堆上移除,会调用 block 内部的 dispose 函数。

dispose 函数内部会调用 _Block_object_dispose 函数,然后自动释放引用的 auto 变量(release)。

测试

block 中引用对象类型的 auto 变量:

typedef void (^Block)(void);

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

        Block block = ^{
            NSLog(@"%d", person.age);
        };
        block();
    }
    return 0;
}

转换后的 C++ 代码:

typedef void (*Block)(void);

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;
    Person *__strong person; // 对Person对象产生强引用
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock; // 不一定准,要以类对象为准[block class]
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

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_25_pb1crnzn29gcfxnjh75vhb900000gn_T_main_07f896_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}

// block被拷贝到堆上会执行copy,引用计数+1
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*/);}

// block从堆上被移除会执行dispose,引用计数-1
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;
    // 捕获对象类型变量,才会有copy/dispose,用于内存管理
    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;
        Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 10);
        Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

如果将 block 中引用的对象,用 __weak 修饰:

Person *person = [Person new];
person.age = 10;

__weak Person *weakPerson = person;

Block block = ^{
    NSLog(@"%d", weakPerson.age);
};
block();

最终转换后的代码,捕获的变量也是用 __weak 修饰,说明 block 没有对 Person 对象产生强引用(引用计数+1):

Person *__weak weakPerson;

block内部修改atuo变量值

__block 可以用于解决 block 内部无法修改 auto 变量值的问题。

__block 不能修饰全局变量静态变量static)。

测试

typedef void (^Block)(void);

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

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

// int age包装后的对象
struct __Block_byref_age_0 {
    void *__isa;
    __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
};

// block结构体
struct __main_block_impl_0 {
    // ...
    __Block_byref_age_0 *age; // 对象引用
};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        // __block int age = 10; 会转换成下面代码
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {
        (void*)0,
        (__Block_byref_age_0 *)&age,  // __forwarding指向结构体自己
        0,
        sizeof(__Block_byref_age_0),
        10};
        // ...
    }
    return 0;
}

循环引用问题

block 内部强引用持有对象,对象又强引用持有 block,就会形成循环引用。

__weak__unsafe_unretained 解决循环引用:

typedef void (^Block)(void);

@interface ViewController ()
@property (nonatomic, copy) Block block; // self 引用了 block
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // ARC可用
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        NSLog(@"%p", weakSelf); // block引用了self,但是用了__weak修饰,就是弱引用self
    };

    // MRC/ARC可用
    __unsafe_unretained id weakSelf = self;
    self.block = ^{
        NSLog(@"%p", weakSelf);
    };
}

@end

__block 解决,必须要调用 block,在内部释放强引用的对象:

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

block weak

__weak 只能修饰对象类型的变量,__block 则不限制变量的类型。所以又想在 block 内部修改变量的值,又想防止强引用,则可以使用 __block __weak 修饰对象类型的变量。