iOS7 Programming Cookbook-Chapter 7:Concurrency(Block部分)

笔者现在自学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.的形式。
讨论:
首先让我们来看看如何在block中使用局部变量的。一种是内联block,另一种是独立block:
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
然后我们再来看内联block:
- (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;
    }];
}
你不能在独立Block中直接访问self,下面的访问就会产生编译错误:
void (^incorrectBlockObject)(void) = ^{
NSLog(@"self = %@", self); /* self is undefined here */
};
如果想要在独立Block中访问self,可以通过参数传递来访问,具体方法如下:
void (^correctBlockObject)(id) = ^(id self){
    NSLog(@"self = %@", self);
};
- (void) callCorrectBlockObject{
    correctBlockObject(self);
}
让我们再来看看声明的属性,以及如何在block中访问他们。在内联Block中你可以使用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;
    }];
}
但是在独立Block中,你无法使用self.的方式来对属性进行读写,在这个情境下,你应该使用setter和getter方法:
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]);
};
使用内联Block时,你需要记住一个重要的法则:内联block对象采用copy的方式复制他们语法作用域内的变量。如果你不理解这是什么意思,让我们来看一个例子

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
在block中只是保持了在其实现的时候对于integerValue这个变量的只读复制。你可能会思考为什么block对象捕捉了一个局部变量的只读值。答案是这样简单。除非在局部变量前加上__block的前缀,语法作用域里的局部变量对于block内都是制度变量。因此,为了改变该行为,我们需要改变scopeTest的实现方法,通过加上__block前缀:
- (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
这部分应该已经给出了足够的关于在block中使用变量的信息。建议可以写一些block,并尝试在其中使用变量,以更好地了解block中变量的使用。

7.3 Invoking Block Objects(使用Block对象)
问题:你已经了解了如何构建Block,现在你希望执行Block以得到结果
解决方法:与执行C函数的方法一模一样。
讨论:
如果你有一个独立block对象,你可以就像调用C方法一样调用它:
void (^simpleBlock)(NSString *) = ^(NSString *paramString){
    /* Implement the block object here and use theparamString parameter */
}
- (void) callSimpleBlock{
    simpleBlock(@"O'Reilly");
}
如果你想在另一个独立block对象中调用另一个独立block对象,指令和在block中调用一个C方法是一样的:
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);
}
接着调用Objective-C方法callTrimBlock:
[self callTrimBlock];
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值