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

objective-c Block 相关内容

2015-11-09 18:49 288 查看

1、什么是Blocks

块可以实现闭包。这项语言特性是作为“扩展”(extension)而加入GCC编译器中的,在近期版本的Clang中都可以使用。10.4版及其后的Mac OS X系统,与4.0版及其后的iOS系统中,都含有正常执行块所需要的组件。从技术上讲,这是个位于C语言层面的特性,因此,只要有支持此特性的编译器,以及能执行块的运行期组件,就可以在C,C++,Objective-C、Objective-C++代码中使用它。

Blocks是C语言的扩展功能。可以用一句话来表示Blocks的扩展功能:带有自动变量(局部变量)的匿名函数。顾名思义,所谓匿名函数就是不带有名称的函数。C语言的标准不允许存在这样的函数。Blocks提供了类似由C++和Objective-C类生成实例或对象来保持变量的方法,其代码量与编写C语言函数差不多。如“带有自动变量”,Blocks保持自变量的值。

2、Block语法

完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同。

1)没有函数名;

2)带有“^”。

第一点不同是没有函数名,因为它是匿名函数;第二点不同是返回值类型前带有“^”(插入记号,caret)记号。Block语法如下:



如:^int (int count) { return count + 1; }

Block语法可省略好几个项目,首先是返回值类型;



省略返回类型时,如果表达式中有return语句就使用该返回值的类型,
4000
如果表达式中没有return语句就使用void类型。表达式中憨厚多个return语句时,所有return的返回值类型都必须相同。

如:^int (int count) { return count + 1; }

返回值类型以及参数列表均被省略的Block语法是大家最为熟悉的记述方式:



如:^ { printf("Blocks\n"); }

3、Block类型变量

Block语法单在其记述上来看,除了没有名称以及带有“^”以外,其他都与C语言函数定义相同。

声明Block类型变量的示例如:int (^blk) (int);

声明Block类型变量仅仅是将声明函数指针类型变量的“*”变为"^“,该类型变量与一般的C语言变量完全相同,可作为以下用途使用:

自动变量
函数参数
静态变量
静态全局变量
全局变量

4、截获自动变量值

通过Block语法和Block类型的变量说明,我们已经理解了”带有自动变量值的匿名函数“中”匿名函数“。而”带有自动变量值“究竟是什么呢?”带有自动变量值“在Blocks中表现为”截获自动变量“。

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();	// val = 10

return 0;
}

该源码中,Block语法的表达方式使用的是它之前声明的自动变量fmt和val。Block中,Block表达式截获所有的自动变量的值,即保存该自动变量的瞬间值。因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写Block中使用的自动变量的值也不会影响Block执行时自动变量的值。该源码的执行结果是:val = 10.

5、__block说明符

实际上,自动变量值截获只能保存执行Block语法瞬间的值,保存后就不能改写该值。使用附有__block说明符的自动变量可以在Block中赋值,该变量称为__block变量。

int val = 0;
void (^blk) (void) = ^{ val = 1; };	// error

__block int val = 0;
void (^blk) (void) = ^{ val = 1; };	// ok

6、截获的自动变量

如果将值赋给Block中截获的自动变量,就会产生编译错误。那么截获Objective-C对象,调用改变该对象的方法也会产生编译错误么?这是没问题的,而向截获的变量array赋值则会产生编译错误。该源代码中截获的变量值为NSMutableArray类的对象,如果用C语言来描述,即是截获NSMutableArray类对象用的结构体实例指针。虽然赋值给截获的自动变量array的操作会产生编译错误,但使用截获的值却不会有任何问题。

id array = [[NSMutableArray alloc] init];
void (^blk) (void) = ^ {

id = obj = [[NSObject alloc] init];

[array addObject: obj]; // ok

array = [[NSMutableArray alloc] init]; // error
};

另外,在使用C语言数组时必须小心使用其指针。

const char text[] = "hello";

void (^blk) (void) = ^ {
printf("%c\n", text[2]);
}

只是使用C语言的字符串字面量数组,而并没有向截获的自变量赋值,因此看似没有问题,但实际上回产生编译错误。这是因为在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获。此时,使用指针可以解决该问题。
const char* text = "hello";

void (^blk) (void) = ^ {
printf("%c\n", text[2]);
}

7、块的实质

如果块所捕获的变量是对象类型,那么就会自动保留它。系统在释放这个块的时候,也就将其一并释放。这就引出了一个与块有关的问题,块本身可视为对象。实际上,在其他Objective-C对象所能响应的选择子中,有很多是块也能响应的。而最重要之处则在于,块本身也和其他对象一样,有引用计数。当最后一个指向块的引用移走之后,块就回收了。

如果将块定义在Objective-C类的实例方法中,那么除了可以访问类的所有实例变量之外,还可以使用self变量。块总能修改实例变量,所以在声明时无须加__block。不过,如果通过读写操作捕获了实例变量,那么也会自动把self变量一并捕获,因为实例变量是与self所指代的实例变量关联在一起的。例如:

@interface EOCClass

- (void) anInstanceMethod {
//...
void (^blk) () = ^ {
_anInstanceVariable = @"Something";
NSLog(@"_anInstanceVariable = %@", _anInstanceVariable);
};
//..
}

@end

如果某个EOCClass实例正在执行anInstanceMethod方法,那么self变量就指向此实例。由于块里没有明确使用self变量,所以很容易就会忘记self变量其实也为块所捕获了。直接访问实例变量和通过self来访问时等效的:
<span style="white-space:pre">	</span>self->_anInstanceVariable = @"_Something";
之所以要捕获self变量,原因在于此。然而,一定要记住:self也是 ,因而块在捕获它时也会将其保留。如果self所指代的那个对象同时也保留了块,那么这种情况通常就会导致”保留环”。

8、块的内部结构

每个Objective-C对象都占据某个内存区域。块本身也是对象,在存放块对象的内存区域中,首个变量是指向Class对象的指针,该指针叫做isa。其余内存里含有块对象正常运转所需的各种信息。块对象的内存布局如下:



在内存布局中,最重要的就是invoke变量,这是个函数指针,指向块的实现代码。函数原型至少要接受一个void*型的参数,此参数代表块。

descriptor变量是指向结构体的指针,每个块里都包含此结构体,其中声明了块对象的总体大小,还声明了copy与dispose这两个辅助函数所对应的函数指针。辅助函数在拷贝及丢弃块对象时运行,其中会执行一些操作,比方说,前者保留捕获的对象,后者将之释放。

块还会把它所捕获的所有变量都拷贝一份。这些拷贝放在descriptor变量后面,捕获了多少个变量,就要占据多少内存空间。请注意,拷贝的并不是对象本身,而是指向这些对象的指针变量。invoke函数为何需要将块对象作为参数传进去呢?原因在于,执行块时,要从内存中把这些捕获到的变量读出来。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ios block