Objective-C高级编程读书笔记(二)

Blocks

含义:带有自动变量值的匿名函数

Blocks 模式

Block 语法

^ 返回值类型 参数列表 表达式

与C语言函数的定义相比,不同点在于:没有函数名,带有^(插入记号)

Block 类型变量

参数类型 (^ 参数名称) (block参数列表)

自动变量的捕获

int main() {
  int dmy = 256;
  int val = 10;
  const char *fmt = "val = %d\n";
  void (^blk)(void) = ^{printf (fmt, val); };

  val = 2;
  fmt = "These values were changed, val = %d\n";
  blk();
  return 0;
}

// 执行结果
// val = 10
复制代码

从上面的代码可以看出,Block表达式使用的变量值,是在block声明时变量的瞬间值。也就是说block会捕获自动变量的瞬时值。在之后哪怕更改了变量的值,也不会对block造成影响。

__block说明符

block所捕获的值不能再block中进行修改或重新赋值。如果想要修改捕获的值的话,需要在block外对该变量使用__block进行修饰: __block int a = 0;

但是,对于oc对象的话,如果调用的是变更对象的方法的话,则没有问题。比方说给捕获到的数组添加元素。

Blocks 的实现

实质

int main() {
  void (^blk)(void) = ^{ printf("Block\n"); };

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

使用-rewrite-obj将上面的代码,转换为C++代码:

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;
  __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) {
printf("Block\n");}

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, char * argv[]) {
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

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

通过和源码的比对,可以看出对应的就是void (*blk) (void) = ((void(*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));这一句代码。这句代码的作用就是将栈上生成的__main_block_impl_0结构体实例的指针赋值给blk。

__main_block_impl_0()这个结构体的构造函数接受两个参数,第一个是c语言的函数指针,第二个是__main_block_desc_0的结构体实例指针。

换句话说,就是将__main_block_impl_0中的__block_impl初始化为:

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
复制代码

而Block的调用的话,就是简单的通过函数指针来调用函数:(*blk->impl.FuncPtr)(blk);

捕获变量

int main(int argc, char * argv[]) {
    
    int i  = 1;
    void (^blk)(void) = ^{
        printf("%d", i);
    };
    blk();
    return 0;
}
复制代码

同样将上面的代码改写为C++

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int i = __cself->i; // bound by copy

        printf("%d", i);
    }

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, char * argv[]) {

    int i = 1;
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}
复制代码

可以看到__main_block_impl_0的结构体中,block外的变量被作为成员变量添加到了结构体当中。然后其构造函数变成了__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i)这样:

impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
i = 1;
复制代码

因此可以看到,block的自动捕获变量的功能,实际上是将该变量值保存到block的结构体实例中(block自身)。

__block

在block中,静态变量,静态全局变量和全局变量这三种类型的变量是允许进行改写的,除此之外,其他类型的变量,如果想在block中进行修改,就只能使用__block变量。

int i = 0;加上__block这个说明符,然后改写成C++:

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref

        printf("%d", (i->__forwarding->i));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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, char * argv[]) {

    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}
复制代码

从代码我们看到,变量i变成了__Block_byref_i_0这个结构体实例(__block修饰的变量会被转化为__Block_byref_ParameterName_0这样的结构体),该结构体的声明如下:

struct __Block_byref_i_0 {
  void *__isa;
  __Block_byref_i_0 *__forwarding;
  int __flags;
  int __size;
  int i; // 原变量
}
复制代码

如果在block中为__block变量赋值的话,其转换代码为:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_i_0 *i = __cself->i; // bound by ref

        (i->__forwarding->i) = 2;
    }
复制代码

__Block_byref_val_0结构体实例的成员变量__forwarding是个指向实例自身的指针:

并且__Block_byref_i_0这个结构体并不在__main_block_impl_0这个结构体的实例中,这是因为为了在多个block中使用__block变量

Block 存储域

Block实际上会转换为__main_block_impl_0这样的结构体类型的自动变量,__block变量会转换为__Block_byref_para_0这样的结构体类型自动变量。(结构体类型自动变量指的是栈上生成该结构体的实例)

名称实质
Block栈上Block的结构体实例
__block变量栈上__block变量的结构体实例

Block作为OC对象来看时,其类是_NSConcreteStackBlock,除了这个类之外,还有:

设置对象的存储域
_NSConcreteStackBlock
_NSConcreteGlobalBlock程序的数据区域
_NSConcreteMallocBlock

可以看到block的类型有三种,可是怎么知道block的类型呢?

当block为全局变量或者是未使用捕获自动变量时,为__NSConcreteGlobalBlock,其他情况为__NSConcreteStackBlock。 使用__block变量时,实际上是将block和变量从栈上复制到堆上,此时,block的类型为__NSConcreteMallocBlock

Block的类副本源的配置存储域复制效果
_NSConcreteStackBlock从栈复制到堆
_NSConcreteGlobalBlock程序的数据区域什么也不做
_NSConcreteMallocBlock引用计数增加

而对于__block变量的话:

__block变量的配置存储域Block被复制到堆时的影响
从栈复制到堆并被Block持有
被Block持有

在Block被复制到堆上的时候,原有的__forwarding的值会指向堆上的Block的__block变量的结构体实例的地址

捕获OC对象

通过__main_block_copy_0__main_block_dispose_0来改变对象的引用计数数值。 在以下几种情况下,Block会被复制到堆上:

  • 调用block的copy方法
  • Block作为函数返回值返回时
  • 将Block赋值给带有__strong的id类型的类或者Block类型的成员变量
  • 使用方法名称含有usingBlock的Cocoa框架方法或者是GCD的block时

循环引用

解决block循环引用的方法有两种,一种是__weak,另一种是__block.不过是引用__block的话,必须执行block,且在block中要将变量赋值为nil, 使用__block解决循环引用的优点在于可以控制对象的持有时间。

转载于:https://juejin.im/post/5a30e29151882559e225a58c

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值