在开始之前,先贴一张图片,表明为什么需要将Block,代理,通知和KVO放在一起。
由上图可知,Block,代理,通知和KVO都能实现数据传递的功能,可以根据不同的情景选择合适的方式
Block
声明
// 使用typedef为block变量定义别名
// 解释:将一个返回值为double,参数为int的block取一个Rename的别名
typedef double (^Rename)(int);
// Block作为属性的写法
@property (nonatomic,copy) int (^sumOfTwoNumber)(int,int);
// 使用typedef后的block声明变量
@property (nonatomic,copy) Rename divideByTwo;
赋值
// 直接为block赋值
self.sumOfTwoNumber = ^(int a,int b){
return a + b;
};
// 调用block
NSLog(@"%zd",self.sumOfTwoNumber(10,10));
参数
/**
* block作为方法参数的的写法
* @param before 只是为了说明如果不只有一个block参数,那该怎么写
* @param aBlock block参数
*
* @return block返回的整数值
*/
- (int) blockParam:(NSString *)before Block:(int (^)(int,int)) aBlock {
// 模拟数据的处理
if (arc4random_uniform((unsigned int)before.length) % 2 == 0) {
// 执行block
return aBlock(2,(int)before.length);
}
return aBlock(1,(int)before.length);
}
/// 不使用block实现相同功能
- (int) nonBlockParam:(NSString *)before {
// 模拟数据的处理
if (arc4random_uniform((unsigned int)before.length) % 2 == 0) {
return 2 + (int)before.length;
}
return 1 + (int)before.length;
}
返回值
NSLog(@"%f",self.divideByTwo(5));
// MARK: - 懒加载block
- (Rename)divideByTwo {
if (_divideByTwo == nil) {
_divideByTwo = ^(int param){
return param / 2.0;
};
}
return _divideByTwo;
}
价值(个人观点)
block是一段预先准备的代码块,它和函数非常的类似,
// 调用方法来传递block,并返回
int result = [self blockParam:@"Jus for test" Block:self.sumOfTwoNumber];
- (int) blockParam:(NSString *)before Block:(int (^)(int,int)) aBlock {
使用了代码块作为参数,真正执行的时是根据预先定义的
return a + b
来完成的
接收者是预先定义的sumOfTwoNumber
发送者是- (int) blockParam:(NSString *)before Block:(int (^)(int,int)) aBlock
,接收者做为参数传递而发送者又回调了接收者,所以实现了数据的双向传递
代码块的好处是什么呢? 如果只从上面的- (int) nonBlockParam:(NSString *)before
来进行对比,代码块似乎没有什么用,但是我们可以换个角度去思考,如果nonBlockParam:
是系统或第三方框架提供的方法,我们一般修改它的代码,现在要求在处理完之后,不是要a + b,而是a - b,那就稍微不太妙了,当然我们也可以将需要改变的代码抽取出来,然后写个方法,那也可以实现需求,但是两种方式的实现,block感觉更漂亮一些。
更多
关于block还有很多的内容,内存相关的,还有block引起的循环引用….
代理
代理在iOS中是处处可见的,很多视图都提供了代理属性,比如UITableView,UICollectionView,代理的是通过@protocol(协议)这个语言特性来体现的。
被代理方
- 创建一个协议,一般是类名+delegate,声明代理方法
- 定义代理属性一般就叫delegate (一般是id<协议>) 即遵守协议的任意NSObject对象
- 在适当的时候调用代理属性的代理方法
代理方
- 遵守代理协议
- 成为被代理方的代理对象 (被代理对象.delegate = self)
- 实现代理方法
下面通过代码来实现一个代理的例子
// 被代理对象中:
// 1.创建协议
@protocol DelegateViewControllerDelegate <NSObject>
@optional
- (void) delegateViewControllerDidChange:(UIViewController *) delegateVC;
@end
// 2.代理属性
@property (nonatomic,weak) id<DelegateViewControllerDelegate> delegate;
- (IBAction)changeImage {
if ([self.delegate respondsToSelector:@selector(delegateViewControllerDidChange:)]) {
// 3.在合适的时机调用
[self.delegate delegateViewControllerDidChange:self];
}
}
// 在代理对象中
// 1.遵守代理协议
@interface ViewController () <DelegateViewControllerDelegate>
/// storyboard方式跳转前会调用的方法
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
// 如果是从代理对象跳到被代理对象
if ([segue.identifier isEqualToString:@"delegate2ed"]) {
// 取出目标控制器
DelegateViewController *delegateVC = segue.destinationViewController;
// 2.当前控制器成为代理对象
delegateVC.delegate = self;
}
}
/// 实现代理方法
- (void)delegateViewControllerDidChange:(UIViewController *)delegateVC{
self.imgView.image = [UIImage imageNamed:@"02"];
}
// 实现dealloc方法,看是否有循环引用
- (void)dealloc {
NSLog(@"abc");
}
分析:从运行的结果看,并没有循环引用,而要实现代理要做的步骤非常清晰,代理是一对一的,消息不是直接响应的,然后发送者知道接收者是谁,比如在这里,发送者有delegate属性delegateVC.delegate = self;
后,就知道谁是接收者了。
通知
代理是一对一的,而通知是一对多,代理或block都可以双向通讯,但是通知只能是单向的,注册通知的对象就是接收者,iOS实现通知主要分两步:
发布通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"ChangeImageNotification" object:self];
注册通知
// 注册通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeImageNoti:) name:@"ChangeImageNotification" object:nil];
两者都是依靠通知中心这个单例 对象
下面就来用代码实现一个简单的通知
- 自定义一个UIImageView的子类
- 设置它的touchBegin事件,发布通知
- 然后在ViewController的viewDidLoad中设置初始的tag,让UIImageView可以与用户交互,注册通知
- 创建接收到通知的响应事件
相关代码:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.tag = self.tag == 1 ? -1 : 1;
// 创建一个异步任务去发送通知,测试通知是同步的还是异步的
// 点击图片的时候发布一个通知并把自己做为参数进行传递
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"----%@",[NSThread currentThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:@"ChangeImageNotification" object:self];
});
}
- (void)viewDidLoad {
[super viewDidLoad];
// 让imageView可以与用户交互
self.imagView.userInteractionEnabled = YES;
// 给imageView设置一个tag值
self.imagView.tag = 1;
// 注册通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeImageNoti:) name:@"ChangeImageNotification" object:nil];
}
// 接收到指定通知的响应方法
- (void) changeImageNoti:(NSNotification *) noti {
NSLog(@"当前线程:%@",[NSThread currentThread]);
// Just for log
NSLog(@"%@",noti);
if (self.imagView.tag == -1) {
self.imagView.image = [UIImage imageNamed:@"02"];
} else {
self.imagView.image = [UIImage imageNamed:@"01"];
}
}
// 切记通知需要被取消通知 不然有可能会有野指针错误
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// 记得:要在storyboard中绑定对应的视图
补充一点:通知是同步的
KVO
KVO的全称是Key-Value Observing,提供了一种机制,为一个对象的属性添加一个观察者,当该属性发生变化时,这个观察者就会得到消息。
以下是KVO的一个例子
- (void)viewDidLoad {
[super viewDidLoad];
// 添加KVO通知 观察frame的新值
[self.imgView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.imgView.frame = CGRectOffset(self.imgView.frame, 0, -40);
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"%@",change);
}
发现isa指针指向的类不同了,查阅有关资料,得知KVO实现过程中,会创建一个当前观察对象类的子类,然后在其中调用observeValueForPath:ofObject:change:context:方法