OC底层探究 – Block

Block

Block的本质

我们可以在这里看到Block的定义:

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

struct Block_layout {
    void *isa;
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};
复制代码

Block的底层结构如图所示:

block

实际上Block的底层就是这两个结构体,我们来举个简单的例子验证一下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 20;
        void (^block)(void) =  ^(){
            NSLog(@"Hello World! -- %d", age);
        };
        block();
    }
    return 0;
}
复制代码

使用 clang 指令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp 将 mian.m 文件转为 main.cpp 文件:

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;
  int age;
  // c++中的构造函数,类似于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 age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5g_y8xkr6bx58b0lj4mnl9d_gsh0000gn_T_main_6c8305_mi_0, age);
        }

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; 

        int age = 20;
        // 定义block变量
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
        // 执行block内部的代码
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    }
    return 0;
}
复制代码

可以看到,block的底层结构实际上就是 struct __main_block_impl_0,其中包含了 isa指针、FuncPtr 函数地址,以及捕获的外部变量 age,在定义完block之后,其实是创建了一个函数 __main_block_func_0,在创建block结构体的时候把函数指针一起传给了block,所以之后可以拿出来调用。

所以,可以总结出:

  • block从结构上看,其实也是一个OC对象,它内部也有isa指针,block是封装了函数调用以及函数调用环境的OC对象
  • block本质上就是一个函数指针,即那个代码块的内存地址。block常用作传值,实际上就是把block的地址传到要调用block的地方。
  • block 实际上就是一个匿名函数,你定义一个block,实际上就是创建了一个函数,并把这个函数的地址交给block,当你想调用这个函数的时候就调用这个block。定义的时候包含了block的声明和实现。
  • blcok可以声明为属性,一般为copy,把block从栈区移动到堆区。正常我们使用Block是在栈上生成的,离开了栈作用域便释放了,如果copy一个Block,那么会将这个Block copy到堆上分配,这样就不再受栈的限制,可以随意使用啦。
  • 循环引用发生的条件就是持有这个block的对象,又被 block 所持有。为了避免循环引用,__weak typeof(self) weakSelf = self;

Block变量捕获

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

capture

示例一:

void (^block)(void);

void test()
{
  	// auto:自动变量,离开作用域就销毁
    auto int age = 10;
  	// static:静态变量,永远存在于内存中
    static int height = 10;
    
    block = ^{
        // age的值捕获进来(capture)
        NSLog(@"age is %d, height is %d", age, height);
    };
    
    age = 20;
    height = 20;
}

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

将其转换为c++代码:

void (*block)(void);

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

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
    }

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)};
void test()
{
    int age = 10;
    static int height = 10;

    block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));

    age = 20;
    height = 20;
}

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

可以看到,age 的值被捕获进 block 结构体中,height 的指针也被捕获进 block 结构体中,所以在外面修改 age 是不会影响到 block 中的 age 变量的,而在 block 中修改 height 的值,外面的 height 变量的值也会改变。

那么为什么需要变量捕获呢?

  • 我们可以看到age和height都是被定义在test函数中的,而我们调用他们的时候是在 __main_block_func_0 函数中,如果我们不捕获它们,就需要进行跨函数的变量访问,这是不合理的。
  • age的值在离开作用域之后就会被销毁,如果我们不把它的值捕获,有可能之后就找不到它了。
  • height的值会一直保存在内存中,所以我们不需要捕获它的值,而是把它的内存地址捕获,以便之后可以找到它。

示例二:

int age_ = 10;
static int height_ = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^{
            NSLog(@"age is %d, height is %d", age_, height_);
        };

        age_ = 20;
        height_ = 20;

        block();
    }
    return 0;
}
复制代码

将其转换为c++代码:

int age_ = 10;
static int height_ = 10;

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_5g_y8xkr6bx58b0lj4mnl9d_gsh0000gn_T_main_4b848e_mi_0, age_, height_);
        }

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 argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        age_ = 20;
        height_ = 20;

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
复制代码

可以看到,全局变量 age_ 和 height_ 并没有被捕获进 block 的结构体中,而是直接访问了。

示例三:

