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

IOS开发Block代码块基本语法使用-从基础到高级

2015-08-31 18:54 796 查看

1,Block的定义格式

 返回值类型 (^block变量名)(形参列表) = ^(形参列表) {};



2,使用typedef定义Block类型

typedef 返回值类型 (^block类型名称)(形参列表);
定义好的Block变量可以像其它数据类型那样方便的使用了。注意:Block语法是苹果公司提出的C语言的新功能,并不是标准功能。

另外,当函数的返回值是block代码块的时候,声明函数时候必须使用typedef定义的代码块类型的数据类型了。(后面做解释)

// 使用typedef声明两个变量类型
typedef int (^Block)(int, int); //加上typedef之后,Block不再是一个变量,而是一个数据类型
typedef int (^Block1)();

//使用typedef声明的变量类型定义另个变量
Block b1, b2;

//定义没有参数的代码块时候的三种写法
void (^block1)() = ^(){};
void (^block2)() = ^{};
void (^block3)(void) = ^{};

3,块对象作形参和块对象作函数返回值

块对象可以像其它变量和对象那样作为形参。

//块对象作为形参的写法,
void fun(int (^b)(int x, int y))
{
NSLog(@"b(3, 4) = %d", b(3, 4)); //在fun函数内部调用代码块,并传参。
}
<p class="p1"><span class="s1">//</span>代码块作为函数返回值</p><p class="p2"><span class="s2">typedef</span> <span class="s2">void</span> (^EmptyBlockType)();</p><p class="p3">//void (^EmptyBlockType)() fun1(int a) //<span class="s3">这样定义函数的返回值是不对的,这时候只能通过</span>typedef<span class="s3">定义新的</span>block<span class="s3">数据类型,用数据类型定义函数返回值,如下所示</span></p><p class="p4">EmptyBlockType<span class="s4"> fun1(</span><span class="s2">int</span><span class="s4"> a)</span></p><p class="p2">{</p><p class="p4"><span class="s4">    </span>EmptyBlockType<span class="s4"> block;</span></p><p class="p2">    <span class="s2">switch</span> (a) {</p><p class="p2">        <span class="s2">case</span> <span class="s5">0</span>:</p><p class="p2">            block = ^(){ <span class="s6">NSLog</span>(<span class="s7">@"</span><span class="s8">今天星期一</span><span class="s7">"</span>); };</p><p class="p2">            <span class="s2">break</span>;</p><p class="p2">        <span class="s2">case</span> <span class="s5">1</span>:</p><p class="p2">            block = ^(){ <span class="s6">NSLog</span>(<span class="s7">@"</span><span class="s8">今天星期二</span><span class="s7">"</span>); };</p><p class="p2">            <span class="s2">break</span>;</p><p class="p2">        <span class="s2">case</span> <span class="s5">2</span>:</p><p class="p2">            block = ^(){ <span class="s6">NSLog</span>(<span class="s7">@"</span><span class="s8">今天星期三</span><span class="s7">"</span>); };</p><p class="p2">            <span class="s2">break</span>;</p><p class="p2">        <span class="s2">case</span> <span class="s5">3</span>:</p><p class="p2">            block = ^(){ <span class="s6">NSLog</span>(<span class="s7">@"</span><span class="s8">今天星期四</span><span class="s7">"</span>); };</p><p class="p2">            <span class="s2">break</span>;</p><p class="p2">        <span class="s2">case</span> <span class="s5">4</span>:</p><p class="p2">            block = ^(){ <span class="s6">NSLog</span>(<span class="s7">@"</span><span class="s8">今天星期五</span><span class="s7">"</span>); };</p><p class="p2">            <span class="s2">break</span>;</p><p class="p5">            </p><p class="p2">        <span class="s2">default</span>:</p><p class="p2">            <span class="s2">break</span>;</p><p class="p2">    }</p><p class="p2">    <span class="s2">return</span> block;</p><p class="p2">}</p>
int main()
{
//1,直接在实参位置定义block代码块,这时候 如下的 int 表示代码块的返回值类型,可以写可以不写,不写的时候,程序会根据形参的返回值类型做类型转换,一般情况这里还是写返回值类型为好,避免不必要的错误。
fun(^int(int x, int y) {
return x + y;
});
//2,先定义一个block代码块,再将代码块类型的变量作为实参传给fun函数。
int (^block)(int, int) = ^(int a, int b){
return a * b;
};
fun(block);
<p class="p1"><span class="s1">//</span>函数返回值是<span class="s1">block</span>代码块</p><p class="p2"><span class="s2">    </span>EmptyBlockType<span class="s2"> b;</span></p><p class="p3">    <span class="s3">for</span> (<span class="s3">int</span> i = <span class="s4">0</span>; i < <span class="s4">5</span>; i++) {</p><p class="p4"><span class="s2">        b = </span><span class="s5">fun1</span><span class="s2">(i); </span>//<span class="s6">调用函数并用</span>block<span class="s6">变量</span>b<span class="s6">来接收函数的返回值</span></p><p class="p4"><span class="s2">        b(); </span>//<span class="s6">调用从</span>fun1<span class="s6">返回的</span>block<span class="s6">代码块</span></p><p class="p3">    }</p>
return 0;
}

