低耦合性是良好程序的特性。低耦合性程序可读性和可维护性比较好。Cocoa中的委托、通知功能可使低耦合性更易实现,下面结合demo说明如何使用委托、通知进行传值,及委托与通知的区别。ios
委托传值在反向传值中使用。使用委托可让委托和委托对象之间的关系变得清晰,特别是在委托的方法必须实现时。git
委托传值步骤以下:github
1.1 在ChildViewController.h
声明协议,协议内方法默认必须实现。若是想选择实现,在方法前用@optional
标志出来。编程
#import <UIKit/UIKit.h> @protocol ChildVCDelegate <NSObject> - (void)didReceiveText:(NSString *)string; @optional - (void)receiveTextFailedWithError:(NSError *)error; @end
1.2 在ChildViewController.h
接口部分建立一个ChildVCDelegate
类型的实例变量。此时的特性应该使用weak
,不然会形成循环引用。app
#import <UIKit/UIKit.h> @protocol ChildVCDelegate <NSObject> - (void)didReceiveText:(NSString *)string; @optional - (void)receiveTextFailedWithError:(NSError *)error; @end @interface ChildViewController : UIViewController @property (weak, nonatomic) id<ChildVCDelegate> delegate; @end
1.3 在RootViewController.m
中,使你的类遵照ChildViewController.h
里声明的ChildVCDelegate
协议。异步
#import "ViewController.h" #import "ChildViewController.h" @interface ViewController () <ChildVCDelegate> @end
1.4 在RootViewController.m
实现协议方法,将ChildViewController
的代理委托给当前控制器。post
@implementation ViewController // 1 ChildViewController *childVC = [[ChildViewController alloc] init]; childVC.delegate = self; - (void)didReceiveText:(NSString *)string { }
注释1后的代码须要添加到跳转到ChildViewController
的方法内。若是使用纯代码编程,添加到presentViewController: animated: completion:
或showViewController: animated:
方法前;若是使用 storyboard的 segue跳转,添加到prepareForSegue: sender:
方法内,此时初始化视图控制器应该使用SecViewController *secVC =segue.destinationViewController;
。
1.5 在ChildViewController.m
实现部分,调用代理方法。为防止运行时出现问题,调用方法前要先判断代理是否实现了调用的方法。atom
// 在某方法内 if ([self.delegate respondsToSelector:@selector(didReceiveText:)]) { [self.delegate didReceiveText:@"pro648"]; }
NSNotificationCenter
对象(简称通知中心)提供了广播信息的机制,NSNotificationCenter
对象实质上是一个通知分发表。对象使用addObserver: selector: name: object:
或addObserverForName: object: queue: usingBlock:
方法向通知中心注册以接收通知,每次调用上面的方法都会指定一组通知。所以,对象能够经过屡次调用这些方法注册为不一样通知的观察者。url
每个运行的Cocoa程序都有一个默认通知中心,通常不须要本身建立。NSNotificationCenter
对象只能在单个进程中传递通知。若是须要向其余进程发送通知,或从其余进程接收通知,请使用NSDistributedNotificationCenter
。spa
要想接收通知,先要在通知中心注册观察者,注册时声明想要观察通知的名称。若是你是在为iPhone应用程序的视图控制器添加观察者,最好写在viewDidLoad
方法中,这样能够确保视图控制器加载完成时只建立惟一一个观察者用以接收通知。添加观察者方法以下:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveText:) name:@"DidReceiveNotification" object:nil];
观察者对象self
是当前视图控制器,selector
指明当视图控制器接收到通知时调用的方法,这个方法必须为无返回类型、带有一个参数。以下所示:
- (void)didReceiveText:(NSNotification *)notification
若是须要获取与通知一块儿发送的用户信息,能够从NSNotification
对象中提取,以下:
- (void)didReceiveText:(NSNotification *)notification { NSDictionary *userInfo = [notification userInfo]; NSString *receivedText = [userInfo objectForKey:@"YOUR_KEY"]; ... }
发送通知的方法很简单,以下所示:
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"YOUR_OBJECT" forKey:@"YOUR_KEY"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"DidReceiveNotification" object:self userInfo:userInfo];
通知名称通常为字符串常量,object
能够是任何想要和通知一块儿发送的对象,但通常为self
或nil
,若是须要发送额外信息,可使用可选的userInfo
。若是不须要发送额外信息,能够直接把userInfo
设置为nil
,或使用postNotificationName: object:
方法。
从OS X 10.11和iOS 9.0开始,NSNotificationCenter
将再也不向已被释放掉的观察者发送通知,通知中心对观察者是零引用( zeroing weak reference)。所以,下一次通知中心想要向观察者发送通知时,会检测到观察者已不存在并为咱们移除观察者,也就是再也不须要手动移除观察者。须要注意的是,若是使用addObserverForName: object: queue: usingBlock:
方法添加的观察者,或须要支持iOS 8 或更低版本,依旧须要移除观察者,移除方法以下:
- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:@"DidReceiveNotification" object:nil]; }
若是要移除全部观察者,能够直接使用removeObserver:
方法。
这个demo总体思路是:有三个视图控制器,第一个视图控制器上有两个UILabel
,一个名称为下一页UIButton
,点击下一页按钮进入第二个视图控制器;第二个视图控制器上有一个UILabel
,一个UITextField
,两个UIButton
,在UITextField
输入文本后,点击上一页将UITextField
的内容使用委托传值到第一个视图控制器并在UILabel
显示,点击下一页进入第三个视图控制器;第三个视图控制器有一个UITextField
和一个上一页按钮,在UITextField
输入文本后点击上一页按钮,使用通知传值到前两个视图控制器,并显示到UILabel
中。
以下面gif所示:
这里提供一个demo模版,在这个模板上添加代码进行传值练习。
模板名称:Delegation&Notification模板
下载地址:https://github.com/pro648/Bas...
4.1 在SecondViewController.h
接口前面声明协议,用来传值。
#import <UIKit/UIKit.h> @protocol SendTextDelegate <NSObject> - (void)sendText:(NSString *)string; @end @interface SecondViewController : UIViewController @end
4.2 在SecondViewController.h
中定义一个代理属性。
@interface SecondViewController : UIViewController @property (weak, nonatomic) id<SendTextDelegate> delegate; @end
4.3 在SecondViewController.m
实现文件中,调用代理方法。这里在点击上一页按钮回到首页时调用代理方法,把self.textField
的内容传给代理。传值前能够先判断代理是否实现了协议的方法,防止运行时出现问题。更新后的代码以下:
- (IBAction)backToVC:(UIButton *)sender { // 判断是否实现了协议方法 if ([self.delegate respondsToSelector:@selector(sendText:)]) { // 代理实现了协议方法,传送TextField内文本给代理 [self.delegate sendText:self.textField.text]; }else { NSLog(@"代理没有实现协议方法,%d, %s",__LINE__, __PRETTY_FUNCTION__); } // 返回ViewController [self.navigationController popViewControllerAnimated:YES]; }
4.4 进入ViewController.m
文件,声明遵照SendTextDelegate
协议。在跳转到SecondViewController
的方法中设置SecondViewController
的代理为当前控制器。
@interface ViewController () <SendTextDelegate> - (void)goToSecondVC:(UIButton *)sender { // 跳转到SecondViewController SecondViewController *secVC = [[SecondViewController alloc] init]; // 设置secVC的代理为当前控制器 secVC.delegate = self; [self.navigationController pushViewController:secVC animated:YES]; }
4.5 在ViewController.m
实现代理方法,并把传来的值显示到self.deleLabel
中。
- (void)sendText:(NSString *)string { self.deleLabel.text = string; }
在ViewController.m
和SecondViewController.m
的viewDidLoad
方法中添加观察者,name使用全局变量,接收到通知后,执行被调用的方法,把通知附带的字符串显示在notiLabel上。更新后的代码以下:
// ViewController.m extern NSString *NotificationFromThirdVC; @implementation ViewController - (void)viewDidLoad { ... // 添加观察者 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveNotificationMessage:) name:NotificationFromThirdVC object:nil]; } - (void)didReceiveNotificationMessage:(NSNotification *)notification { if ([[notification name] isEqualToString:NotificationFromThirdVC]) { // 把通知传送的字符串显示到notiLabel NSDictionary *dict = [notification userInfo]; NSString *string = [dict objectForKey:@"TextField"]; self.notiLabel.text = string; } } // SecondViewController.m中的代码与ViewController.m中的同样,你能够本身写。若是遇到问题,能够在文章尾部下载源码查看。
SecondViewController.m
中使用addObserverForName:object:queue:usingBlock:
方法注册观察者。代码以下:
- (void)viewDidLoad { ... // 添加观察者 [[NSNotificationCenter defaultCenter] addObserverForName:NotificationFromThirdVC object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { if ([note.name isEqualToString:NotificationFromThirdVC]) { // 把通知传送的字符串显示到notiLabel。 NSDictionary *userInfo = [note userInfo]; self.notiLabel.text = [userInfo valueForKey:@"TextField"]; } }]; }
首先在ThirdViewController.m
实现部分前先声明通知名称为全局变量。
NSString *NotificationFromThirdVC = @"NotificationFromThirdVCTextField";
在ThirdViewController.m
实现部分,在点击回到上一页按钮时发送通知,把UITextField
中的字符串作为额外信息发送,更新后代码以下:
- (IBAction)backToSecVC:(UIButton *)sender { // 发送通知 NSString *string = self.textField.text; NSDictionary *userInfo = [NSDictionary dictionaryWithObject:string forKey:@"TextField"]; [[NSNotificationCenter defaultCenter] postNotificationName:NotificationFromThirdVC object:nil userInfo:userInfo]; // 返回SecondViewController [self.navigationController popViewControllerAnimated:YES]; }
在ViewController.m
中添加观察者使用的是addObserver: selector: name: object:
方法,模拟器是iOS 11,且不计划支持iOS 8或更低版本,ViewController.m
中添加的观察者不须要移除。
SecondViewController.m
中添加观察者使用的是addObserverForName:object:forQueue:usingBlock:
方法,必须手动移除观察者。代码以下:
- (void)dealloc { // 移除观察者。 [[NSNotificationCenter defaultCenter] removeObserver:self name:NotificationFromThirdVC object:nil]; }
使用通知中心传递信息时,必定要先实例化观察者,再发送通知。例如: TabBar上有 VC1和 VC2两个视图控制器,运行后先进入 VC1,若是直接在 VC1上发送通知, VC2上将不能接收到通知,由于此时 VC2尚未运行, VC2的观察者尚未在通知中心注册,因此须要进入 VC1后再点击进入 VC2,以后再返回 VC1发送通知,此时 VC2就能够接收到通知。
如今,运行demo,以下所示:
初看,通知是一种没有缺点的方式来减小类之间的依赖,你甚至不须要向你的类添加一个委托实例变量。如今再来看一下通知的缺点,当你发送通知时,通知中心会同步向全部在通知中心注册的观察者发送信息,直到全部观察者调用他们的注册方法后,发起通知的代码才会再次得到控制。值得注意的是,当你向多个观察者发送通知而且发送通知的代码须要等待完成某些操做时,只有当全部观察者方法被调用并执行完毕时,发送通知的代码才会再次得到控制(观察者方法以一些未指定的顺序一个接一个地调用)。为解决这个问题,一种方法是在不一样线程上有额外通知中心,同时使用异步通知,NSNotificationQueue
容许调用当即返回。这样在大多数状况下会额外增长代码的复杂性。另外一种简便方法是使用performSelector: withObject: afterDelay:
延迟处理通知。
- (void)didReceiveNotificationMessage:(NSNotification *)notification { if ([[notification name] isEqualToString:NotificationFromThirdVC]) { // 把通知传送的字符串显示到notiLabel NSDictionary *dict = [notification userInfo]; NSString *string = [dict objectForKey:@"TextField"]; // 延迟处理 [self performSelector:@selector(DO_YOUR_REAL_WORK) withObject:string afterDelay:0.3]; } }
这样可使发布通知的代码更快得到控制。此时观察者方法在同一线程执行。
通知是将信息传播到你没法接触到的多个对象的一种方法,所以,它能够用在视图控制器间传递数据,但通常来讲,不要这样作。当你发送一个通知,你不知道哪个对象会对此作出反应,若是遇到错误将难以追踪,别人维护你的代码也会变的更加困难。
NSUserDefaults
是用来永久保存用户偏好设置,以便app下次启动时使用。任何保存在此位置的数据如没有明确删除会永远保存在这里。因此最好不要使用NSUserDefaults
传值。
始终使用代理将信息传回其余控制器,内容视图控制器应该永远不须要知道源视图控制器的类或不是它建立的视图控制器。另外,若是你想获取对象属性的变化,最好使用Key Value Observing。
在任何状况下,都不该该让视图控制器发送通知或委托消息。在多数状况下,视图控制器应该更改模型,而后模型通知观察者或委托它已被更改。
文件名称:Delegation&Notification
源码地址:https://github.com/pro648/Bas...
参考资料: