iOS学习笔记【六】—— Block

iOS学习笔记【六】—— Block

key point:Block 是 Objective-C 对于闭包的实现,本质是封装了函数调用以及函数调用环境的OC对象

源码解析

/* Block 结构体 */
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;
    }
};
/* 包含 Block 实际函数指针的结构体 */
struct __block_impl {
    void *isa;
    int Flags;               
    int Reserved;        // 今后版本升级所需的区域大小
    void *FuncPtr;      // 函数指针,指向 Block 的主体部分
};
/* Block 函数部分结构体 */
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("myBlock\n");
}
/* Block 附加信息结构体:包含今后版本升级所需区域大小,Block 的大小*/
static struct __main_block_desc_0 {
    size_t reserved;        // 今后版本升级所需区域大小
    size_t Block_size;    // Block 大小
} __main_block_desc_0_DATA;
void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));

Block的存储区

  • NSConcreteGlobalBlock,存储在程序的数据(.data)区域,没有用到外界变量或只用到全局变量、静态变量的block。
  • NSConcreteStackBlock,存储在栈区,捕获局部变量、成员变量,且没有被强引用。如果其所属的变量作用域结束,则该 Block 和__block 变量就会被废弃。
  • NSConcreteMallocBlock,存储在堆区,被强引用的栈block。可以将 Block 对象和__block 变量从栈区复制到堆区上,拷贝又分为自动拷贝和手动拷贝。
    在这里插入图片描述在这里插入图片描述

Block 的自动拷贝

MRC下需要我们手动拷贝。在使用 ARC 时,编译器会自动进行判断,自动生成将Block从栈上复制到堆上的代码:

  • 1.Block是函数的返回值
  • 2.Block被强引用,Block被赋值给__strong或者id类型
  • 3.在方法名中含有usingBlock的Cocoa方法或者GCD的API中传递Block时

Block 的手动拷贝

通过copy 实例方法来对 Block 进行手动拷贝,对于不同的block类型,拷贝的效果也不同:
在这里插入图片描述

Block捕获变量

在这里插入图片描述

int c = 180; // 全局(静态)变量任意访问,无需捕获
- (void)useBlockInterceptLocalVariables {
    int a = 10; // 栈区局部变量随作用域释放,值传递
    static int b = 20; // 静态局部变量随程序释放,指针传递
    void (^myLocalBlock)(void) = ^{
        printf("a = %d, b = %d, c = %d\n",a, b, c);
    };
}
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int a;
    int *b;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

** __block 修饰的普通局部变量**
当给普通局部变量a加上__block后,block内可以修改该变量,转换为源码如下:

struct __Block_byref_a_0 {
    void *__isa;
    __Block_byref_a_0 *__forwarding;
    int __flags;
    int __size;
    int a;
};
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_a_0 *a; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref
    (a->__forwarding->a) = 20;
    printf("a = %d\n",(a->__forwarding->a));
}

__main_block_impl_0 结构体中可以看到:新增了成员变量 __Block_byref_a_0 类型的结构体指针 a,通过指针的形式引用,这是为了可以在多个不同的 Block 中使用 __block 修饰的变量。
__Block_byref_a_0 有 5 个部分:

  • __isa:标识对象类的 isa 实例变量
  • __forwarding:传入变量的地址
  • __flags:标志位
  • __size:结构体大小
  • a:存放实变量 a 实际的值

如果对__block的修改,实际上是在修改堆上的__block变量。即__forwarding指针存在的意义就是,无论在任何内存位置,都可以顺利地访问同一个__block变量。
通过 __block 来修饰的变量,在 Block 的主体部分中改变值的原理其实是:通过指针传递的方式。
在这里插入图片描述

Block捕获对象

强引用对象,引用计数增加

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableArray *__strong arrM;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *__strong _arrM, int flags=0) : arrM(_arrM) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long 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
};

为管理对象变量,block需要在__main_block_desc_0结构体中增加的成员变量copy和dispose,以及作为指针赋值给该成员变量的__main_block_copy_0函数和__main_block_dispose_0函数。

  • __main_block_copy_0函数内部使用_Block_object_assign函数,相当于retain实例方法的函数,将对象类型对象赋值给结构体block的成员变量并持有该对象。调用copy函数的时机是栈上的block赋值到堆上时。
static void __main_block_copy_0(struct __main_block_impl_0 *dst,struct __main_block_impl_0 *src) {
    _block_object_assign(&dst->array, src_>array, BLOCK_FIELD_IS_OBJECT);
}
  • __main_block_dispose_0函数内部使用_Block_object_dispose函数,相当于release实例方法的函数,释放赋值在block用结构体变量中的对象。调用dispose函数的时机是堆上的block被废弃时。
static void __main_block_dispose_0(struct __main_block_impl_0 *src) {
    _block_object_dispose(src_>array, BLOCK_FIELD_IS_OBJECT);
}

ARC下使用__weak防止循环引用

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    Person *__weak weakPerson;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
        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 weakPerson = __cself->weakPerson; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_ct_0dyw1pvj6k16t5z8t0j0_ghw0000gn_T_retainCycleBlcok_447367_mi_0,weakPerson);
}

MRC下使用__block防止循环引用
通过指针的方式来访问对象,而没有对对象进行强引用,所以不会造成循环引用。

Block循环引用

  • 在block中,并不是所有的block都会造成循环引用,比如UIView动画block、Masonry添加约束block、AFN网络请求回调block等。UIView动画block不会造成循环引用是因为这是类方法,调用者对象不可能强引用一个类,所以不会造成循环引用。Masonry约束block不会造成循环引用是因为block并没有持有self。AFN请求回调block不会造成循环引用是因为在内部做了处理。
  • 在block中并不只是self会造成循环引用,用下划线调用属性(如_name)也会出现循环引用,效果和使用self是一样的。

Q&A

  • Block带来的问题,和delegate相比有什么好处:block使用更简单,能够直接访问上下文。容易造成循环引用,效率低,block出栈需要将使用的数据从栈内存拷贝到堆内存;delegate更安全,效率高,只是保存了一个对象指针。需要存储一些临时数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值