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

iOS block探究(一): 基础详解

2017-03-21 18:24 281 查看

你要知道的block都在这里

转载请注明出处 http://blog.csdn.net/u014205968/article/details/64463116

本文大纲

block基础语法

block基础使用

block常见问题

block进阶: 深入代码理解

block基础语法

block
作为C语言的扩展,正在OC中发挥着举足轻重的作用,我们经常使用
block块
作为回调函数,这样做可以大大简化编程方式,多线程的核心也是
block
,因此,学会使用
block
并深入理解
block
有助于我们写出更好的代码。

block基础知识

先来看一下定义
block
的基础语法

^ returnType (parameter1, parameter2, parameter3, ...)
{
//block code
}


block
的标志就是
^
,所有的
block
必须以
^
开头


returnType
表示返回值类型,如果没有返回值一般使用
void
表示


再来看一下定义
block变量
的基础语法

returnType (^blockName) (parameter1, parameter2, ...);


必须包含blockName并且以
^
开头,是
block
的标志


参数列表可以和声明函数一样,只写出形参类型不需写出形参名称

接下来看一个具体的栗子:

int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^printBlock)(void) = ^ void(void) {
NSLog(@"Hello World");
};
//调用block,与C语言调用函数一致
printBlock();
}
return 0;
}


上述代码展示了一个无参数、无返回值的
block
,定义
block变量
的时候不能省略返回值类型、
block
名称以及形参列表,如果没有参数则用
void
占位或者不写,这样就能够定义一个
block变量


定义
block
的时候如果返回值为
void
可以省略,如果没有形参可以使用
void
占位或者整个形参列表都省略不写,因此上述代码可以简化为如下:

int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^printBlock)() = ^ {
NSLog(@"Hello World");
};
//调用block,与C语言调用函数一致
printBlock();
}
return 0;
}


再来看看有参数列表有返回值的情况

int main(int argc, const char * argv[]) {
@autoreleasepool {
//定义一个有参数无返回值的block
void (^printBlock)(NSString *) = ^(NSString *content) {
NSLog(@"Block Say: %@", content);
};
printBlock(@"Jiaming Chen is a good guy.");

//定义一个有多个参数且有返回值的block
NSInteger (^addBlock)(NSInteger, NSInteger) = ^ NSInteger (NSInteger a1, NSInteger a2) {
return a1 + a2;
};
NSInteger a = addBlock(12, 13);
NSLog(@"%ld", a);
}
return 0;
}


上述代码定义了两个
block
,一个
block
有一个参数但是无返回值,因此可以省略返回值类型不写,一个
block
有多个参数和返回值,不能省略。

block捕获变量

直接看代码

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSUInteger age = 22;
void (^printBlock)() = ^ {
NSLog(@"My Age is %ld", age);
//age = 33;
};
//输出 Age is 22
NSLog(@"Age is %ld", age);
age = 100;
//输出 Age is 100
NSLog(@"Age is %ld", age);
//输出 My Age is 22
printBlock();
}
return 0;
}


从输出结果可以看出,执行
block
之前进行的变量值修改并没有影响到
block
块内的代码,这是由于在定义
block
块的时候编译器已经在编译期将外部变量值赋给了
block
内部变量(称为“值捕获”),在这时候进行了一次值拷贝,而不是在运行时赋值,因此外部变量的修改不会影响到内部
block
的输出。

如果捕获是一个指针类型的变量则外部的修改会影响到内部,就和函数传递形参是一样的道理,这个时候
block
内部或持有这个对象,并增加引用计数,在
block
结束释放后,也会释放持有的对象,减少引用计数,这里需要注意循环引用的问题,在后文中会讲解。

上述
block
块内注释了一段给
age
重新赋值的代码,因为在
block
内部不允许修改捕获的变量。

int main(int argc, const char * argv[]) {
@autoreleasepool {
//定义一个NSMutableString类型的变量
NSMutableString *content = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
void (^printBlock)() = ^ {
NSLog(@"%@", content);
};
//输出 Jiaming Chen
NSLog(@"%@", content);
[content appendString:@" is a good guy"];
//输出 Jiaming Chen is a good guy
printBlock();
}
return 0;
}


__block的使用

如果希望
block
捕获的变量在外部修改后也可以影响
block
内部,或是想在
block
内部修改捕获的变量,可以使用
__block
关键字定义变量。

int main(int argc, const char * argv[]) {
@autoreleasepool {
//使用__block关键字定义age
__block NSUInteger age = 22;
void (^printBlock)() = ^ {
NSLog(@"My Age is %ld", age);
age = 200;
};
//输出 Age is 22
NSLog(@"Age is %ld", age);
age = 100;
//输出 Age is 100
NSLog(@"Age is %ld", age);
//输出 My Age is 100
printBlock();
//输出 Age is 200
NSLog(@"Age is %ld", age);
}
return 0;
}


上述代码使用
__block
定义变量
age
,这样定义以后编译器会在
block
定义的时候捕获变量的引用而不是拷贝一个值(具体实现细节在iOS block探究(二): 深入理解中有详细介绍)这样,外部变量的修改就会影响到
block
内部。