@implementation MJPerson
- (void)test
{
    void (^block)(void) = ^{
        NSLog(@"-------%@", self);
    };
    block();
}
@end
复制代码

self 会被捕获进block吗?答案是会!

struct __MJPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __MJPerson__test_block_desc_0* Desc;
  MJPerson *self;
  __MJPerson__test_block_impl_0(void *fp, struct __MJPerson__test_block_desc_0 *desc, MJPerson *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
复制代码

因为其实在OC中,函数调用时其实有两个隐藏参数 (MJPerson * self, SEL _cmd) ,而在 test 中调用的self,其实就是传入的参数,那么参数也是局部变量,所以也会被block捕获。

Block类型

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

  • __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
  • __NSStackBlock__ ( _NSConcreteStackBlock )
  • __NSMallocBlock__ ( _NSConcreteMallocBlock )
void test()
{
    // __NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
    void (^block)(void) = ^{
        NSLog(@"Hello");
    };
    
    NSLog(@"%@", [block class]); // __NSGlobalBlock__
    NSLog(@"%@", [[block class] superclass]); // __NSGlobalBlock
    NSLog(@"%@", [[[block class] superclass] superclass]); // NSBlock
    NSLog(@"%@", [[[[block class] superclass] superclass] superclass]); // NSObject
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        
        // 堆:动态分配内存,需要程序员自己申请内存,也需要程序员自己管理内存
        void (^block1)(void) = ^{
            NSLog(@"Hello");
        };
        
        int age = 10;
        void (^block2)(void) = ^{
            NSLog(@"Hello - %d", age);
        };
        
        NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
            NSLog(@"%d", age);
        } class]);
      	// __NSGlobalBlock__  __NSMallocBlock__  __NSStackBlock__
    }
    return 0;
}
复制代码

block类型

block类型 环境
__NSGlobalBlock__ 没有访问 auto 变量
__NSStackBlock__ 访问了 auto 变量(MRC环境下)
__NSMallocBlock__ __NSStackBlock__ 调用了 copy

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

block copy

void (^block)(void)
void test()
{
    // Global:没有访问auto变量
    void (^block1)(void) = ^{
        NSLog(@"block1---------");
    };
    
    // Stack:访问了auto变量
    int age = 10;
    block = ^{
        NSLog(@"block2---------%d", age);
      	// block2---------234323432
    };
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      	test();
        block();
    }
    return 0;
}
复制代码

在 MRC 环境下,全局变量 block 的指针指向的是栈空间的一段内存,也就是说 block 保存在栈上,而栈空间一旦离开了作用域,就会被释放掉,所以在 test 函数执行完后,全局变量 block 指向的那段内存被释放掉了,于是在执行block的时候,打印出来的 age 是乱码。

想要把处在栈空间的Block保存下来,留待以后调用,只需要调用一次 copy 方法,就会把 block 从栈空间复制到堆空间,堆空间内的变量需要程序员自己管理内存,只要程序员不主动释放,block 就会一直存在。

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

Blcok的copy

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

  • block作为函数返回值时

  • 将block赋值给 __strong 指针时,也就是被强指针引用时

  • block作为 Cocoa API 中方法名含有usingBlock的方法参数时

    NSArray * arr = [];
    [arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    
    }];
    复制代码
  • block作为 GCD API 的方法参数时

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                
    });
    复制代码

对象类型的auto变量

typedef void (^MJBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJBlock block;
        
        {
            MJPerson *person = [[MJPerson alloc] init];
            person.age = 10;
            
            __weak MJPerson *weakPerson = person;
            block = ^{
                NSLog(@"---------%d", person.age);
            };
        }
        
        NSLog(@"------");
    }
    return 0;
}
复制代码

将其转换为c++代码

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  MJPerson *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__strong _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) {
  MJPerson *__strong person = __cself->person; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c41e64_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};
复制代码

