- Block对象是一段代码,先给出一个Block对象的示例:
^{
NSLog("This is an instruction with in a block.");
}
- 看上去和C函数类型,都是在一个花括号内的一套指令,但是它没有函数名,相应的位置只有一个^符号,^表示这段代码是一个Block对象。
- 和函数一样,Block对象也可以有实参和返回值,再给出一个Block对象示例:
^(double dividend, double divisor){
double quotient = dividend / divisor;
return quotient;
}
- 这段代码中的Block对象有两个参数,类型都是double,返回一个double类型的值。
- Block对象可以被当做一个实参传递给可以接受block的方法,很多苹果的类型都有可以接收block为实参的方法。
代码实例
- 创建三个数组对象,一个用于保存最初的字符串;一个用于保存去除了元音的字符串;最后一个用于保存需要从字符串中移除的字符。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建两个数组对象,分别用于保存最初的字符串对象和去除元音字母后的版本
NSArray *originalString = @[@"SauerKraut",@"Raygun",@"Big Nerd Ranch",@"Mississippi"];
NSLog(@"original string: %@",originalString);
NSMutableArray *devowelizedStrings = [NSMutableArray array];
//创建数组对象,保存需要从字符串移除的字符
NSArray *vowels = @[@"a",@"e",@"i",@"o",@"u"];
}
return 0;
}
声明Block变量
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建两个数组对象,分别用于保存最初的字符串对象和去除元音字母后的版本
NSArray *oldStirngs = [NSArray arrayWithObjects:@"SauerKraut",@"Raygun",@"Big Nerd Ranch",@"Mississippi",nil];
NSLog(@"original string: %@",oldStirngs);
NSMutableArray *newStrings = [NSMutableArray array];
//创建数组对象,保存需要从字符串移除的字符
NSArray *vowels = [NSArray arrayWithObjects:@"a",@"e",@"i",@"o",@"u"];
//声明Block变量
void(^devowelizer)(id,NSInteger,BOOL *);
}
return 0;
}
- 代码解析。Block变量声明做一个详细的介绍。Block变量的名字(如devowelizer)是写在括号中,跟在^字符后面的。Block的声明需要包括Block的返回类型(void)以及它的参数类型(id,NSInteger,BOOL *),这点类似函数的声明。
编写Block对象
- 编写一个方法赋复制原始字符串,并将移除原始字符串的元音字母,然后将去除元音字母的字符串
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建两个数组对象,分别用于保存最初的字符串对象和去除元音字母后的版本
NSArray *oldStirngs = [NSArray arrayWithObjects:@"SauerKraut",@"Raygun",@"Big Nerd Ranch",@"Mississippi",nil];
NSLog(@"original string: %@",oldStirngs);
NSMutableArray *newStrings = [NSMutableArray array];
//创建数组对象,保存需要从字符串移除的字符
NSArray *vowels = [NSArray arrayWithObjects:@"a",@"e",@"i",@"o",@"u",nil];
//声明Block变量
void(^devowelizer)(id,NSInteger,BOOL *);
//将Block对象赋值给变量
devowelizer = ^(id string,NSInteger i, BOOL *stop){
NSMutableString *newString = [NSMutableString stringWithString:string];
//枚举数组中的字符串,将所有出现的元音字母替换成空字符串
for (NSString *s in vowels) {
NSRange fullRange = NSMakeRange(0, [newString length]);
[newString replaceOccurrencesOfString:s withString:@"" options:NSCaseInsensitiveSearch range:fullRange];
}
//[devowelizedStrings addObject:newString];
};//Block变量赋值结束
}
return 0;
}
- 此外,和其他变量一样,也可以将devowelizer的声明和赋值写在一起
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建两个数组对象,分别用于保存最初的字符串对象和去除元音字母后的版本
NSArray *oldStirngs = [NSArray arrayWithObjects:@"SauerKraut",@"Raygun",@"Big Nerd Ranch",@"Mississippi",nil];
NSLog(@"original string: %@",oldStirngs);
NSMutableArray *newStrings = [NSMutableArray array];
//创建数组对象,保存需要从字符串移除的字符
NSArray *vowels = [NSArray arrayWithObjects:@"a",@"e",@"i",@"o",@"u",nil];
//声明Block变量
void(^devowelizer)(id,NSInteger,BOOL *) = ^(id string, NSInteger i, BOOL *stop){
NSMutableString *newString = [NSMutableString stringWithString:string];
//枚举数组中的字符串,将所有出现的元音字母替换成空字符串
for (NSString *s in vowels) {
NSRange fullRange = NSMakeRange(0, [newString length]);
[newString replaceOccurrencesOfString:s withString:@"" options:NSCaseInsensitiveSearch range:fullRange];
}
[newStrings addObject:newString];
};
}
return 0;
}
传递Block对象
-
enumerateObjectsUsingBlock 要求传入的Block对象三个实参类型是固定的,第一个实参是对象指针,指向当前的对象,该指针的类型是id,所以无论数组包含的是什么类型的对象,都可以将地址赋值给指针。第二个实参的类型是NSInteger,其值是当前对象在数组中的索引。第三个实参是指向BOOL变量的指针,该变量的默认是NO。如果值为YES,那么数组对象会执行完当前的Blcok对象后终止枚举过程。
//枚举数组对象,针对每个数组中的对象,执行Block对象devowelizer
[oldStirngs enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"new string: %@",newStrings);
}];
- 修改代码,检查字符串是否包含字母y,如果有,则设置指针指向YES,终止枚举
//声明Block变量
void(^devowelizer)(id,NSInteger,BOOL*);
//将Block对象赋值给变量
devowelizer = ^(id string,NSInteger i, BOOL *stop){
NSRange yRange = [string rangeOfString:@"y" options:NSCaseInsensitiveSearch];
//是否包含字符y?
if (yRange.location != NSNotFound) {
//执行完当前的Block对象后终止枚举过程
*stop = YES;
//结束当然正在执行的Block对象
return;
}
NSMutableString *newString = [NSMutableString stringWithString:string];
//枚举数组中的字符串,将所有出现的元音字母替换成空字符串
for (NSString *s in vowels) {
NSRange fullRange = NSMakeRange(0, [newString length]);
[newString replaceOccurrencesOfString:s withString:@"" options:NSCaseInsensitiveSearch range:fullRange];
}
[newStrings addObject:newString];
typedef
- Block对象的语法可能比较复杂,通过typedef关键字,可以将某个Block对象类型定义为一个类型。
- 以下代码中的typedef语句看上去与Block变量声明很像,但是这里定义的是一个新的类型,而不是变量。
#import <Foundation/Foundation.h>
typedef void (^ArrayEnumerationBlock)(id,NSUInteger,BOOL *);
Block对象 vs 其他回调
- 两种回调机制:委托机制和通告机制,通过回调机制,程序能够在特定事件发生时调用指定的方法,虽然以上两种回调机制能够很好完成任务,但是也有一个缺点,即回调的设置代码和回调方法的具体实现无法写在同一段代码中。
[[NSNotificationCenter defaultCenter] addObserver:log selector:@selector(zoneChange:) name:NSSystemTimeZoneDidChangeNotification object:nil];
深入学习Block对象
返回值
- 这个Block对象有两个类型为double的实参,返回同一个double类型的值,要在变量中保存这个Block,需要声明一个double类型的变量,然后再在Block赋值给这个变量
^(double dividend, double divisor){
double quotient = dividend / divisor;
return quotient;
};
//声明divBlock变量
double (^divBlock)(double,double);
//将Block对象赋值给变量
divBlock = ^(double dividend, double divisor){
double quotient = dividend / divisor;
return quotient;
};
诺名Block对象
- 三种方法可以将整数传递给方法
- 将Block对象传递给方法的的办法和传递整数相同。分别三行代码来声明Block对象,然后赋值,最后使用。
//方法一:声明,赋值和使用完全分开
int i;
i = 5;
NSNumber *num = [NSNumber numberWithInt:i];
//方法二:声明,在一行中声明赋值使用
int i2 = 5;
NSNumber *num2 = [NSNumber numberWithInt:i2];
//方法三:跳过变量声明步骤
NSNumber *num3 = [NSNumber numberWithInt:5];
外部变量
- Block对象通常会使用外部创建的其他变量。这些外部创建的变量叫做外部变量。当执行Block对象时,为了确保其下的外部变量始终存在,相应的Block对象会捕获这些变量
- 对基本类型的变量,捕获意味着程序会拷贝变量的值,并用Block对象内存的局部变量保存。对指针类型的变量,Block对象会使用强引用,这意味凡是Block对象用到的对象,都会被保留。所以在相应的Block对象被释放前,这些对象一定不会被释放
在Block对象中使用self
- 如果需要写一个使用self的Block对象,就必须要多做几步工作来避免造成强引用循环。以下实例,BNREmployee实例创建一个Block对象,每次执行的时候就会打印这个BNREmployee实例
myBlock = ^ {
NSLog(@"Employee: %@",self);
};
-
BNREmployee有一个指向Block对象的指针,这个Block对象会捕获self,所以它有一个指回BNREmployee实例的指针。
- 为了打破这个强引用循环,可以先在Block对象外声明一个__weak指针;然后将这个指针指向Block对象使用的self;最后在Block对象中使用新的指针
//一个弱引用
__weak BNREmployee *weakSelf = self;
myBlock = ^ {
NSLog(@"Employee: %@",self);
};
- 现在这个Block对象BNREmployee实例是弱引用,强引用循环打破了。
- 然而,由于是弱引用,所以self指向的对象在Block执行的时候可能会被释放。
- 为了避免这种情况的发生,可以再Block对象中创建一个self的局部强引用
//一个弱引用
__weak BNREmployee *weakSelf = self;
myBlock = ^ {
//局部w强引用
BNREmployee *innerSelf = weakSelf;
NSLog(@"Employee: %@",self);
};
- 通过创建innerSelf强引用,就可以在Block和BNREmployee实例中再次创建一个强引用循环。但是,由于innerSelf引用是针对Block内部的,所以只有在Block执行的时候它才会执行,而Block结束之后就会自动消失。
在Block对象中无意使用self
- 如果直接在Block对象中使用实例变量,那么block会捕获self,而不是捕获实例变量。这是实例变量的一个鲜为人知的特点。例如,一下代码直接存取一个实例变量:
__weak BNREmployee *weakSelf = self;
myBlock = ^ {
//局部w强引用
BNREmployee *innerself = weakSelf;
NSLog(@"Employee: %@",self);
NSLog(@"Employee ID: %d",_employID);
};
//编辑器是这么解读代码
__weak BNREmployee *weakSelf = self;
myBlock = ^ {
//局部w强引用
BNREmployee *innerself = weakSelf;
NSLog(@"Employee: %@",self);
NSLog(@"Employee ID: %d", self->_employID);
};
-
->语法看上去是不是很熟悉?这个语法实际是用来后去堆上的成员结构的。从最底层来说,对象实际是结构。
- 由于编辑器将_employID看成self->_employID,self就被Block对象无意地捕获了。这样又会造成之前使用weakSelf和innerSelf避免的强引用循环。
__weak BNREmployee *weakSelf = self;
myBlock = ^ {
BNREmployee *innerself = weakSelf;
NSLog(@"Employee: %@",self);
NSLog(@"Employee ID: %d",innerSelf.employID);
};
- 现在没有直接地使用self了,就不会造成无意地强引用循环。
- 在这种情况下,重要的是理解编辑器是如何思考的,这样才能避免隐藏地强引用循环。然而,绝不要使用->语法来存取对象的实例变量,这样做非常危险,可能会超越这个Block对象的范围。
修改外部变量
- 在Block对象中,被捕获的变量是常数,程序无法修改变量所保存的值。
- 如果需要在Block对象内修改某个外部变量,则可以在声明相应的外部变量时,在前面加上__block关键字。
- 列如以下代码在Block对象外部变量counter的值增加1.
__block int counter = 0;
void(^conterBlock)() = ^{
counter++;
};
//counter增加1,数值为1
counterBlock();
//counter增加1,数值为2
counterBlock();
网友评论