iOS 趣谈设计模式——通知

【前言介绍】

iOS的一种设计模式,观察者Observer模式(也叫发布/订阅,即Publich/Subscribe模式)。
观察者模式,包含:

在这本文中,我们将介绍在日常项目当中经常使用到的通知机制这一种设计模式。

通知机制
委托机制是代理“一对一”的对象之间的通信,而通知机制是广播“一对多”的对象之间的通信;


一、是什么?【生活问题例子】

“短信天气预报”


当A类发送一条信息给通知中心时,注册为用户(观察者)的B类群就会收到相应的通知,并作出反应。


二、有什么用?【代码中的应用】

在不同类之间如何传递数据?
有几种方法:属性传递、代理协议,另外就是通知。
通知:在A类中创建的方法,B类中执行,且可以使用该通知携带数据传递给对方;


三、有什么不同?【与其他“通知”的不同?】

经常提到的通知,有“广播通知”、“本地通知”、“推送通知”。
本文所介绍的就是广播通知,是实现观察者模式的一种机制,可以在一个应用中的多个对象之间进行通信传递数据。
而本地通知和推送通知主要是给用户发送“通知提示”,例如警告提示、声音、震动以及如图标上的红色数字提示。
第一种由“本地发送通知”给用户,第二种由第三方应用发送给苹果官方的远程服务器,然后再由服务器“推送通知”给用户。


四、产品经理:老规矩,代码拿来~【具体实现】

过程
在通知机制中,需要(或者说感兴趣)接收某个通知的信息的所有对象都可以成为接收者,首先注册成为观察者。
进行注册后,通知中心就会把发布者发送的通知信息,广播给注册过该通知的观察者。且观察者只能接收到通知中心的信息,不能知道通知是谁投送的。
最后,接受者不想再对关注该通知的信息时,可以给通知中心发生解除注册的信息,之后都不再接收到通知了。

1.获取通知中心(NSNotificationCenter)对象:(就像获取移动营运商短信中心的权限,作为媒介)

发布、注册、解除通知都需要使用通知中心,负责协助不同对象、不同类之间的消息通信。

[NSNotificationCenter  defaultCenter];  //需要注意的是,通知中心也是一个单例
2.发布(A类)和接收(B类)

a.做为发布者的A类发送通知
可以使用一下三个方法:

- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSString *)aNameobject:(id)anObject;
- (void)postNotificationName:(NSString *)aNameobject:(id)anObject userInfo:
(NSDictionary *)aUserInfo;
  • postNotificationName:指定消息名称;
  • object:指定发消息者;
  • userInfo:通知中用于传递参数的载体,传递的方法是把参数放在NSDictionary类型的userInfo中。例如:NSDictionary *dict = [notification userInfo];

一般使用第三个方法,如果参数不需要的,可以设置为nil.

b.注册通知,加入观察者:
做为观察者B类注册通知,进行监听:

- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;
//@selector中为回调方法,在本类中对通知进行相应的处理,name为通知名称、object为对象;

剖析

  • object==nil,那么客户对象(self)将收到任何对象发出NSWindowDidBecomeMainNotification的通知消息;
  • name==nil,那么观察者将接收到object对象的所有消息,但是确定不了接收这些消息的顺序。
  • object==nil,name==nil,那么该观察者将收到所有对象的所有消息。

对于一个任意的观察者observer,如果不能保证其对应的selector有本类自定义的方法:(例如,MyMethod),可采用[observer respondsToSelector:@selector(MyMethod:)]]进行检查。
所以完整的添加观察者过程为:

if([observer respondsToSelector:@selector(MyMethod:)]) {
[[NSNotificationCenter defaultCenter] addObserver:observer selector:
@selector(MyMethod:) name:NSWindowDidBecomeMainNotification object:nil];
}

当然在苹果API中也有另外一个注册观察者的方法:

- (id )addObserverForName:(nullable NSString *)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block

此方法是支持在该方法中进行block回调的,而queue参数就是表示此模块在queue队列中进行。
但是这方法一般不采用,因为在此方法的 block 中,稍微不注意调用 self 的话,会引起循环引用,造成内存泄露,所以还是建议使用第一种方法进行观察者的创建。

