在觀察者模式中,一個對象將會通知其他對象的任何狀態的改變。這些相關的對象並不需要去知道另一個對象-這樣就造成了一個非耦合的設計。這個模式大部分用在去通知一個感興趣的對象它的一個屬性已經發生了改變。
一般的實現需要一個對象註冊成爲它感興趣的狀態的觀察者,當這個狀態改變了,所有的觀察者對象都會接收到通知。蘋果的Push Notification服務就是對這個最好的例子。
如果你想要堅持MVC設計模式的概念,你需要去允許Model對象去和View對象交流,但是它們之間並沒有直接的引用,這就是觀察站模式引入的原因。
Cocoa實現觀察者模式有兩種常用的方法:Notification和Key-Value-Observing(KVO)
Notificaions
不要和和push或者本地的通知相混淆,Notifications是基於一個訂閱-分發的模型去允許一個對象發送一些消息給其他對象。這個對象不 需要去知道關於訂閱者的任何信息。Notifications被蘋果公司用的很多。例如,當鍵盤顯示或者隱藏時,系統將會發送一個 UIKeyboardWillShowNotification/UIKeyboardWillHideNotification,響應的當你進入到後 臺,系統將會發送一個UIApplicationDidEnterBackgroundNotification的通知.
提醒:打開UIApplication.h在文件的結尾你會看到一系列超過20條系統發送的通知。
如何使用Notifications
打開AlbumView.m加入下面的代碼到[self addSubView:indicator]後面:
[[NSNotificationCenterdefaultCenter] postNotificationName:@"DownloadImageNotification" object:self userInfo:@{@"p_w_picpathView":coverImage, @"coverUrl":albumCover}];
這一行通過NSNotificationCenter單例發送一個通知,這個通知的info裏面包括了UIImageView去計算和要下載的cover
p_w_picpath的URL,這就是全部的你需要去實現下載任務的信息。
添加下面的代碼到LibraryAPI的init方法中,直接在isOnline = No的後面。
[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(downloadImageAction:) name:@"DownloadImageNotification"object:nil];
這就是方程式的另一端,觀察者。每次一個AlbumView類發送一個DownloadImageNotification的通知的是很好,因爲Library已經註冊成爲它的一個觀察者,所以系統會通知它,那麼它就會相應的去執行downloadImage。
然而,在你實現downloadImage之前,你必須記住當你的類deallocated的時候去取消觀察者的狀態。否則的話,一個通知可能會被髮送給一個已經被deallocated的對象,那麼就會導致app的崩潰。
添加下面的方法到Library中:
- (void)dealloc { [[NSNotificationCenterdefaultCenter]removeObserver:self]; }
當這個類結束後,它會移除它所有的通知中的觀察者狀態。
還有一件事要去做。如果你將下載的covers保存起就是一個好主意,因爲我們不用一次又一次的去下載相同的covers。打開PersistencyManager.h然後添加下面的這兩個方法的原型:
- (UIImage *)getImageFromFileName:(NSString *)fileName; -(void)saveAlbums;
然後將它們的實現代碼添加到.m文件中:
-(void)saveImage:(UIImage *)p_w_picpath fileName:(NSString *)filename { filename = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@",filename]; NSData *data = UIImagePNGRepresentation(p_w_picpath); [data writeToFile:filename atomically:YES]; } -(UIImage *)getImageFromFileName:(NSString *)fileName { fileName = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@",fileName]; NSData *data = [NSDatadataWithContentsOfFile:fileName]; return [UIImagep_w_picpathWithData:data]; }
這幾個代碼是十分簡潔的,它下載的圖片將會被保存在Documents目錄下,然後getImage:將會是nil如果在Documents目錄下一個匹配的文件都沒有。
然後添加下面的方法到Library.m中:
- (void)downloadImageAction:(NSNotification *)notification { UIImageView *p_w_picpathView =notification.userInfo[@"p_w_picpathView"]; NSString *coverUrl =notification.userInfo[@"coverUrl"]; p_w_picpathView.p_w_picpath = [managergetImageFromFileName:[coverUrl lastPathComponent]]; if ( p_w_picpathView.p_w_picpath == nil) { NSLog(@"notificatiom%@",notification.userInfo); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ UIImage *p_w_picpath = [clientdownloadImage:coverUrl]; dispatch_async(dispatch_get_main_queue(), ^{ p_w_picpathView.p_w_picpath = p_w_picpath; [managersaveImage:p_w_picpath fileName:[coverUrl lastPathComponent]]; }); }); } }
這是上面代碼的講解:
1. downloadImage會通過通知被執行,所以這個方法以通知爲參數,UIImageView和p_w_picpath URL從通知中得到。
2. 如果先前已經下載的話那麼就從PersistencyManager中去獲取圖片
3. 如果沒下載的話就通過HTTPClient去下載。
4. 當下載結束,在uip_w_picpath view 上顯示圖片,再用manager去將它在本地保存。
你又一次使用外觀設計模式去隱藏了下載圖片的複雜性,這個nofitifation根本不會去關心這個圖片是通過網頁下載的還是本地獲得的。
編譯和運行你的app,你將會看到下面這個美麗的covers在你的HorizentalScroller中。
停止你的app然後再次運行,只一道沒有任何的延時在加載covers的時候,你可以斷開網絡然後你的應用還是運行的完美無瑕。然而你會發現,這個spinning並沒有停止運行。這是怎麼回事?
你在開始下載的時候開始啓動這個spingning,但是你並沒有實現在圖片開始下載完好的時候去停止它的邏輯。你可以在每次圖片已經下載完的時候發送一個notification,但是另外你可以使用另一個觀察者模式-KVO。
Key-Value_Observing(KVO)
在KVO中,一個對象可以請求去在一個具體的屬性開始變化的時候得到它的一個通知,不論這個屬性屬於它自己還是另一個對象。在這個例子裏面,你可以使用KVO去觀察加載p_w_picpath的UIImageView中的這個p_w_picpath屬性的改變。
大家AlbumView.m,添加下面這個代碼到initWIthFrame:albumCover:中,添加在[selfaddSubview:indicator]之後。
[coverImageaddObserver:selfforKeyPath:@"p_w_picpath"options:0 context:nil];
這添加了self,也當前的類成爲coverImage的p_w_picpath屬性的一個觀察者。
你也需要吧去在你結束的時候取消成爲觀察者.仍在AlbumView.m中添加下面的代碼:
-(void)dealloc { [coverImageremoveObserver:selfforKeyPath:@"p_w_picpath"]; }
最後,添加這個方法:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(voidvoid *)context { if ([keyPath isEqualToString:@"p_w_picpath"]) { [indicatorstopAnimating]; } }
你必須在每個你作爲一個觀察者的類裏面吧去實現這個方法。每次當被觀察的屬性變化時系統就會執行這個方法,在上面的方法中當p_w_picpath屬性改變的時候就會調用這個方法。就這樣,當一個圖片下載完成,這個spinning就會停止.
編譯運行你的工程,這個spinnning 就會消失。
小貼士:一定要記得去remove你的observers在它們deallocated時,否則當這個對象視圖像一個不存在的觀察者發送一個消息時那麼你的app就會崩潰掉。
如果你運行以下app然後滾動一下這個covers,之後停止運行它,你會注意到app的狀態並沒有保存下來。你看到的最後一個視圖並沒有在應用再次啓動的時候成爲默認的設置。
要改正這一點,你可以充分的利用下一個設計模式:Memento設計模式(備忘錄模式)