当我们引用了对象类型的auto变量时,可以看到 __main_block_desc_0 函数中多了两个函数指针,copy 和 dispose,分别指向 __main_block_copy_0__main_block_dispose_0 ,用来进行内存管理。

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

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

  • 如果 block 被拷贝到堆上

    • 会调用 block 内部的 copy 函数
    • copy 函数内部会调用 _Block_object_assign 函数
    • _Block_object_assign 函数会根据 auto 变量的修饰符(__strong__weak__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
  • 如果 block 从堆上移除

    • 会调用 block 内部的 dispose 函数
    • dispose 函数内部会调用 _Block_object_dispose 函数
    • _Block_object_dispose 函数会自动释放引用的 auto 变量(release)
block内部函数 调用时机
copy 函数 栈上的 block 复制到堆时
dispose 函数 堆上的 block 被废弃时

当代码中存在 __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)
  • 编译器会将 __block 修饰的变量包装成一个对象

示例一:

typedef void (^MJBlock)(void);

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

将代码转换为c++代码:

typedef void (*MJBlock)(void);

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_5g_y8xkr6bx58b0lj4mnl9d_gsh0000gn_T_main_c4b2ed_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; 
        // 在这里可以看到,__Block_byref_age_0的构造函数中 (__Block_byref_age_0 *)&age 传递给了__forwarding
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};

        MJBlock block = ((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);
    }
    return 0;
}
复制代码

可以看到,添加了 __block 修饰符的age变量,在block中变成了 __Block_byref_age_0 结构体,结构体中有一个 isa 指针,一个 __forwarding 指针(指向自身的指针),age 变量,我们在 block 中修改的 age 变量,实际上是修改的 __Block_byref_age_0 结构体中的 age 变量。

__forwarding__Block_byref_age_0 结构体类型的,并且 __forwarding 存储的值为 (__Block_byref_age_0 *)&age ,即结构体自己的内存地址。

示例二:

typedef void (^MJBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block NSObject *obj = [[NSObject alloc] init];
        MJBlock block = ^{
            obj = nil;
        };
        block();
    }
    return 0;
}
复制代码

将代码转换为c++代码:

typedef void (*MJBlock)(void);

struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_obj_0 *obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
复制代码

可以看到,添加了 __block 修饰符的对象 obj,底层也被包装成了 __Block_byref_obj_0 结构体,内部包含有指向 obj 对象的指针,相比于基础数据类型 age,只是其中多了 copy 和 dispose 函数指针用来进行内存管理。

__block的内存管理

  • 当block在栈上时,并不会对 __block 变量产生强引用

  • 当block被copy到堆时

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

    block1

    block2

  • 当 block 从堆中移除时

    • 会调用 block 内部的 dispose 函数
    • dispose 函数内部会调用 _Block_object_dispose 函数
    • _Block_object_dispose 函数会自动释放引用的 __block 变量(release)

    block3

    block4

__block__forwarding 指针

上面提到过 __forwarding 指针指向的是结构体自己。当使用变量的时候,通过结构体找到 __forwarding 指针,再通过 __forwarding 指针找到相应的变量。这样设计的目的是为了方便内存管理。

我们重回到源码中。当在block中修改 __block 修饰的变量时:

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_5g_y8xkr6bx58b0lj4mnl9d_gsh0000gn_T_main_c4b2ed_mi_0, (age->__forwarding->age));
}
复制代码
  • 当block在栈中时,__Block_byref_age_0 结构体内的 __forwarding 指针指向结构体自己。

  • 而当block被复制到堆中时,栈中的 __Block_byref_age_0 结构体也会被复制到堆中一份,而此时栈中的 __Block_byref_age_0 结构体中的 __forwarding 指针指向的就是堆中的 __Block_byref_age_0 结构体,堆中 __Block_byref_age_0 结构体内的 __forwarding 指针依然指向自己。

  • 此时当对age进行修改时:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      	// 栈中的age结构体
    		__Block_byref_age_0 *age = __cself->age; // bound by ref
      	// age->__forwarding 获取堆中的age结构体
      	// age->__forwarding->age 修改堆中age结构体内的age变量
    		(age->__forwarding->age) = 20;
    }
    复制代码

    通过 __forwarding 指针巧妙的将修改的变量赋值在堆中的 __Block_byref_age_0 中。

    __forwarding

__block修饰的对象类型

