iOS中消息传递方式
在iOS中有很多种消息传递方式,这里先简单介绍一下各种消息传递方式。
1、通知:在iOS中由通知中心进行消息接收和消息广播,是一种一对多的消息传递方式。
NSNotificationCenter消息通知机制,向NSNotificationCenter
中addObserver
后,并没有对这个对象进行引用计数加1操作,所以它只是保存了地址。为了验证这个操作,我们来做下代码的测试。
[[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"test" object:nil]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [[NSNotificationCenter defaultCenter] removeObserver:self name:@"test" object:nil]; }
多线程通知:
NSNotificationCenter
消息的接受线程是基于发送消息的线程的。也就是同步的,因此,有时候,你发送的消息可能不在主线程,而大家都知道操作UI必须在主线程,不然会出现不响应的情况。所以,在你收到消息通知的时候,注意选择你要执行的线程。
代理和block的选择
-
多个消息传递,应该使用
delegate
。
在有多个消息传递时,用delegate
实现更合适,看起来也更清晰。block
就不太好了,这个时候block
反而不便于维护,而且看起来非常臃肿,很别扭。
例如UIKit
的UITableView
中有很多代理如果都换成block
实现,我们脑海里想一下这个场景,这里就不用代码写例子了.....那简直看起来不能忍受。 -
一个委托对象的代理属性只能有一个代理对象,如果想要委托对象回调多个代理对象应该用
block
。(这里主要是针对于对象内部属性不会对block
进行引用的情况下,否则再调用同一个方法也会造成重新赋值问题)
上面图中代理1可以被设置,代理2和代理3设置的时候被划了叉,是因为这个步骤是错误的操作。我们上面说过,delegate
只是一个保存某个代理对象的地址,如果设置多个代理相当于重新赋值,只有最后一个设置的代理才会被真正赋值。
block
不应用于声明在.h
文件中属性回调,主要是应用于方法回调。例如现在有如下情况需要回调,用block
可以,但是用设置delegate
方式就不行了:“假设有block
回调对象downloadImage
类,同一个downloadImage
对象带有block
回调的方法,在多个类或多个地方进行回调,这种情况就更适合用block
的方式了。“每调用一次方法就可以在block
回调代码块中,进行自己的操作,比代理的方式更佳强大。
单例对象最好不要用delegate,
单例对象由于始终都只是同一个对象,如果使用delegate
,就会造成我们上面说的delegate
属性被重新赋值的问题,最终只能有一个对象可以正常响应代理方法。这种情况我们可以使用block
的方式,在主线程的多个对象中使用block
都是没问题的,下面我们将用一个循环暴力测试一下block
到底有没有问题。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 10;
for (int i = 0; i < 100; i++) {
[queue addOperationWithBlock:^{
[[LoginViewController shareInstance] userLoginWithSuccess:^(NSString *username) {
NSLog(@"TestTableViewController : %d", i);
}];
}];
}
上面用NSOperationQueue
创建了一个新的队列,并且将最大并发数设置为10,然后创建一个100次的循环。我们在多线程情况下测试单例在block的情况下能否正常使用,答案是可以的。
但是我们还是需要注意一点,在多线程情况下因为是单例对象,我们对block
中必要的地方加锁,防止资源抢夺的问题发生。
代理是可选的,而block在方法调用的时候只能通过将某个参数传递一个nil
进去,只不过这并不是什么大问题,没有代码洁癖的可以忽略。
[self downloadTaskWithResumeData:resumeData sessionManager:manager savePath:savePath progressBlock:nil successBlock:successBlock failureBlock:failureBlock];
从设计模式的角度来说,代理更佳面向过程,而block
更佳面向结果。例如我们使用NSXMLParserDelegate
代理进行XML解析,NSXMLParserDelegate
中有很多代理方法,NSXMLParser
会不间断调用这些方法将一些转换的参数传递出来,这就是NSXMLParser
解析流程,这些通过代理来展现比较合适。而例如一个网络请求回来,就通过success
、failure
代码块来展示就比较好。
从性能上来说,block
的性能消耗要略大于delegate
,因为block
会涉及到栈区向堆区拷贝等操作,时间和空间上的消耗都大于代理。而代理只是定义了一个方法列表,在遵守协议对象的objc_protocol_list
中添加一个节点,在运行时向遵守协议的对象发送消息即可。
多线程通知:
NSNotificationCenter
消息的接受线程是基于发送消息的线程,也就是同步的。因此,有时候,你发送的消息可能不在主线程,而大家都知道操作UI必须在主线程,不然会出现不响应的情况。所以,在你收到消息通知的时候,注意选择你要执行的线程。
//接受消息通知的回调
- (void)test
{
if ([[NSThread currentThread] isMainThread]) {
NSLog(@"main");
} else {
NSLog(@"not main");
}
dispatch_async(dispatch_get_main_queue(), ^{
//do your UI
});
}
//发送消息的线程
- (void)sendNotification
{
dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(defaultQueue, ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:nil];
});
}
2、代理:是一种通用的设计模式,iOS中对代理支持的很好,由代理对象、委托者、协议三部分组成。
代理是一种通用的设计模式,在iOS中对代理设计模式支持的很好,有特定的语法来实现代理模式,OC语言可以通过@Protocol实现协议。
代理主要由三部分组成:
-
协议:用来指定代理双方可以做什么,必须做什么。
-
代理:根据指定的协议,完成委托方需要实现的功能。
-
委托:根据指定的协议,指定代理去完成什么功能。
(1)
@protocol LoginProtocol @optional - (void)userLoginWithUsername:(NSString *)username password:(NSString *)password; @end
// 通过属性来设置代理对象 @property (nonatomic, weak) id delegate;
// 判断代理对象是否实现这个方法,没有实现会导致崩溃 if ([self.delegate respondsToSelector:@selector(userLoginWithUsername:password:)]) { // 调用代理对象的登录方法,代理对象去实现登录方法 [self.delegate userLoginWithUsername:self.username.text password:self.password.text]; }
(2)
// 遵守登录协议 @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LoginViewController *loginVC = [[LoginViewController alloc] init]; loginVC.delegate = self; [self.navigationController pushViewController:loginVC animated:YES]; } /** * 代理方实现具体登录细节 */ - (void)userLoginWithUsername:(NSString *)username password:(NSString *)password { NSLog(@"username : %@, password : %@", username, password); }
3、block:iOS4.0中引入的一种回调方法,可以将回调处理代码直接写在block
代码块中,看起来逻辑清晰代码整齐。
①在后面控制器的 .h文件 中声明block
// 一会要传的值为NSString类型 typedef void (^newBlock)(NSString *); @interface NewViewController : UIViewController // 声明block属性 @property (nonatomic, copy) newBlock block;
// 声明block方法 - (void)text:(newBlock)block; @end
②在后面控制器的 .m文件 中设置block
// 设置block,设置要传的值 - (void)text:(newBlock)block { self.block = block; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:YES]; if (self.block != nil) { self.block(@"呵呵"); } }
③在前面控制器的 .m文件 中接收传来的值
#import "ViewController.h" #import "NewViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; UIButton *button = [UIButton buttonWithType:(UIButtonTypeRoundedRect)]; button.frame = CGRectMake(0, 100, 100, 100); button.backgroundColor = [UIColor redColor]; [button addTarget:self action:@selector(push) forControlEvents:(UIControlEventTouchUpInside)]; [self.view addSubview:button]; } - (void)push { NewViewController *newVC = [[NewViewController alloc] init]; // 接收block传来的值 newVC.block = ^(NSString *str){ NSLog(@"%@", str); }; [self.navigationController pushViewController:newVC animated:YES]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
4、KVO:NSObject
的Category
-NSKeyValueObserving
,通过属性监听的方式来监测某个值的变化,当值发生变化时调用KVO
的回调方法。
KVO(key-value-observing)
是一种十分有趣的回调机制,在某个对象注册监听者后,在被监听的对象发生改变时,对象会发送一个通知给监听者,以便监听者执行回调操作。最常见的KVO运用是监听scrollView
的contentOffset
属性,来完成用户滚动时动态改变某些控件的属性实现效果,包括渐变导航栏、下拉刷新控件等效果。
使用KVO的要求是对象必须能支持kvc机制——所有NSObject的子类都支持这个机制。拿上面的渐变导航栏做,我们为tableView添加了一个监听者controller,在我们滑动列表的时候,会计算当前列表的滚动偏移量,然后改变导航栏的背景色透明度。
//添加监听者 [self.tableView addObserver: self forKeyPath: @"contentOffset" options: NSKeyValueObservingOptionNew context: nil]; /** * 监听属性值发生改变时回调 */ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { CGFloat offset = self.tableView.contentOffset.y; CGFloat delta = offset / 64.f + 1.f; delta = MAX(0, delta); [self alphaNavController].barAlpha = MIN(1, delta); }
每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
@implementationViewController - (void)viewDidLoad { [superviewDidLoad]; self.person = [[Personalloc] init]; NSLog(@"%@",_person.name); #pragma mark ------使用KVC检测person对象的name属性值有没有发生变化,当它变化时,观察者会做出相应的操作*(执行指定方法) // 1.注册观察者 [_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"我观察的是name属性"]; } // 2.实现指定的方法(回调方法) //当person的name值发生变化时,观察者会自动执行这个方法,这个方法名是固定的,不可改变 - (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context { NSLog(@"被监测的那个对象的属性所在的路径:%@",keyPath); NSLog(@"被观察者:%@", object); NSLog(@"属性所有状态下的值:%@", change); NSLog(@"在注册观察者的时候,传过来的context :%@", context); if(![[changeobjectForKey:@"new"]isEqualToString:[changeobjectForKey:@"old"]]) { self.view.backgroundColor= [UIColorredColor]; } //4.移除观察者 [_person removeObserver:selfforKeyPath:keyPathcontext:context]; } - (IBAction)changePersonValue:(UIButton*)sender { // 3.当属性值发生变化时,将会触发回调方法 NSLog(@"name值发生了变化!"); _person.name=_textField.text; } @end
//注册屏幕旋转通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientChange:) name:UIDeviceOrientationDidChangeNotification object:[UIDevice currentDevice]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarOrientationChange:)name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
// 监听网络状态改变的通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkStateChange) name:kReachabilityChangedNotification object:nil];
//监听系统中断音频播放
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(yinpinInterruption:) name:AVAudioSessionInterruptionNotification object:nil];
//监听APP状态信息
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(someMethod1:) name:UIApplicationDidBecomeActiveNotification object:nil];
// 键盘通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBoardWillShow:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBoardWillHide:) name:UIKeyboardWillHideNotification object:nil]; #pragma mark - 键盘显示/隐藏 /** * 键盘显示 * * @param note */ - (void)keyBoardWillShow:(NSNotification *)note{ CGRect rect = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; [UIView animateWithDuration:[note.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue] animations:^{ _tableView.frame = CGRectMake(0, 20, self.view.frame.size.width, self.view.frame.size.height-rect.size.height); }completion:^(BOOL finished) { }]; } /** * 键盘隐藏 * * @param note */ - (void)keyBoardWillHide:(NSNotification *)note{ [UIView animateWithDuration:[note.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue] animations:^{ _tableView.frame = CGRectMake(0, 20, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)-20); }]; }