您的位置:首页 > 移动开发 > Objective-C

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

2013-11-11 17:20 405 查看

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/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息