该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
第15章 块
之前已经介绍了OC平台的所有基础元素,现在介绍一些高级特性及程序中使用它们的方式;
本章介绍在程序中使用块的方式;
块语法和语义、块内存管理、怎样在程序中开发块和怎样使用现在API(如Foundation)中的块;
块提供了一种方式,使用这种方式可以创建一组语句(即代码块)并将这些语句赋予一个变量,随后就可以调用这个变量;
从这方面看,块与函数和方法类似,但除了是可执行代码外,块还含有与堆内存和栈内存绑定的变量;
块就是一个实现的闭包,一个允许访问其常规范围之外变量的函数;
此外,OC的块实际上就是一个对象,它是NSObject类的子类,拥有NSObject类的所有相关属性(如可以向块发送消息);
15.1 块的语法
块语法:
通过支持块的语法功能可以声明块类型的变量和常量;
块类型:
由返回值类型和参数类型列表构成;
使用脱字符(^)可以声明块类型的变量;
形如:
返回值类型 块的名称 参数类型
returnType (^blockName) (parameterType,parameterType,...)
块变量声明语法分析:
返回值类型指快被调用时返回值的类型;
快变量名带有一个脱字符前缀并且被封装在括号中;
参数类型列表被封装在括号中并且位于块名称的后边,参数类型以逗号分隔;
如无参数,应该将变量的参数类型列表设置为void(封装在括号中);
和OC方法声明类似,块变量声明中也可以含有参数名;
示例:
int (^testBlock1)(int);
void (^testBlock2)(int,int);
void (^testBlock3)(int a,int b);
int (^testBlock4)(void);
块类型作为块变量的类型,所以块变量也可以作为函数和方法的参数;
这种情况下,通常需要通过创建类型定义(typedef)为块类型提供别名,从而起到简化块类型变量名称的作用;
示例:
typedef int (^AdderBlock)(int);
@interface HQCapture15ViewController ()
-(void)testAdderBlock:(AdderBlock)block;
@end
如上所述,typedef语句用于创建了一个名为AdderBlock的块类型;之后,该块类型被用作方法定义中的参数类型;
块常量表达式:
使用块常量表达式可以定义对块的引用;
该表达式以脱字节开头,后跟返回值类型、参数列表和封装在花括号中的语句集合(块的主体);
形如:
^返回值类型 参数
^returnType (参数类型 参数名称,...){
块代码
//块语句
}
语法分析:
开头的^表示这是一个块常量表达式;
块参数(传递给函数主体)被封装在括号中,每个块参数都是由一个参数类型和一个参数名称表示,块参数之间以逗号隔开;
快代码封装在花括号中,由块被调用时执行的语句构成;
示例:
^int (int para){
return para + 1;
};
特殊语法:
1)没有设置返回值类型的块常量表达式:
由于编译器可以从块主体中推断出返回值类型,所以也可以不在块定义中为这个块设置返回值类型;
如下:
^(int para){
return para + 1;
};
2)不带参数的块常量定义:
^{
NSLog(@"Hello");
}
可以将块常量赋予相应的块变量定义:
示例:
int (^testBlock5)(int);
testBlock5 = ^(int para){
return para + 1;
};
//或者直接将块声明和定义组合到一起(要求:参数类型和返回值类型相符合)
int (^testBlock6)(int) = ^(int para){
return para + 1;
};
上述介绍了块变量和块常量表达式的内容,这里顺便说明下两者的区别,使用是还需注意:
1)脱字节:
块变量声明:标示开始,和变量名之前,并一起被封装在圆括号中;
块常量表达式:标示开始;
2)名称:
块变量声明:必选项;
块常量表达式:无名称;
3)返回值类型:
块变量声明:必选项,没有返回值需声明为void;
块常量表达式:可选项,依靠编译器从块主体推断;块主体的多个return语句返回的数据类型必须相同;
4)参数:
块变量声明:必选项,没有需声明为void;
块常量表达式:可选项;(在无参数列表的条件下)
当声明了块类型变量并将符合其声明的块常量赋予它后,就可以直接使用调用操作符调用该变量;
还可以使用一条语句定义并调用块表达式;
因为块表达式是匿名的,因此调用操作符会接收带有符合块表达式的参数列表的匿名函数;
示例:
int para7 = 9;
int (^testBlock7)(int) = ^int(int para){
return para + 1;
};
testBlock7(para7);
^(NSString * user){
NSLog(@"%@",user);
}(@"flower");
还可以像使用匿名函数一样,以内嵌的方式定义块表达式,从而将其用作函数或方法的参数;
这种情况下,最好仅对函数或方法使用一个块常量参数,并且应将块常量设置为参数列表中的最后一个参数(在有其他参数的情况下);
块语法我们就介绍完了,这部分内容是立即块用法的基础,需牢记;
15.2 块就是闭包
块就是一个实现的闭包;
就是一个允许访问在其常规范围外部声明的局部变量的函数(重点理解);
先来理解一些概念;
变量的可见性/范围:
指变量在程序的哪个(些)部分中可以被访问;
C语言的局部变量拥有局部范围;
与C语言的函数相比,块参数通过以下特性提高了变量的可见性:
1)对词汇范围的支持:
这是从封闭范围引用局部变量和参数的特性;
2)__block变量:
__block关键字可以应用于块外部但仍处于统一词汇范围的变量;
通过该特性可以修改块内部的变量;
3)访问实例变量:
在对象的方法实现代码中定义的块可以访问该对象中的实例变量;
4)导入参数:
通过头文件导入的常量变量在块常量表达式中是可见的;
15.2.1 词汇范围
块的一大特性就是支持词汇范围;(C语言函数是无法访问在其定义外部声明的局部变量的)
块支持词汇范围,因此块常量表达式可以访问在统一词汇范围内声明的变量;
能够定义变量的地方就可以定义块;(C语言的函数可不是随便定义的)
示例:
(Code C15BlockBoject)
#import <Foundation/Foundation.h>
@class C15BlockItemObject;
@interface C15BlockBoject : NSObject
@property (nonatomic , strong) C15BlockItemObject * blockObj;
@end
(Code C15BlockItemObject)
#import <Foundation/Foundation.h>
@interface C15BlockItemObject : NSObject
@property (nonatomic , strong) NSString * name;
@end
NSString * testStr1 = @"hello";
C15BlockBoject * bObj1 = [C15BlockBoject new];
bObj1.blockObj = [C15BlockItemObject new];
bObj1.blockObj.name = @"haha";
^{
bObj1.blockObj.name = @"hahaaa";
NSLog(@"%@",testStr1);
}();
NSLog(@"%@",bObj1.blockObj.name);
log:
2017-12-27 18:10:14.500474+0800 精通Objective-C[39850:10190403] hello
2017-12-27 18:10:14.500601+0800 精通Objective-C[39850:10190403] hahaaa
通过词汇范围,块常量表达式可以将局部变量作为常量值(对于原始数据类型)或引用变量(对于对象)访问;
值得注意的是:
上述代码如果想修改testStr1的值是办不到的;
因为,在块常量表达式中通过词汇范围访问的原始数据类型变量是无法修改的;(注意区分上述示例中打印的两个变量的情况)
这里尝试修改bObj1和testStr1都是不可以的,但是可以修改bObj1的对象属性;
我们接下来会解决这个问题;
因为局部范围是可以嵌套的,所以块可以捕捉多重(嵌套)范围内的局部变量;
15.2.2 可修改的__block变量
默认情况下,在块常量表达式中通过词汇范围访问的局部变量不能修改;(重点理解)
__block:
使用存储内容修改符__block可以将这些变量切换为读写模式(使得他们可以被修改);
除了C中的变长数组(不是由常数表达式表示长度的数组)和含有变长数组的C语言结构之外,可以对任何OC支持的数据类型使用__block修改符;
__block不能与局部变量存储修改符auto、register和static组合使用;
当引用变量的块被复制到堆存储区域时,使用__block修改符的变量也会被复制到堆存储区域;
(不太懂这句话,作为引出块内存管理的话题,我们下一节介绍下该内容)