block作为参数传递

在实际应用中很少有上述那样的用法,更多的是定义一个
block
块然后作为参数传递。

//使用typedef定义一个无返回值、有一个NSInteger类型的形参的block类型,该block名字为 CJMNumberOperationBlock
typedef void (^CJMNumberOperationBlock)(NSInteger);

//numberOperator函数,参数为一个numberArray数组和一个CJMNumberOperationBlock块类型
void numberOperator(NSArray *numberArray, CJMNumberOperationBlock operationBlock) {
NSUInteger count = [numberArray count];
for (NSUInteger i = 0; i < count; i++) {
operationBlock([numberArray[i] integerValue]);
}
}

int main(int argc, const char * argv[]) {
@autoreleasepool {
//定义一个数组
NSArray *numberArray = @[@1, @2, @3];
//直接调用numberOperator函数,实现一个匿名的block传入成为“内联块”(inline block),在swift中成为“尾随闭包”(Trailing closure)
//输出 pow(number, 2) = 1
//   pow(number, 2) = 4
//    pow(number, 2) = 9
numberOperator(numberArray, ^(NSInteger number) {
NSLog(@"pow(number, 2) = %ld", number * number);
});

//定义一个CJMNumberOperationBlock
CJMNumberOperationBlock operationBlock = ^(NSInteger number) {
NSLog(@"add(number, number) = %ld", number + number);
};
//将上述定义的CJMNumberOperationBlock参数传入
//输出 add(number, number) = 2
//   add(number, number) = 3
//    add(number, number) = 6
numberOperator(numberArray, operationBlock);
}
return 0;
}


使用
typedef
定义块变量类型的语法为

typdef returnType (^blockTypeName)(parameter1, parameter2,..);


定义了块变量类型就能够重复定义块变量,上述代码也是一种常见的使用
block
的方式。也可以作为回调函数,在代码逻辑处理完成之后执行
block
块,这也是常用做法。

在实际代码中,经常将一些处理封装在
block
中,或使用
delegate
方式进行处理,这样有利于代码解耦,逻辑更清晰,具体的栗子本文不再赘述,可以多看开源代码来学习。

block常见问题

使用block最常见的问题就是循环引用问题,循环引用也可能发生在
delegate
NSTimer
中,具体可以自行查阅。

前文讲过如果
block
内部访问了外部变量会进行值捕获,
block
同样是一个对象,也有引用计数,如过一个对象持有了一个
block
block
内部也捕获了这个对象,那么就会产生循环引用。

举个栗子:

typedef void (^CJMInfoOperationBlock)();

@interface Person : NSObject

@property (nonatomic, copy) NSString* cjmName;
@property (nonatomic, assign) NSUInteger cjmAge;
@property (nonatomic, copy) CJMInfoOperationBlock infoOperationBlock;

- (void)setFixBlock;

@end

@implementation Person

@synthesize cjmName = _cjmName;
@synthesize cjmAge = _cjmAge;
@synthesize infoOperationBlock = _infoOperationBlock;

- (void)setFixBlock {
self.infoOperationBlock = ^ {
NSLog(@"My name is %@ and my age is %ld", self.cjmName, self.cjmAge);
};
}

@end


首先声明,上述代码应该不会在实际中应用,太懒了不想用Xcode写代码,就没有用
UIKit
,只用了
Foundation
框架,
setFixBlock
函数就可以想象成在一个
UIViewController
类中手动传递
block
变量。

上述代码首先自定义了一个
block
变量类型,然后
Person
类持有了一个
block
变量,在函数
setFixBlock
中使用了
self
变量,这样就会捕获
self
self
也是一个对象,因此
block
也会持有一个
self
如果调用了
setFixBlock
函数就会造成循环引用。编译器也会检查出来并给出警告。

上述代码的修改方法如下:

- (void)setFixBlock {
__weak typeof(self) weakSelf = self;
self.infoOperationBlock = ^ {
__strong typeof(self) strongSelf = weakSelf;
NSLog(@"My name is %@ and my age is %ld", strongSelf.cjmName, strongSelf.cjmAge);
};
}


首先定义一个
__weak
修饰的
weakSelf
变量(在iOS @property探究(一): 基础详解一文中有详细介绍
weak
修饰符与此处含义一致),使用
__weak
修饰表示弱引用, 定义
weakSelf
不会增加
self
的引用计数,因此在
block
内部使用这个
weakSelf
就不会产生循环引用,因此在所有可能产生循环引用的地方都这么做,就能有效避免循环引用的产生。

那么为什么在
block
内部又使用
__strong
修饰符去定义了一个
strongSelf
变量呢?其实这个
strongSelf
定义或者不定义都无所谓,不会产生问题,因为在ARC环境下,
block
会被自动拷贝到堆,不存在栈堆了,这样
block
被传递到其他地方的时候也不会释放
self
对象。

block进阶: 深入代码理解

三种block类型

深入代码理解block

由于篇幅有限,如果对进阶内容有兴趣可以查看另一篇文章iOS block探究(二): 深入代码

备注

由于作者水平有限,难免出现纰漏,如有问题还请不吝赐教。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ios