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

iOS 笔记七:block

2015-09-09 08:46 411 查看
块(block)

1.什么是块?

块是一块代码,如一系列在{}中的表达式。

通常以"in-line"方式被包含在调用块的方法中。

以下是调用一个需要一个block作为参数的方法的例子:

[aDictionary enumerateKeysAndObjectsUsingblock:^(id key, id value, BOOL *stop) {
 NSLog(@"value for key %@ is %@", key, value);
 if ([@"ENOUGH" isEqualToString:key]) {
  *stop = YES;
 }
}];
这跟Java中的匿名类有点类似。

block以字符^开始,然后是返回类型(可选)、参数(可选)、{, code }.

可以在block中使用在block前定义的本地变量(local variable).

double stopValue = 53.5;
[aDictionary enumerateKeysAndObjectsUsingblock:^(id key, id value, BOOL *stop) {
 NSLog(@"value for key %@ is %@", key, value);
 if ([@"ENOUGH" isEqualToString:key] || ([value doubleValue] == stopValue)) {
  *stop = YES;
 }
}];
但是它们是只读的!

BOOL stopedEarly = NO;
double stopValue = 53.5;
[aDictionary enumerateKeysAndObjectsUsingblock:^(id key, id value, BOOL *stop) {
 NSLog(@"value for key %@ is %@", key, value);
 if ([@"ENOUGH" isEqualToString:key] || ([value doubleValue] == stopValue)) {
  *stop = YES;
  stoppedEarly = YES; // ILLEGAL! 非法
 }
}];
除非你以__block标识本地变量:

__block BOOL stopedEarly = NO;
double stopValue = 53.5;
[aDictionary enumerateKeysAndObjectsUsingblock:^(id key, id value, BOOL *stop) {
 NSLog(@"value for key %@ is %@", key, value);
 if ([@"ENOUGH" isEqualToString:key] || ([value doubleValue] == stopValue)) {
  *stop = YES;
  stoppedEarly = YES; // this is legal now
 }
}];
if (stoppedEarly) NSLog(@"I stopped logging dictionary value early!");
本地变量也可以是对象实例,但是我们只能以setter、getter方式来访问。

NSString *stopKey = [@"Enough" uppercaseString];
__block BOOL stopedEarly = NO;
double stopValue = 53.5;
[aDictionary enumerateKeysAndObjectsUsingblock:^(id key, id value, BOOL *stop) {
 NSLog(@"value for key %@ is %@", key, value);
 if ([stopKey isEqualToString:key] || ([value doubleValue] == stopValue)) {
  *stop = YES;
  stoppedEarly = YES; // this is legal now
 }
}];
if (stoppedEarly) NSLog(@"I stopped logging dictionary value early!");
block中将自动持有stopKey的强引用(strong)指针,直到block被释放。

要让block正常执行,这是很明显的要求。

2.创建一个可以持有block的变量类型。

声明持有block的变量时,block有点像有特殊语法的对象。

一般情况下,我们使用typedef声明一个类型来存储一个block到变量中,如:

typedef double (^unary_operation_t)(double op);
以上代码声明了一个"unary_operation_t"的类型,该类型对应的变量可以存储block。

然后我们可以定义一个类型为unary_operation_t、名为square的变量,并给该变量赋值:

unary_operation_t square;
square = ^(double operand) { // square变量的值是一个block
 return operand * operand;
}
然后用以下方式来使用square变量:

double squareOfFive = square(5.0); // 这句代码执行后squareOfFile的值是25.0
并不需要强制使用typedef,比如以下代码也是创建square的合法方式:

double (^square)(double op) = ^(double op) {return op * op;};
我们可以用以下方式来使用unary_operation_t:

例如你可以使用一个block数组属性:

@property (nonatomic, strong) NSMutableDictionary *unaryOperations;
然后像这样实现一个方法:

typedef double (^unary_operation_t)(double op);
-(void)addUnaryOperation:(NSString *)op whichExecutesBlock:(unary_operation_t)opBlock {
 self.unaryOperations[op] = opBlock;
}
注意block某种程度上可以被当作对象(e.g. adding it to a dictionary).