4,块对象中的变量行为



/*
结论:
1,在block代码块内部可以访问定义的全局变量,局部变量,静态局部变量,但是访问局部静态变量时候是只读的并且局部变量和在代码块中访问到的不是同一个地址的变量,他们在数值上相等,互相似乎没什么联系。 因为代码块中使用到局部变量的时候,会将局部变量进行const类型的copy,所以在代码块中访问到的局部变量都是只读的;静态变量和全局变量都存放在静态区,在程序运行过程中都存在,他们可以在不同的代码块中共享,不同代码块中访问到的同一个全局变量,局部变量是同一块内存的数据;对于普通局部变量在代码块中只读,全局变量和静态局部变量在代码块中可以读写。
2,在块句法的主体中,除块句法内部的局部变量和形参之外,还包含块句法当前位置处可以访问的变量;这些变量中包含外部变量也包含块中可以访问的局部变量。
3,代码块中访问局部变量时候,局部变量会从栈内存被const类型的copy一份到堆内存中。
*/


块对象和函数指针的定义使用功能都差不多,块对象的精髓之处就在于,在块对象中可以访问到上下文的变量,而函数指针不能。

5,块对象的实例和生命周期

1)块句法也可以写在函数的外部,当写在函数外面时候,只是在静态数据区分配一块内存给块对象,这块区域在程序执行期间会一直存在。
2)块句法写在函数内部的时候,块对象和变量的生命周期和普通局部变量一样,块对象的内存区域会在执行包含块对象的函数时保存在栈上;该块对象的生命周期就是函数运行期间。
3)在现实的实现中,当函数内的块语法不包含自动变量的时候,就没必要进行复制值,所以块对象的内存区域也会被保存在静态数据区。
4)block代码块被保存在堆或者静态区中,不会被保存在栈中,如下图可以说明这一点。



代码如下所示:

//1,一个定义在函数外部的block代码块
EmptyBlockType static_empty = ^{
NSLog(@"static_empty");
};

//int glob = 10; //外部变量(全局静态变量)
int main()
{
//4,普通局部变量
int int_val = 30;
//2,一个定义在函数内部的块对象,块对象中用到了局部变量
EmptyBlockType block = ^{
NSLog(@"int_val:%d",int_val);
};
//3,一个定义在函数内部的块对象,块对象中没有用到局部变量
EmptyBlockType empty = ^{
NSLog(@"static_empty");
};
printf("1, %p \n2, %p \n3, %p \n4, %p", static_empty, block, empty, &int_val);return 0; }


示例:

void function()
{
int i;
int (^blocks[10])(); //定义一个块对象类型的数组
for (i = 0; i < 10; i++) { //for循环给数组赋值
blocks[i] = ^{ return i; };
}
for (i = 0; i < 10; i++) { //打印数组中的内容,就是每个数组存放的代码块的返回值
NSLog(@"%d", blocks[i]());
}
}
如上代码,在非ARC环境下运行结果是10个9,原因是虽然循环了十次,但是只有一个实体。以上代码在ARC环境下是正确的,后面做说明。

6,块对象的复制

函数内的块对象和局部变量的生命周期相同,都只是在函数的执行期间。但是在函数的方法调用参数中直接代入块对象也是块对象的一种非常常见的用法,这时候使用与函数调用关系或栈状态无关的块对象是非常必要的。
有一个函数可以复制块对象到新的堆内存,通过使用该函数,即使是在函数内部定义的块对象也能独立于栈被持续的使用,此外还有一个函数可以释放不需要的块对象。
Block_copy( block ) 
参数为栈上的块对象的时候,返回堆上的块对象。参数为堆上的块对象或者静态区的块对象,不进行复制,直接返回原对象,但是会增加参数块对象的引用计数。
Block_release( block )
减少参数块对象的引用计数。当引用计数减到0时候,块对象被释放。
在使用这些函数的时候,需要引入头文件Block.h .堆上的块对象使用引用计数的方式来管理。即使使用垃圾回收也必须成对出现。使用ARC时候可以不考虑这些,编译器会自动帮我们判断什么时候释放,什么时候保持。
用法示例:
g = Block_copy(block);
Block_rlease(g);

