Objective-C基于C语言闭包Block的实现

0.简单的Block定义和中间实现代码

先写一个简单的Block程序block.c:

#include <stdio.h>
typedef void(^TestBlock)(void);
int main()
{
    TestBlock testBlock = ^(){ printf("Hello World!\n"); };
    testBlock();
    return 0;
}
在命令行下用Clang工具的-rewrite-objc编译选项编译:

clang -rewrite-objc block.c
得到一份block.cpp的C语言中间代码,其中关于Block的主要代码如下:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;

};
typedef void(*TestBlock)(void);

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("Hello World!\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()
{
    TestBlock testBlock = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    return 0;

}

1.函数调用的理解

对于这个简单的Block实现别人也讲过很多遍了,我只有一处看明白的比较慢就是下面这行函数调用的代码:

((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
testBlock是void(*)(void)类型的函数指针,指向了一个__main_block_impl_0结构体,由于__main_block_impl_0结构体的内存第一部分是__block_impl,就可以通过指针的强制类型转换((__block_impl *)testBlock)->FuncPtr得到函数指针,由于__block_impl的函数指针类型是void *类型的此时就可以同样通过类型转换得到( void (*)(__block_impl *))类型的函数指针,虽然此时函数指针和真正调用的函数类型(void (*)( struct __main_block_impl_0 *__cself))不同,但是实际提供的参数testBlock实际上是__main_block_impl_0类型的。这里的关键就是__main_block_impl_0结构体内存结构以__block_impl结构体开始,这就像父子类关系一样,__block_impl扮演父类角色,__main_block_impl_0扮演子类角色。通过指针类型的强制转换来实现通用的Block函数调用。说是通用就是无论实现多少个Block都可以通过同样的这样的形式调用Block真正的执行函数。

2.Block类型的理解

(1)在非ARC下,这里虽然isa指向的是NSConcreteStackBlock,但是在LLVM编译下没有访问局部变量的Block应该是NSConcreteGlobalBlock类型的,访问了局部变量的Block是NSConcreteStackBlock类型的。
(2)在ARC下,访问了局部变量的Block是NSConcreteMallocBlock类型的,未访问局部变量的Block是NSConcreteGlobalBlock类型的。

3.Block对变量访问的理解

(1)普通栈变量   :通过传值实现一次性可读访问权限。
(2)static变量  :通过传地址实现读写权限。
(3)全局变量     :本身就具有可读写权限。
(4)__block变量 :通过把普通变量打包成__Block_byref_*_0结构体来传递地址实现变量的读写权限。__Block_byref_a_0结构体如下:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};
其中__forwarding在初始化的时候是指向自身的,当NSConcreteStackBlock类型的Block通过copy变为NSConcreteMallocBlock时,栈空间的Block的__forwarding指针会改变指向堆空间的Block。

4.Block中循环引用的解决

使用Block容易产生Retain Cycle。这是因为Block会自动retain他引用的对象。在使用SDWebImage时一个简单的Retain Cycle例子:

UIImageView *imgView = self.imageView;
[self.imageView setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) 
{
    // ...
    imgView.image = image; // warning: Capturing 'imgView' strongly in this block is likely to lead to a retain cycle

}];
self.imageView在调用setImageWithURL:completed:时会retain定义的这个Block,而Block用到了imgView又会自动retainimgView,所以就造成了Retain Cycle。解决办法:

(1)非ARC下

__block UIImageView *weakImgView = self.imageView;
[self.imageView setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) 
{
    // ...
    weakImgView.image = image;

}];

添加:在不用__block的说明符的情况下,当Block发生copy的时候会retain Block内部用到的对象实例。在用了__block说明符的情况下,打包成的

struct __Block_byref_a_0

只是记录的用到的对象的指针,在copy的时候并没有retain结构体内部的对象实例。

(2)ARC下

ARC下也是可以用__block打破循环引用的:

__block UIImageView *weakImgView = self.imageView;
[self.imageView setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType)
{
    // ...
    weakImgView.image = image;
    //注意下面这句
    weakImgView = nil;
}];

__block变量打包成的结构体内部对对象变量的引用是强引用,同样会产生循环引用,只不过是我们在Block执行最后的时候打破了(注释下面那句),但是如果我们不执行这个Block,循环引用还是存在的。所以最好用下面的方法。

__weak UIImageView *weakImgView = self.imageView;
[self.imageView setImageWithURL:url completed:^(UIImage *image,NSError *erroe,SDImageCacheType cacheType)
{
    // ...
    weakImgView.image = image;
}

为了异步操作回调的时候防止weakImgView为nil,我们可以这么做:

__weak UIImageView *weakImgView = self.imageView;
[self.imageView setImageWithURL:url completed:^(UIImage *image,NSError *erroe,SDImageCacheType cacheType)
{
    UIImageView *strongImgView = weakImgView;
    // ...
    strongImgView.image = image;
}

另外注:

(1)NSNotificationCenter的addObserverForName:object:queue:usingBlock:会retainBlock。

(2)NSTimer会retain目标Target。

(3)如果在iOS5.0之前,__unsafe_unretained 替代 __block

5.Block的使用

http://blog.csdn.net/joywii/article/details/9903095

6.参考链接

LLVM开源的Block实现源码:https://llvm.org/svn/llvm-project/compiler-rt/trunk/BlocksRuntime/
谈Objctive-C Block的实现:http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/
iOS中Block实现的探究:http://blog.csdn.net/jasonblog/article/details/7756763
Objctive-C Blocks Quiz:http://blog.parse.com/2013/02/05/objective-c-blocks-quiz/
对Objective-C中Block的追探:http://www.cnblogs.com/biosli/archive/2013/05/29/iOS_Objective-C_Block.html 

正确的使用Block http://tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值