c.移除通知
注册过的对象必须在释放之前注销掉,如果不这样的话,当该通知再次出现时,通知中心可能会向已释放的观察者对象发送消息,从而导致应用崩溃。
在ARC下,系统会自动回收无用的通知对象内存,但是由于系统回收机制ARC有一定的延迟性,所以即使不会出错,也建议养成习惯,对通知进行手动释放无用的通知。
移除有2种方法:

//释放所有通知
- (void)removeObserver:(id)observer;

//释放名称为aName的通知
- (void)removeObserver:(id)observer name:(nullable NSString *)aName object:(nullable id)anObject;

一般在视图控制器中,可以在“didReceiveMemoryWarning:”中发送解除消息:【这只是参考,建议还是在 :-(void)dealloc ){} 中进行移除。 】

-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
//移除观察者
[[NSNotificationCenter defaultCenter]removeObserver:self];
}

(by:从 iOS 9 开始通知中心会对观察者进行 weak 弱引用,所以不需要在观察者对象释放之前从通知中心移除。即使不对通知进行手动移除,指针也会在注册者被回收后自动置空,向空指针 nil 发送消息是不会有问题的。
但是,通过 - (id )addObserverForName: object: queue: usingBlock: 方法注册的观察者依然需要手动的释放,因为通知中心对它们持有的是强引用。)


五、那些年我们用过的系统通知名称~

系统自带的也有许多有用的通知,我们只需要注册为相应的通知接收对象,就能根据通知状态的变化发生相应的数据改变。
部分系统通知名称如下:

UIApplicationDidFinishLaunchingNotification   // 应用程序启动后
UIApplicationDidBecomActiveNotification       //进入前台
UIApplicationWillResignActiveNotification     //应用将要进入后台
UIApplicationDidEnterBackgroundNotification   //进入后台
UIKeyboardWillShowNotification       // 键盘即将显示
UIKeyboardDidShowNotification        // 键盘显示完毕
UIKeyboardWillHideNotification       // 键盘即将隐藏
UIKeyboardDidHideNotification        // 键盘隐藏完毕

六、举个栗子:“🌰”
本文有2个例子:

  • 一个是完整的通知发布、接收、解除过程;
  • 一个是系统通知名称的应用(以第三个:UIApplicationWillResignActiveNotification
    为例);

(by:觉得文章太长不想看这段的童鞋,也可以到github上下载啊左的demo,:Mydemo1Mydemo2。自己琢磨琢磨。
点击“DownLoad ZIP”按钮就可以了。一般使用Safari浏览器下载得了,啊左用QQ浏览器貌似下载不了...囧)

【本次开发环境: Xcode:7.2 iOS Simulator:iphone6 By:啊左】

1.完整的通知发布、接收、解除过程:

UI控件摆放如下,视图、控件的背景可以自己设置成比较明显的颜色,便于观察:


  • A视图创建一个textView用于显示B视图传递过来的信息,一个按钮用于切换到B视图;
  • B视图创建一个文本框用于更新信息,一个按钮用于把文本框的信息更新并返回到视图A。

然后,点击A类的按钮,并且按住control拖拽到B视图的控制器后松开鼠标,在弹出的选择框(如下图)选择:“Present Modally”用于创建A、B控制器之间的模态类型的Segue。


接下来,我们需要在新建一个视图控制器B类SeocndViewController:


回到故事板中,选择B视图控制器,打开标识检查器(下图第一排第三个选项),选择class为:SeocndViewController。这就使代码与故事板中的视图控制器对应起来。(A视图默认对应ViewController,如果有错误,可以检查一下。)


然后我们打开辅助编辑器,按住control,拖拽A视图中的文本连接到对应的输出口,这里我们命名为“myLabel”.


以此方式,继续为B类中的文本框连接到代码中,并命名为:“MyTextView”,
为B类的按钮添加行为,方法名为:“saveBtn:”,
啊左还是觉得上代码实在点:
(ViewController.h类)

#import
@interface ViewController : UIViewController
//每次视图打开后,监听B类的数据是否发生变化,如有变化,在这个文本视图中显示更新。
@property (weak, nonatomic) IBOutlet UITextView *myLabel;  
@end

(ViewController.m类)

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//1.注册为观察者,监听B视图中的通知
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(AMethod:) name:@"MyNotificationName" object:nil];
}

//回调方法:AMethod:
-(void)AMethod:(NSNotification *)notification
{
//2.获取通知携带的数据,更新label的文本信息
NSDictionary *dictData = [notification userInfo];
NSString *str = [dictData objectForKey:@"MyUserInfoKey"];
self.myLabel.text = str;
}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
//3.移除所有通知
[[NSNotificationCenter defaultCenter]removeObserver:self];
}
@end

(SecondViewController.h)

#import
@interface SecondViewController : UIViewController
 //文本框,用于更新传递给ViewController视图的数据
@property (weak, nonatomic) IBOutlet UITextField *MyTextView; 
- (IBAction)saveBtn:(UIButton *)sender;        //保存返回按钮事件
@end

(SecondViewController.m)

#import "SecondViewController.h"
@interface SecondViewController ()
@end
@implementation SecondViewController

- (void)viewDidLoad {
[super viewDidLoad];
}

- (IBAction)saveBtn:(UIButton *)sender {

//返回视图A并在块中发布通知
[self dismissViewControllerAnimated:YES completion:^{
//1.创建userInfo携带的信息
NSString *str = self.MyTextView.text;
NSDictionary *dictData = [NSDictionary dictionaryWithObject:str forKey:@"MyUserInfoKey"];

//2.发布信息
[[NSNotificationCenter defaultCenter]postNotificationName:@"MyNotificationName" object:nil userInfo:dictData];
}];

}
@end

验证:
第一次A视图的文本视图中没有显示数据,点击按钮“确定切换页面”,打开视图B,在文本框中输入信息(例如123),点击“保存返回”按钮,在A视图的文本视图中看到更新的信息:123。
by:有需要的童鞋可以到github上下载啊左的demo:Mydemo1

2.系统通知名称的应用(以UIApplicationWillResignActiveNotification为例):

UIApplicationWillResignActiveNotification的意思是应用即将进入后台的这个时刻。
首先,创建UI界面如下,相比第一个例子,这个会简单很多:一个按钮+一个显示颜色的UIView视图。


创建一个命名为“myView”的UIView控件,一个方法为“changeColorBtn:”的按钮行为即可,关联ViewController控制器。
代码如下:
(ViewController.h类)

#import
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UIView *myView;
- (IBAction)changeColorBtn:(UIButton *)sender;
@end

(ViewController.m类)

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController

- (void)viewDidLoad {    
[super viewDidLoad];       
 
//1. 注册为观察者    
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(applicationWillResignActiveNotification:) name:UIApplicationWillResignActiveNotification object:nil];
}

//2.当应用即将进入后台时,调用通知回调方法:
-(void)applicationWillResignActiveNotification:(NSNotification *)notification{  
//返回后台的过程,把视图背景改为红色;    
self.myView.backgroundColor = [UIColor redColor];
}

- (IBAction)changeColorBtn:(UIButton *)sender {        
//按钮把视图背景改为黄色;    
self.myView.backgroundColor = [UIColor yellowColor];
}
@end

视图第一次打开,视图为默认白色:
点击按钮,视图变为黄色:


>【按钮事件】

按住“command+shift”,双击H,进入iOS多任务栏;
或者按住“command+shift”,单击H,回到模拟器主界面。


>【iOS多任务栏】

发现,以上2种情况都可以看到视图变为红色。
且回到应用后,颜色仍然是红色。


>【回到前台】

也就是,当应用从活跃的状态进入非活跃状态的时候,系统自动发送“UIApplicationWillResignActiveNotification”这个通知,如有注册监听者(观察者),则执行回调方法。
by:有需要的童鞋可以到github上下载啊左的demo:Mydemo2



(转载请标明原文出处,谢谢支持 ~ - ~)
 by:啊左~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容