struct __Block_byref_weakPerson_0 {
    void *__isa; // 8
    __Block_byref_weakPerson_0 *__forwarding; // 8
    int __flags; // 4
    int __size; // 4
    void (*__Block_byref_id_object_copy)(void*, void*); // 8
    void (*__Block_byref_id_object_dispose)(void*); // 8
    MJPerson *__weak weakPerson; // 根据对象的修饰符来判断强弱引用,这里就是弱引用
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_weakPerson_0 *weakPerson; // 在堆上时,强引用
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__forwarding) {
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
        }
};
复制代码
  • __block 变量在栈上时,不会对指向的对象产生强引用
  • __block 变量被 copy 到堆时
    • 会调用 __block 变量内部的 copy 函数,__Block_byref_id_object_copy
    • copy 函数内部会调用 _Block_object_assign 函数
    • _Block_object_assign 函数会根据所指向对象的修饰符( __strong__weak__unsafe_unretained )做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于 ARC 时会 retain,MRC 时不会 retain)
  • 如果 __block 变量从堆上移除
    • 会调用 __block 变量内部的 dispose 函数,__Block_byref_id_object_dispose
    • dispose 函数内部会调用 _Block_object_dispose 函数
    • _Block_object_dispose 函数会自动释放指向的对象(release)

循环引用

循环引用发生的条件就是持有这个block的对象,又被block内部所持有。

循环引用

ARC下解决循环引用

  • __weak__unsafe_unretained 解决

    • __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
    • __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变,会产生野指针错误
    __weak typeof(person) weakPerson = person;
    person.block = ^{
    		NSLog(@"age is %d", weakPerson.age);
    };
    
    __unsafe_unretained typeof(person) weakPerson = person;
    person.block = ^{
    		NSLog(@"age is %d", weakPerson.age);
    };
    复制代码

    __weak解决循环引用

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

    __block MJPerson *person = [[MJPerson alloc] init];
    person.age = 10;
    person.block = ^{
    		NSLog(@"age is %d", person.age);
    		person = nil;
    };
    person.block();
    复制代码

    __block解决循环引用

MRC下解决循环引用

  • __unsafe_unretained 解决,MRC 不支持 __weak

    __unsafe_unretained MJPerson *person = [[MJPerson alloc] init];
    person.age = 10;
    person.block = [^{
    		NSLog(@"age is %d", person.age);
    } copy];
    [person release];
    复制代码
  • __block 解决,因为在MRC下,__block 变量持有的 person 对象不会 retain。

    __block MJPerson *person = [[MJPerson alloc] init];
    person.age = 10;
    person.block = [^{
    		NSLog(@"age is %d", person.age);
    } copy];
    [person release];
    复制代码

面试题

block的原理是怎样的?本质是什么?

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

  • block本质上就是一个函数指针,即那个代码块的内存地址。block常用作传值,实际上就是把block的地址传到要调用block的地方。

  • block 实际上就是一个匿名函数,你定义一个block,实际上就是创建了一个函数,并把这个函数的地址交给block,当你想调用这个函数的时候就调用这个block。定义的时候包含了block的声明和实现。

__block 的作用是什么?有什么使用注意点?

  • __block 修饰的对象,底层会将 __block 变量包装成一个结构体对象。

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

  • 需要注意内存管理的问题。

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

  • block一旦没有进行copy操作,就不会在堆上

  • 使用注意:循环引用问题

block在修改NSMutableArray时,需不需要添加 __block

NSMutableArray *array = [NSMutableArray array];
person.block = [^{
  	// 不需要
    [array addObject:@"111"];
  	// 需要
    array = nil;
} copy];
复制代码

Block使用实例

Fucking Block Syntax

As a local variable:

returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
复制代码

As a property:

@property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);
复制代码

As a method parameter:

- (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;
复制代码

As an argument to a method call:

[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
复制代码

As a parameter to a C function:

void SomeFunctionThatTakesABlock(returnType (^blockName)(parameterTypes));
复制代码

As a typedef:

typedef returnType (^TypeName)(parameterTypes);TypeName blockName = ^returnType(parameters) {...};
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享