然后,我们可以使用一个已经被添加到字典中的操作:

-(double)performOperation:(NSString *)operation onOperand:(double)operand
{
 unary_operation_t unaryOp = self.unaryOperations[operation];
 if(unaryOp) {
  double result = unaryOp(operand);
 }
 ...
}
我们并不总是需要typedef,当一个block是一个方法的参数并且需要马上使用的时候,

经常不需要typedef,以下是字典枚举方法的声明:

-(void)enumerateKeyAndObjectUsingBlock:(void (^)(id key, id obj, BOOL *stop))block;
以上方法定义的block没有名字,除了没有名字之外,语法其实跟使用typedef是一样的.

参数名"block"是参数的关键字。

如果使用typedef,以上参数block看起来应该像这样:

typedef void (^enumeratingBlock)(id key, id obj, BOOL *stop);
其中enumeratingBlock这部分在方法参数中并没有使用。

block还有一些缩略的定义方法,如果block没有参数,你可以不使用圆括号:

如以下block作为参数时

[UIView animateWithDuration:5.0 animations:^() {
 view.opacity = 0.5;
}];
可以写成这样:

[UIView animateWithDuration:5.0 animations:^ {
 view.opacity = 0.5;
}];
与此类似的还有返回类型,返回类型经常可以被推断出来,此时返回类型是可以省略的。

NSSet * mySet = ...;
NSSet * matches = [mySet objectsPassingTest:^(id obj, ...) {
 return [obj isKindOfClass:[UIView class]];
}];
return语句表面此block返回的是BOOL类型,所以在定义以上匿名block时没有使用返回类型。

3.为什么blocks可以表现得像对象(objects).

通过以上学习表面blocks可以存储在objects中(in property,arrays,dictionarys,etc.).

其实blocks表现像objects的唯一目的就是为了存储它们(block的唯一”方法“是copy).

如下:

@property (nonatomic, strong) NSMutableArray * myBlocks; // array for blocks
你可以做以下事情

[self.myBlocks addObject:^{
 [self doSomething];
}];
非常简洁有木有!

另外,你可以用以下方式触发(调用)这个数组中的block:

void (^doit)(void) = self.myBlocks[0];
doit();
但是这样是有危险的。

4.内存循环引用(交差引用).

我们之前说过所有在block中引用的对象都会保存在堆中,直到block被释放。

换句话说,block持有其中引用到的所有对象的strong指针。

在上面的例子中,self是block中引用的对象

[self .myBlocks addObject:^ {
 [self doSomething];
}];
所以这个block持有self的strong指针,但是请注意self同时也持有block的strong指针,

因为这个block在myBlocks数组中。

这是个严重的问题!!!

现在self和blcok都无法从堆中释放了,因为它们相互持有对方的强指针。

这个称为内存循环引用。

内存循环引用的解决方案:

我们回忆下,本地变量总是strong类型的,这是可以的,

因为当程序调用栈离开这个范围时,它们会消失,strong指针也会消失。

可以声明一个本地变量是weak类型的,以下是方法:

__weak MyClass *weakSelf = self; //虽然even是strong类型的,weakSelf是weak类型的。


现在如果我们在block中引用weakSelf,block将不会以strong方式引用到self:

[self.myBlocks addObject:^ {
 [weakSelf doSomething];
}
现在我们不再有内存循环引用了,只要外部什么地方持有了self的strong指针,

block的指针就是安全的;而只要self被释放了,block也会被释放掉。

5.在iOS中,我们什么时候会用block?

A.枚举,如上面提到的NSDictionary.

B.视图动画;

C.排序,使用block作为比较方法;

D.通知Notification,当某些事件发生时,执行block;

E.错误处理Error handlers,当错误发生时,执行block;

F.结束处理Completion handlers,完成某项任务后,执行block;

还有一个超级重要的使用方式:多线程

使用Grand Central Dispatch(GCD)API.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: