您的位置:首页 > 移动开发 > IOS开发

IOS block

2015-11-20 18:01 501 查看
准备走ios开发之一块,但是感觉C/C++的底子还是不够,既然之前学过,趁着刚刚学oc,还能做个对比什么的。


参考:

http://www.ithao123.cn/content-5613988.html

http://www.cnblogs.com/wustlj/archive/2013/08/12/3252152.html

http://www.dreamingwish.com/article/block%E4%BB%8B%E7%BB%8D%EF%BC%88%E5%9B%9B%EF%BC%89%E6%8F%AD%E5%BC%80%E7%A5%9E%E7%A7%98%E9%9D%A2%E7%BA%B1%EF%BC%88%E4%B8%8B%EF%BC%89.html

http://www.cnblogs.com/biosli/archive/2013/05/29/iOS_Objective-C_Block.html

http://www.zhihu.com/question/30779258

http://blog.csdn.net/hherima/article/details/38586101?utm_source=tuicool&utm_medium=referral

block 被称为自动截获变量匿名函数

1。内存方面

普通声明的block是存在栈中的,通过copy得来的会存储在堆中。

int main(int argc, const char *argv[]){
@autoreleasepool{
int j = 1;
__block int i = 1024;
void (^block)(void) = ^{
printf("%d%d\n", i, j);
}
block();
void (^inheapblock)(void) = Block_copy(block);
inheapblock();
i++;
j++;
block();
inheapblock();
block();
printf("%d %d\n", i, j);
}
}


结果会是

1024 1

1024 1

1025 1

1025 1

1025 2

首先,我们在栈上创建了变量ij,并赋予初始值,然后创建一个block变量名为block,但未赋值。

然后我们初始化这个block,赋值为一个只有一句printf的block,值得注意的是你定义完block之后,其实是创建了一个函数,在创建结构体的时候把函数的指针一起传给了block,所以之后可以拿出来调用。,其引用到的常规变量会进行如下操作:

没有__block标记的变量,其值会被复制一份到block私有内存区 有__block标记的变量,其地址会被记录在block私有内存区 然后调用block,打印1024, 1很好理解.

接下来复制block到堆,名曰inheapblock(下面称为ihb),调用,打印1024, 1也很好理解.

接下来我们为ij增值,使其变为1025和2,此时再调用block或者ihb,会发现结果为1025, 1,这是因为变量j早已在创建原始的block时,被赋值进block的私有内存区,后续对i的操作并非操作的私有内存区的复制品,当调用block或者ihb时,其打印使用的是私有内存区的复制品,故而打印结果依旧为1;而变量j的修改会实时生效,因为block记录的是它的地址,通过地址来访问其值,使得外部对j的修改在block中得以生效.

我们再来看下源码剖析

http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/runtime.c

http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/Block_private.h

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. */
};


在Objective-C中,类都是继承NSObject的,NSObject里面都会有个isa,是一个objc_class指针。

我们可以通过clang-rewrite 去看下,我们的block到底怎样实现的。

^int(){printf("val"); return 111;};


这个block会被转化为

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

struct __testBlock_block_impl_0 {
struct __block_impl impl;
struct __testBlock_block_desc_0* Desc;
__testBlock_block_impl_0(void *fp, struct __testBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};


__testBlock_block_impl_0是block结构,他的第一个属性也是一个结构__block_impl,而第一个参数也是一个isa的指针。我们可以看到isa指针刚刚的值是&_NSConcreteStackBlock;,在栈上创建的。

在运行时,NSObject和block的isa指针都是指向在对象一个4字节。

isa指针一共有以下几种

BLOCK_EXPORT void * _NSConcreteStackBlock[32];

BLOCK_EXPORT void * _NSConcreteMallocBlock[32];

BLOCK_EXPORT void * _NSConcreteAutoBlock[32];

BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32];

BLOCK_EXPORT void * _NSConcreteGlobalBlock[32];

BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];

– 如果是定义在函数外面的block是global的,另外如果函数内部的block但是,没有捕获任何自动变量,那么它也是全局的。比如下面这样的代码:

void (^block) (void) = ^{ printf("Hello world\n"); }; int

main(int argc, const char * argv[]) {     ...... }
clang

-rewrite 之后会发现是是 isa = _NSConcreteGlobalBlock;

copy 对 Global无效

在非ARC下执行如下代码:

//非MyBlock block = func();
NSLog(@"%d", block());
NSLog(@"%@", [block class]);

MyBlock block2 = [block copy];
//Copy操作对__NSGlobalBlock__类型无效
NSLog(@"%d", block == block2);


123

__NSGlobalBlock__

1

可以看到,copy后的
Block和原来是同一个对象的。

而对于引用了外部变量的Block,如果没有对他进行copy,他的作用域只会在声明他的函数栈内(类型是NSStackBlock),

一种情况在非ARC下是无法编译的:

typedef int(^blk_t)(int);
blk_t func(int rate){
return ^(int count){return rate*count;} }


而对于引用了外部变量的Block,如果没有对他进行copy,他的作用域只会在声明他的函数栈内(类型是NSStackBlock),如果想在非ARC下直接返回此类Block,Xcode会提示编译错误的

这是因为:block捕获了栈上的rate自动变量,此时rate已经变成了一个结构体,而block中拥有这个结构体的指针。即如果返回block的话就是返回局部变量的指针。而这一点恰是编译器已经断定了。在ARC下没有这个问题,因为ARC会自动加入copy操作(类型是NSMallocBlock

3.有时候我们需要调用block 的copy函数,将block拷贝到堆上。看下面的代码:

-(id) getBlockArray{
int val =10;
return [[NSArray alloc]initWithObjects:
^{NSLog(@"blk0:%d",val);},
^{NSLog(@"blk1:%d",val);},nil];
}

id obj = getBlockArray();
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();


这段代码在最后一行blk()会异常,因为数组中的block是栈上的。因为val是栈上的。解决办法就是调用copy方法。

4.不管block配置在何处,用copy方法复制都不会引起任何问题。在ARC环境下,如果不确定是否要copy block尽管copy即可。ARC会打扫战场。

注意:在栈上调用copy那么复制到堆上,在全局block调用copy什么也不做,在堆上调用block 引用计数增加

但是可以改变全局变量、静态变量、全局静态变量。 其实这两个特点不难理解:第一、为何不让修改变量:这个是编译器决定的。理论上当然可以修改变量了,只不过block捕获的是自动变量的副本,名字一样。为了不给开发者迷惑,干脆不让赋值。道理有点像:函数参数,要用指针,不然传递的是副本。 第二、可以修改静态变量的值。静态变量属于类的,不是某一个变量。所以block内部不用调用cself指针。所以block可以调用。 解决block不能保存值这一问题的另外一个办法是使用__block修饰符。

http://www.cnblogs.com/biosli/archive/2013/05/29/iOS_Objective-C_Block.html中有介绍关于copy的问题
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: