笔者现在自学iOS开发中,想把部分学习的历程写到blog上分享,现在在看《iOS7 Programming Cookbook》,虽然都说CookBook没必要全看,但是还是想挑其中的一些章节重点学习一下,笔者语文老师死得早,写blog经验不多,如果有看的同学发现语句不通,表述不清还请见谅。
iOS7 Programming Cookbook第七章篇幅过长,所以按照内容相关度和篇幅切分成四部分来翻译,这是其中的第一部分Block部分。
7.1Constructing Block Objects(构建Block对象)
问题:在Objective-C中构建Block并且使用
解决方案:你需要理解block和经典的C函数之间的语法差别,区别将在下面的讨论部分详述。
Block对象可以有内联和独立两种形式。我们从后一种开始学习。假设你有一个Objective-C方法,求两个NSInteger的差值,形式如下:
- (NSInteger) subtract:(NSInteger)paramValue from:(NSInteger)paramFrom{
return paramFrom - paramValue;
}
现在,让我们把这段代码转化为具有相同功能的纯C函数。提供同样功能的纯C函数会使我们离学习block语法更进一步:
NSInteger subtract(NSInteger paramValue, NSInteger paramFrom){
return paramFrom - paramValue;
}
你可以看到,C函数和Objective-C形式的语法有很大差别,我们再来看具有相同功能的block对象的代码:
NSInteger (^subtract)(NSInteger, NSInteger)= ^(NSInteger paramValue,NSInteger paramFrom){
return paramFrom - paramValue;
};
在我们深入语法细节之前,先来看几个例子,假设我们有一个C函数,将一个NSUInteger类型变量作为参数,返回对应的NSString类型的字符串:
NSString* intToString (NSUInteger paramInteger){
return [NSString stringWithFormat:@"%lu", (unsigned long)paramInteger];
}
有同样功能的block对象代码如下:
NSString* (^intToString)(NSUInteger) = ^(NSUInteger paramInteger){
NSString *result = [NSString stringWithFormat:@"%lu",
(unsigned long)paramInteger];
return result;
};
最简单的独立block对象是没有任何参数,没有返回值的形式:
void (^simpleBlock)(void) = ^{
/* Implement the block object here */
};
使用block对象的方法就和使用C函数是一样的,下面是一个例子:
NSString* (^intToString)(NSUInteger) = ^(NSUInteger paramInteger){
NSString *result = [NSString stringWithFormat:@"%lu",
(unsigned long)paramInteger];
return result;
};
- (void) callIntToString{
NSString *string = intToString(10);
NSLog(@"string = %@", string);
}
CallIntToString这个Objective-C方法通过传递10这个值作为参数,并将返回值作为局部变量的形式调用intToString这个block对象。
现在我们已经知道了怎样以独立代码段的形式写Block了,让我们来看一看如何将block作为方法参数进行传值。我们需要稍微抽象一点理解下面这个例子。
假设我们有一个Objective-C方法。这个方法接收一个integer作为参数,在方法内进行一些转化,这个转化的进行依赖于程序内其他部分的运行情况。我们知道我们会将integer作为输入,string作为输出,但是我们要把转化的具体过程留给每次运行都会发生变化的block对象,让它来决定。因此,这个方法会同时接收奖杯转化的integer和执行转化的block作为参数。
我们使用之前的intToString作为block。现在我们需要一个将接收一个unsigned integer参数和一个block实体参数的Objective-C方法。为了方便接收intToString作为block参数,我们使用typedef告诉编译器接收什么样的block作为参数:
typedef NSString* (^IntToStringConverter)(NSUInteger paramInteger);
上面这段代码里,typedef告诉编译器将接收一个NSUInteger参数,并返回一个NSString作为结果的block对象表示为一个名为 IntToStringConverter的标识符。现在让我们继续编写这个接收integer和一个IntToStringConverter类型的block对象作为参数的Objective-C方法:
- (NSString *) convertIntToString:(NSUInteger)paramInteger usingBlockObject:(IntToStringConverter)paramBlockObject{
return paramBlockObject(paramInteger);
}
我们现在来调用convertIntToString这个方法:
- (void) doTheConversion{
NSString *result = [self convertIntToString:123
usingBlockObject:intToString];
NSLog(@"Result=%@",result);
}
现在我们已经了解了独立block对象,接着让我们来看看内联block对象。在我们刚刚看过的doTheConversion方法,我们将intToString作为参数传入convertIntToString:UsingBlockObject:方法,那么如果我们还没有一个准备好的block实体作为参数呢?block对象是第一级函数,可以再运行时构建。让我们再来看看doTheConversion的另一种实现方法:
- (void) doTheConversion{
IntToStringConverter inlineConverter = ^(NSUInteger paramInteger){
NSString *result = [NSString stringWithFormat:@"%lu",
(unsigned long)paramInteger];
return result;
};
NSString *result = [self convertIntToString:123
usingBlockObject:inlineConverter];
NSLog(@"result = %@", result);
}
除了构建内联block外,我们还可以在传值时构建block:
- (void) doTheConversion{
NSString *result =
[self convertIntToString:123
usingBlockObject:^NSString *(NSUInteger paramInteger) {
NSString *result = [NSString stringWithFormat:@"%lu",(unsigned long)paramInteger];
return result;
}];
NSLog(@"result = %@", result);
}
7.2 Accessing Variables in Block Objects(在Block中访问变量)
问题:你想要理解在方法和Block中访问变量的区别
解决方案:
下面是在Block对象里面使用变量时需要记住的要点:
- block内的局部变量工作方式和方法中一模一样。
- 对于内联Block来说,局部变量除了在Block中定义的变量外,还包括在方法中定义,但是在Block中实现的变量。
- 你不能在独立Block中访问self,除非将self作为参数进行传值。
- 只有当self在block创建的语法作用域中已经被定义了,那么就可以在内联block中访问self了。
- 对于内联Block来说,在Block中实现的变量,可以同时执行读操作和写操作。
- 对于内联Block来说,在方法中实现的变量,只能在Block内执行读操作。如果用__block来修饰的话,则也可以执行写操作。
- 假设你有一个NSObject类型的对象,在这个对象的实现文件内,你要和GCD协同使用Block,那么在这个Block实现中,你对该NSObject的属性可以进行读写操作。
- 在独立block中只能通过setter和getter来访问你的NSObject声明的属性,不能使用self.的形式。
void (^independentBlockObject)(void) = ^(void){
NSInteger localInteger = 10;
NSLog(@"local integer = %ld", (long)localInteger);
localInteger = 20;
NSLog(@"local integer = %ld", (long)localInteger);
};
输出结果如下:
local integer = 10
local integer = 20
- (void) simpleMethod{
NSUInteger outsideVariable = 10;
NSMutableArray *array = [[NSMutableArray alloc]
initWithObjects:@"obj1",
@"obj2", nil];
[array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSUInteger insideVariable = 20;
NSLog(@"Outside variable = %lu", (unsigned long)outsideVariable);
NSLog(@"Inside variable = %lu", (unsigned long)insideVariable);
/* Return value for our block object */
return NSOrderedSame;
}];
}
block实体可以对自己内部的变量进行读写操作,但是默认对于外部的变量只能进行读操作。为了允许block可以对外部变量执行写操作,我们必须在变量前加上__block存储类型的前缀:
- (void) simpleMethod{
__block NSUInteger outsideVariable = 10;
NSMutableArray *array = [[NSMutableArray alloc]
initWithObjects:@"obj1",
@"obj2", nil];
[array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSUInteger insideVariable = 20;
outsideVariable = 30;
NSLog(@"Outside variable = %lu", (unsigned long)outsideVariable);
NSLog(@"Inside variable = %lu", (unsigned long)insideVariable);
/* Return value for our block object */
return NSOrderedSame;
}];
}
只要self在内联block的词法作用域内被定义了,那么在内联block对象中就可以访问self。比如,下面这个例子中,block对象可以访问self,因为simpleMethod是一个Objective-C类的实例方法:
- (void) simpleMethod{
NSMutableArray *array = [[NSMutableArray alloc]
initWithObjects:@"obj1",
@"obj2", nil];
[array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSLog(@"self = %@", self);
return NSOrderedSame;
}];
}
void (^incorrectBlockObject)(void) = ^{
NSLog(@"self = %@", self); /* self is undefined here */
};
如果想要在独立Block中访问self,可以通过参数传递来访问,具体方法如下:
void (^correctBlockObject)(id) = ^(id self){
NSLog(@"self = %@", self);
};
- (void) callCorrectBlockObject{
correctBlockObject(self);
}
#import "AppDelegate.h"
@interface AppDelegate()
@property (nonatomic, copy) NSString *stringProperty;
@end
@implementation AppDelegate
- (void) simpleMethod{
NSMutableArray *array = [[NSMutableArray alloc]
initWithObjects:@"obj1",
@"obj2", nil];
[array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSLog(@"self = %@", self);
self.stringProperty = @"Block Objects";
NSLog(@"String property = %@", self.stringProperty);
/* Return value for our block object */
return NSOrderedSame;
}];
}
void (^correctBlockObject)(id) = ^(id self){ NSLog(@"self = %@", self);
/* This will work fine */
[self setStringProperty:@"Block Objects"];
/* This will work fine as well */
NSLog(@"self.stringProperty = %@",
[self stringProperty]);
};
typedef void (^BlockWithNoParams)(void);
- (void) scopeTest{
NSUInteger integerValue = 10;
BlockWithNoParams myBlock = ^{
NSLog(@"Integer value inside the block = %lu",
(unsigned long)integerValue);
integerValue = 20;
/* Call the block here after changing the
value of the integerValue variable */
myBlock();
NSLog(@"Integer value outside the block = %lu",
(unsigned long)integerValue);
}
我们声明了一个integer局部变量,并复制为10。然后我们实现block对象,在这个block被实现之后,我们改变这个局部变量的值,然后再读取这个变量,并打印出来。你可能会期待block打印的变量值为20,但是你可以看到输出如下:
Integer value inside the block = 10
Integer value outside the block = 20
- (void) scopeTest{
__block NSUInteger integerValue = 10;
BlockWithNoParams myBlock = ^{
NSLog(@"Integer value inside the block = %lu", (unsigned long)integerValue);
};
integerValue = 20;
/* Call the block here after changing the
value of the integerValue variable */
myBlock();
NSLog(@"Integer value outside the block = %lu",
(unsigned long)integerValue);
}
现在我们可以得到如下输出:
Integer value inside the block = 20
Integer value outside the block = 20
void (^simpleBlock)(NSString *) = ^(NSString *paramString){
/* Implement the block object here and use theparamString parameter */
}
- (void) callSimpleBlock{
simpleBlock(@"O'Reilly");
}
NSString *(^trimString)(NSString *) = ^(NSString *inputString){
NSString *result = [inputString stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceCharacterSet]];
return result;
};
NSString *(^trimWithOtherBlock)(NSString *) = ^(NSString *inputString){
return trimString(inputString);
};
- (void) callTrimBlock{
NSString *trimmedString = trimWithOtherBlock(@" O'Reilly ");
NSLog(@"Trimmed string = %@", trimmedString);
}
[self callTrimBlock];