7,指定特殊变量 __block 

ARC下测试结果和总结:



非ARC下测试结果和总结:



8,IOS开发中,使用block代替代理

代码如下:
自定义类中
#import <UIKit/UIKit.h>

//声明一个含一个参数的block代码块类型的数据类型
typedef void (^MyBlock)(UIButton *btn);

@interface customView : UIView

//使用声明的数据类型定义一个变量
@property (nonatomic, strong)MyBlock clickBlock;

//重写set方法
- (void)setClickBlock:(MyBlock)clickBlock;

@end
- (void)btnClick:(UIButton *)sender
{
//在单击事件中,判断block代码快是否为空,如果不是空的话,执行block代码块
if (_clickBlock) {
_clickBlock(sender);
}else{
NSLog(@"没有找到方法");
}
}

//通过set方法给block类型的变量赋值
- (void)setClickBlock:(MyBlock)clickBlock
{
_clickBlock = clickBlock;
}
使用代码块的时候:
customView *view = [[customView alloc] initWithFrame:CGRectMake(10, 20, 100, 100)];
[self.view addSubview:view];

[view setClickBlock:^(UIButton *btn) {
NSLog(@"btn click");
btn.backgroundColor = [UIColor redColor];
}];同时使用block和代理时候,实例:



使用block时候注意事项:
声明一个block变量一般是

 @property (nonatomic, strong) MyBlock block;

block同时具有函数和实力便来给你的性质,使用起来方便,代码整洁。

使用注意事项:

1)在块内改变外部变量的值时候,在外部变量前加__block,否则该值在block块内部是只读的。

2)在引用某个实例变量或者所在控制器本身时候,在ARC下,要再前面加__weak如:__weak (typeof(self) weak self = self), 在mrc下用__block, 这样做是为了避免内存泄露和循环引用。

3)在使用block前需要对block指针做判空处理,如果是MRC的编译环境下,要先release掉block对象。

4)在MRC的编译环境下,block如果作为成员参数要copy一下将栈上的block拷贝到堆上(因为block默认是在栈上创建的,如果在定义block的作用于外部使用block那么需要使用copy将block放到堆上)//MRC下:_sucBlock = [callbackBlock copy]; 不copy block会在栈上被回收。

5)将block赋值为空,是解掉循环引用的重要方法。
6)还有一种改法,在block接口设计时,将可能需要的变量作为形参传到block中,从设计上解决循环引用的问题。
7)在多线程环境下(block中的weakSelf有可能被析构的情况下),需要先将self转为strong指针,避免在运行到某个关键步骤时self对象被析构。

第四、第五条合起来有个名词叫weak–strong dance,来自于2011 WWDC Session #322 (Objective-C Advancements in Depth)

以下代码来自AFNetworking,堪称使用weak–strong dance的经典。

__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};Review一下上面这段代码,里面玄机不少。

第一行:__weak __typeof(self)weakSelf = self;

如之前第四条所说,为防止callback内部对self强引用,weak一下。

其中用到了__typeof(self),这里涉及几个知识点:

a. __typeof、__typeof__、typeof的区别

恩~~他们没有区别,但是这牵扯一段往事,在早期C语言中没有typeof这个关键字,__typeof、__typeof__是在C语言的扩展关键字的时候出现的。

typeof是现代GNU C++的关键字,从Objective-C的根源说,他其实来自于C语言,所以AFNetworking使用了继承自C的关键字。

b.对于老的LLVM编译器上面这句话会编译报错,所以在很早的ARC使用者中流行__typeof(&*self)这种写法,原因如下

大致说法是老LLVM编译器会将__typeof转义为 XXX类名 *const __strong的__strong和前面的__weak关键字对指针的修饰又冲突了,所以加上&*对指针的修饰。

第三行:__strong __typeof(weakSelf)strongSelf = weakSelf;

按照之前第五条的说法给转回strong了,这里__typeof()里面写的是weakSelf,里面写self也没有问题,因为typeof是编译时确定变量类型,所以这里写self 不会被循环引用。

第四、五、六行,如果不转成strongSelf而使用weakSelf,后面几句话中,有可能在第四句执行之后self的对象可能被析构掉,然后后面的StausBlock没有执行,导致逻辑错误。

最后第五行,使用前对block判空。

写在最后,阅读好的开源库源码是提高个人水平的一个很好途径,看见不懂的地方去查去摸索会得到更多。

工具类网站:http://fuckingblocksyntax.com/

使用block5个注意事项:http://www.cnblogs.com/biosli/p/block_usage.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: