iOS 趣谈设计模式——通知

TereseXtk 8年前
   <h2>【前言介绍】</h2>    <p>iOS的一种设计模式,观察者Observer模式(也叫发布/订阅,即Publich/Subscribe模式)。<br> 观察者模式,包含了通知机制(notification)和KVO(Key-value-observing)机制。<br> 在这本文中,我们将介绍在日常项目当中经常使用到的<strong>通知机制</strong>这一种设计模式。</p>    <p><strong>通知机制</strong>:<br> 委托机制是代理“一对一”的对象之间的通信,而通知机制是广播“一对多”的对象之间的通信;</p>    <h2>一、是什么?【生活问题例子】</h2>    <p>“短信天气预报”</p>    <p><img alt="iOS 趣谈设计模式——通知" src="https://simg.open-open.com/show/51ded65db1f6eeba66ac614bbbee7e88.png"></p>    <p> </p>    <p>当A类发送一条信息给通知中心时,注册为用户(观察者)的B类群就会收到相应的通知,并作出反应。</p>    <h2>二、有什么用?【代码中的应用】</h2>    <p>在不同类之间如何传递数据?<br> 有几种方法:属性传递、代理协议,另外就是通知。<br> 通知:在A类中创建的方法,B类中执行,且可以使用该通知携带数据传递给对方;</p>    <h2>三、有什么不同?【与其他“通知”的不同?】</h2>    <p>经常提到的通知,有“广播通知”、“本地通知”、“推送通知”<br> 本文所介绍的就是广播通知,是实现观察者模式的一种机制,可以在一个应用中的多个对象之间进行通信传递数据。<br> 而本地通知和推送通知主要是给用户发送“通知提示”,例如警告提示、声音、震动以及如图标上的红色数字提示。<br> 第一种由“本地发送通知”给用户,第二种由第三方应用发送给苹果官方的远程服务器,然后再由服务器“推送通知”给用户。</p>    <h2>四、产品经理:老规矩,代码拿来~【具体实现】</h2>    <p><em>过程</em>:<br> 在通知机制中,需要(或者说感兴趣)接收某个通知的信息的所有对象都可以成为接收者,首先注册成为观察者。<br> 进行注册后,通知中心就会把发布者发送的通知信息,广播给注册过该通知的观察者。且观察者只能接收到通知中心的信息,不能知道通知是谁投送的。<br> 最后,接受者不想再对关注该通知的信息时,可以给通知中心发生解除注册的信息,之后都不再接收到通知了。</p>    <p>1.获取通知中心(NSNotificationCenter)对象:(就像获取移动营运商短信中心的权限,作为媒介)</p>    <p>发布、注册、解除通知都需要使用<strong>通知中心</strong>,负责协助不同对象、不同类之间的消息通信。</p>    <pre>  <code class="language-objectivec">[NSNotificationCenter  defaultCenter];  //需要注意的是,通知中心也是一个单例</code></pre>    <p>2.发布(A类)和接收(B类)</p>    <p><strong>a.做为发布者的A类发送通知</strong><br> 可以使用一下三个方法:</p>    <pre>  <code class="language-objectivec">- (void)postNotification:(NSNotification *)notification;  - (void)postNotificationName:(NSString *)aNameobject:(id)anObject;  - (void)postNotificationName:(NSString *)aNameobject:(id)anObject userInfo:  (NSDictionary *)aUserInfo;</code></pre>    <ul>     <li>postNotificationName:指定消息名称;</li>     <li>object:指定发消息者;</li>     <li>userInfo:通知中用于传递参数的载体,传递的方法是把参数放在NSDictionary类型的userInfo中。例如:NSDictionary *dict = [notification userInfo];</li>    </ul>    <p>一般使用第三个方法,如果参数不需要的,可以设置为nil.</p>    <p><strong>b.注册通知,加入观察者:</strong><br> 做为观察者B类注册通知,进行监听:</p>    <pre>  <code class="language-objectivec">- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;  //@selector中为回调方法,在本类中对通知进行相应的处理,name为通知名称、object为对象;</code></pre>    <p><strong>剖析</strong>:</p>    <ul>     <li>object==nil,那么客户对象(self)将收到任何对象发出NSWindowDidBecomeMainNotification的通知消息;</li>     <li>name==nil,那么观察者将接收到object对象的所有消息,但是确定不了接收这些消息的顺序。</li>     <li>object==nil,name==nil,那么该观察者将收到所有对象的所有消息。</li>    </ul>    <p>对于一个任意的观察者observer,如果不能保证其对应的selector有本类自定义的方法:(例如,MyMethod),可采用[observer respondsToSelector:@selector(MyMethod:)]]进行检查。<br> 所以完整的添加观察者过程为:</p>    <pre>  <code class="language-objectivec">if([observer respondsToSelector:@selector(MyMethod:)]) {  [[NSNotificationCenter defaultCenter] addObserver:observer selector:  @selector(MyMethod:) name:NSWindowDidBecomeMainNotification object:nil];  }</code></pre>    <p>当然在苹果API中也有另外一个注册观察者的方法:</p>    <pre>  <code class="language-objectivec">- (id )addObserverForName:(nullable NSString *)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block</code></pre>    <p>此方法是支持在该方法中进行block回调的,而queue参数就是表示此模块在queue队列中进行。<br> 但是这方法一般不采用,所以建议使用第一种方法进行观察者的创建。</p>    <p><strong>c.移除通知</strong><br> 由于通知中心不会retain观察者对象,因此注册过的对象必须在释放之前注销掉,如果不这样的话,当该通知再次出现时,通知中心会向已释放的观察者对象发送消息,从而导致应用崩溃。<br> 在ARC下,系统会自动回收无用的通知对象内存,但是由于系统回收机制ARC有一定的延迟性,所以即使不会出错,也建议养成习惯,对通知进行手动释放无用的通知。<br> 移除有2种方法:</p>    <pre>  <code class="language-objectivec">//释放所有通知  - (void)removeObserver:(id)observer;    //释放名称为aName的通知  - (void)removeObserver:(id)observer name:(nullable NSString *)aName object:(nullable id)anObject;</code></pre>    <p>一般在视图控制器中,可以在“didReceiveMemoryWarning:”中发送解除消息:</p>    <pre>  <code class="language-objectivec">-(void)didReceiveMemoryWarning  {  [super didReceiveMemoryWarning];  //移除观察者  [[NSNotificationCenter defaultCenter]removeObserver:self];  }</code></pre>    <h2>五、那些年我们用过的系统通知名称~</h2>    <p>系统自带的也有许多有用的通知,我们只需要注册为相应的通知接收对象,就能根据通知状态的变化发生相应的数据改变。<br> 部分系统通知名称如下:</p>    <pre>  <code class="language-objectivec">UIApplicationDidFinishLaunchingNotification   // 应用程序启动后  UIApplicationDidBecomActiveNotification       //进入前台  UIApplicationWillResignActiveNotification     //应用将要进入后台  UIApplicationDidEnterBackgroundNotification   //进入后台  UIKeyboardWillShowNotification       // 键盘即将显示  UIKeyboardDidShowNotification        // 键盘显示完毕  UIKeyboardWillHideNotification       // 键盘即将隐藏  UIKeyboardDidHideNotification        // 键盘隐藏完毕</code></pre>    <h2><strong>六、举个栗子</strong></h2>    <p>本文有2个例子:</p>    <ul>     <li>一个是完整的通知发布、接收、解除过程;</li>     <li>一个是系统通知名称的应用(以第三个:UIApplicationWillResignActiveNotification<br> 为例);</li>    </ul>    <p>(by:觉得文章太长不想看这段的童鞋,也可以到github上下载啊左的demo,:<a href="/misc/goto?guid=4959672270857706516">Mydemo1</a>、<a href="/misc/goto?guid=4959672270940679893">Mydemo2</a>。自己琢磨琢磨。<br> 点击“DownLoad ZIP”按钮就可以了。一般使用Safari浏览器下载得了,啊左用QQ浏览器貌似下载不了...囧)</p>    <p><em>【本次开发环境: Xcode:7.2 iOS Simulator:iphone6 By:啊左】</em></p>    <p>1.完整的通知发布、接收、解除过程:</p>    <p>UI控件摆放如下,视图、控件的背景可以自己设置成比较明显的颜色,便于观察:</p>    <p><img alt="iOS 趣谈设计模式——通知" src="https://simg.open-open.com/show/3bc9e8cf4e370e97e1977973b1bef78e.png"></p>    <p><img alt="iOS 趣谈设计模式——通知" src="https://simg.open-open.com/show/a890dd5f9cc8ca66a12d8f9b748f826a.png"></p>    <ul>     <li>A视图创建一个textView用于显示B视图传递过来的信息,一个按钮用于切换到B视图;</li>     <li>B视图创建一个文本框用于更新信息,一个按钮用于把文本框的信息更新并返回到视图A。</li>    </ul>    <p>然后,点击A类的按钮,并且按住control拖拽到B视图的控制器后松开鼠标,在弹出的选择框(如下图)选择:“Present Modally”用于创建A、B控制器之间的模态类型的Segue。</p>    <p><img alt="iOS 趣谈设计模式——通知" src="https://simg.open-open.com/show/0869976b99620f0abc7490dcf97fe1b0.png"></p>    <p>接下来,我们需要在新建一个视图控制器B类SeocndViewController:</p>    <p><img alt="iOS 趣谈设计模式——通知" src="https://simg.open-open.com/show/ea11b1731d3c8fa956bfc20a6d36fc04.png"></p>    <p>回到故事板中,选择B视图控制器,打开标识检查器(下图第一排第三个选项),选择class为:SeocndViewController。这就使代码与故事板中的视图控制器对应起来。(A视图默认对应ViewController,如果有错误,可以检查一下。)</p>    <p><img alt="iOS 趣谈设计模式——通知" src="https://simg.open-open.com/show/c6c78b3f107f190cf889174c29891990.png"></p>    <p>然后我们打开辅助编辑器,按住control,拖拽A视图中的文本连接到对应的输出口,这里我们命名为“myLabel”.</p>    <p><img alt="iOS 趣谈设计模式——通知" src="https://simg.open-open.com/show/7b47b8f309ea31419048edbe466d803a.png"></p>    <p>以此方式,继续为B类中的文本框连接到代码中,并命名为:“MyTextView”,<br> 为B类的按钮添加行为,方法名为:“saveBtn:”,<br> 啊左还是觉得上代码实在点:<br> (ViewController.h类)</p>    <pre>  <code class="language-objectivec">#import  @interface ViewController : UIViewController  //每次视图打开后,监听B类的数据是否发生变化,如有变化,在这个文本视图中显示更新。  @property (weak, nonatomic) IBOutlet UITextView *myLabel;    @end</code></pre>    <p>(ViewController.m类)</p>    <pre>  <code class="language-objectivec">#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</code></pre>    <p>(SecondViewController.h)</p>    <pre>  <code class="language-objectivec">#import  @interface SecondViewController : UIViewController   //文本框,用于更新传递给ViewController视图的数据  @property (weak, nonatomic) IBOutlet UITextField *MyTextView;   - (IBAction)saveBtn:(UIButton *)sender;        //保存返回按钮事件  @end</code></pre>    <p>(SecondViewController.m)</p>    <pre>  <code class="language-objectivec">#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</code></pre>    <p><em>验证:</em><br> 第一次A视图的文本视图中没有显示数据,点击按钮“确定切换页面”,打开视图B,在文本框中输入信息(例如123),点击“保存返回”按钮,在A视图的文本视图中看到更新的信息:123。<br> by:有需要的童鞋可以到github上下载啊左的demo:<a href="/misc/goto?guid=4959672270857706516">Mydemo1</a>。</p>    <p>2.系统通知名称的应用(以UIApplicationWillResignActiveNotification为例):</p>    <p>UIApplicationWillResignActiveNotification的意思是应用即将进入后台的这个时刻。<br> 首先,创建UI界面如下,相比第一个例子,这个会简单很多:一个按钮+一个显示颜色的UIView视图。</p>    <p><img src="https://simg.open-open.com/show/40adbee3fcb1d2d93cc43388abff2c3b.png" alt="iOS 趣谈设计模式——通知" width="378" height="427"></p>    <p>创建一个命名为“myView”的UIView控件,一个方法为“changeColorBtn:”的按钮行为即可,关联ViewController控制器。<br> 代码如下:<br> (ViewController.h类)</p>    <pre>  <code class="language-objectivec">#import  @interface ViewController : UIViewController  @property (weak, nonatomic) IBOutlet UIView *myView;  - (IBAction)changeColorBtn:(UIButton *)sender;  @end</code></pre>    <p>(ViewController.m类)</p>    <pre>  <code class="language-objectivec">#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</code></pre>    <p>视图第一次打开,视图为默认白色:<br> 点击按钮,视图变为黄色:</p>    <p><img src="https://simg.open-open.com/show/c9832a8b4e48c212aecda6226737df20.png" alt="iOS 趣谈设计模式——通知" width="372" height="369"></p>    <p>>【按钮事件】</p>    <p>按住“command+shift”,双击H,进入iOS多任务栏;<br> 或者按住“command+shift”,单击H,回到模拟器主界面。</p>    <p><img src="https://simg.open-open.com/show/b6f9c0661b137b7f93ad75c1a204ef47.png" alt="iOS 趣谈设计模式——通知" width="374" height="537"></p>    <p>>【iOS多任务栏】</p>    <p>发现,以上2种情况都可以看到视图变为红色。<br> 且回到应用后,颜色仍然是红色。</p>    <p><img src="https://simg.open-open.com/show/ffaaf405ce378c08a3ded95ed0af5185.png" alt="iOS 趣谈设计模式——通知" width="378" height="347"></p>    <p>>【回到前台】</p>    <p>也就是,当应用从活跃的状态进入非活跃状态的时候,系统自动发送“UIApplicationWillResignActiveNotification”这个通知,如有注册监听者(观察者),则执行回调方法。<br> by:有需要的童鞋可以到github上下载啊左的demo:<a href="/misc/goto?guid=4959672270940679893">Mydemo2</a>。</p>    <p><br> 文/<a href="/misc/goto?guid=4959672266575441972">啊左</a>(简书)</p>