Ios 設計模式,你可能據說過,可是你真正知道這是什麼意思麼?大部分的開發者大概都贊成設計模式很重要,可是關於這一部分卻沒有不少的文章去介紹它,咱們開發者不少時候寫代碼的時候也並不重視設計模式.ios
設計模式是在軟件設計上去解決普通問題的可重用的方法.他們是是幫助你讓所寫的代碼更加容易理解和提升可重用性的模板.它們還能夠幫你建立鬆散耦合的代碼是你能不費很大功夫就能夠改變或者替代你的代碼中的一部分.objective-c
若是你對設計模式感到生疏,那麼我有個好消息告訴你!首先,你已經用了不少ios設計模式多虧了Cocoa 內建的方法。其次,這個教程會帶你加快對在Cocoa中最經常使用的ios設計模式的認識。設計模式
這個教程會分割成幾部分,每一部分都是一個設計模式,在每個部分,你都會讀到如下內容:這是什麼設計模式,爲何使用這個設計模式,怎麼使用這個設計模式,當使用這個設計模式時要注意的常見的陷阱。api
在這個教程中,你會建立一個音樂庫app-會顯示專輯和他們相應的信息。數組
在開發這款app的過程當中,你將會熟悉如下Cocoa中最多見的設計模式。xcode
1建立類型的:單例模式,抽象工廠模式安全
2結構化類型的:MVC, Decorator, Adapter, Facade and Composite服務器
3行爲類型的:Observer, Memento, Chain of Responsibility andCommand。網絡
不要覺得這個是關於理論的一篇文章,你將會學到如何使用這些設計模式的大部分去建立這款app。在這個教程結束後你的app將會是這個樣子:數據結構
開始:
下載開始工程,這只是默認的ViewController和一個簡單的包含空的實現文件的HTTP客戶端。
常識:你知道當你建立一個空的Xcode文件工程那你的代碼已經充滿了設計模式?MVC,Delegate,Protocol,Singleton。
在你涉足到第一個設計模式時,你要建立兩個類去hold和展現專輯的數據。
按住Command + N鍵去建立一個名爲Album的object-c文件,繼承於NSObject。打開Album.h文件,加入如下的屬性和方法。
注意到這些屬性是隻讀的,由於當Album建立之後沒有必要去改變他們。
這個方法是這個對象的初始化方法,當你建立了一個專輯,你會傳給它專輯的名字,藝術家的名字,專輯封面的URL和專輯的年份。
打開Album.m而後將下面代碼添加到 @implementation和@end之間。
這裏沒有什麼神奇的; 只是一個簡單的init方法來建立新Album的實例。
再次,導航到文件\新建\文件...。選擇Cocoa Touch,而後Objective-C類,而後單擊下一步。將類名AlbumView,但此次設定的子類UIView的。單擊下一步,而後建立。
打開ALbumView.h文件,在imlementation.h中添加如下代碼:
在這裏你須要注意的第一件事情是有一個實例變量名爲coverImage,它表明了專輯的封面圖片,第二個變量是indicator,它的旋轉去指示圖片正在被下載。
在實現部分的初始化中將背景設爲黑色,建立的image view有5像素邊緣,而後將指示器indicator添加到試圖中。
小貼士:爲何將私有變量定義在實現部分而不是接口部分?這是由於外部的類不須要知道這些變量的存在由於它們只是在這個類中的實現部分被使用。這個約定是十分重要的,若是你在建立一個庫或者框架給其餘開發者使用。
編譯你的工程(Comamand +B)確保沒有問題,接下來準備好去接受你的第一個設計模式吧。
MVC -設計模式之王
Model View Controller 是Cocoa的基石之一,且毫無疑問的是全部設計模式中最經常使用的設計模式,它根據你的應用中的通常角色去分類對象,鼓勵在徹底分離的模式下分角色。
Model:這個對象hold住你的應用數據,且定義如何去操做它,例如本例中就是Album類。
View:這個對象掌管了Model的可視化顯示,和控制用戶的交互,基本上全部的都是UIView和它的子類。在本例中這個就是被分離成的AlbumView類。
Controller:控制器是調節全部工做的調節器,它訪問模型中的數據,而後用視圖去顯示它,根據要求監聽事件和操做數據。你能想象在這個哪一個是Controller麼,就是ViewController.
視圖和模型經過控制器去交流的場景能夠被描述成如下圖:
若是在Model中有任何數據變化,那麼它就會通知Controller,反過來,Controller更新在View中的數據,View能夠通知Controller關於用戶的行爲,而後Controller要麼根據須要或者檢索要求的數據去更新Model。
你也許會懷疑爲何不僅是建立了Controller而後將View和Model一塊兒放到裏面去實現?那樣看起來不是更容易麼?
這全部的全部都是爲了是代碼分離化和提升可重用性。理想狀況下,視圖應該會從Model中徹底分離出來,若是視圖不依賴於某個具體的Model的實現部分,那麼它能夠用不一樣的Model去展現其餘一些數據來實現它的可重用性。
例如:若是未來你想添加一些電影和書籍到你的庫中去,你仍然可使用相同的AlbumView去展現你的電影和書籍的對象,更進一步說,若是你想去建立一個工程去處理專輯,你能夠很簡單的去重用你的Album類,由於它不依賴於任何一個視圖。這就是MVC的魔力。
首先,你須要確保你的工程中的每個類都是Controller,或者View,或者Model,不要講任何兩個中的角色的任務鏈接在一塊,經過建立Album和AlbumV類你已經作了一個很好的工做。
其次,爲了確保遵照這個工做方法,你應該建立三個工程組去hold住你的代碼,每類一個分組。
按住Command+option+N鍵,建立一個組,名爲Model,一樣建立View和Controller,將Album.h和Album.m拖入Model中,拖動AlbumView.h和AlbumView.m的視圖組,最後拖ViewController.h和ViewController.m到控制器組.
這時候你的工程結構應該看起來是這樣的:
如今看起來沒有那些文件浮在四周,看着好多了。顯然你能夠有其餘的組和類,可是這個應用中的核心就是包含在這三個類中的。
既然你的組成部分已經被組織起來了,你須要從別的地方去得到album的數據,你將會建立一個API類去在所有的代碼中去管理這些數據-這將會在你的下一個設計模式-單例中獲得展現。
單例模式確保爲一個肯定的類只有一個實例存在,並且有一個全局的訪問指針只想它,他常用延時加載去在第一次使用的時候建立一個簡單的實例。
小貼士:蘋果使用這個方法很頻繁。好比:[NSUserDefaults standarUserDefaults], [UIApplicationsharedApplication],[UIScreen mainScreen],[NSFileManager defaultManager],都返回一個單例。
你可能會覺得爲何你會介意一個類周圍會有不止一個實例,代碼和內存都很簡化,對不對?
有一些狀況下一個類只有一個實例會頗有意義。好比:沒有必要去有不少的Logger實例,除非你想要同時寫入幾個日記文件,或者,擁有一個全聚德配置控制的類,很容易就實現一個線程安全的單個共享資源,好比配置文件,而不是多個類同時修改同一個配置文件。
看一下右邊的這個圖表:
它顯示了一個Logger類只有一個屬性,(也是一個實例),和兩個方法init和sharedInstance;
第一次客戶端發送sharedInstance消息,這和屬性的實例尚未初始化,因此你要建立這個類的一個新的實例而後返回一個指針指向它。
下一次你調用sharedInstance方法,實例就會立馬返回而不須要任何的初始化,這個邏輯保證任什麼時候候都只有一個實例存在。
你將要實現這和模式經過建立一個單例的類去管理全部的album 數據。
你將會意識到在這個組中有一個叫作API的租在這個工程中,這是你將會將全部的提供給你的app服務的類放到其中。在這個組中建立一個叫作LibraryAPI的類,繼承於NSObject。
打開LibraryAPI.h,加入如下方法:
在LibraryAPI.m中加入這些方法:
這個簡單的方法中有不少內容:
1. 定義了一個靜態變量去hold住類中的實例,確保它是全局可用的在你的類中。
2. 定義了一個靜態變量dispatch_once_t類型的去確保這個初始化代碼只執行一次。
3. 使用Grand Central Dispatch(GCD)去執行一個初始化LibraryAPI的實例的block,這就是單例設計模式的本質:這個初始化永遠不會再次被調用一旦這個類被實例化。
下次你調用sharedInstance方法是,這個dispatch_once裏面的block代碼不會被執行了,你會獲得一個先前建立這個LibraryAPI的一個引用。
如今你有了一個單例對象做爲albums的入口指針,更進一步去建立一個類去處理庫中數據的持久化。
在API組中建立一個PersistencyManager的繼承於NSObject的類。打開它的.h文件,加入
#import 「Album.h」再加入如下代碼:
以上是加入了三個方法去處理數據的。
打開.m文件在 @implemetation的上方加入這些代碼:
上面的代碼添加了一個類擴展,這是另外一個添加私有變量和方法帶類中因此外部的類並不僅帶他們,在這裏,你聲明瞭一個可變數組去hold住album的數據,這個數據是可變的以便你能夠輕鬆地添加和刪除專輯。
如今添加下面代碼到PersistencyManager.m中去
在init這個初始化函數中你用五個實例轉會填充了這個數組,其餘幾個方法容許你去獲得,添加額刪除albums。編譯你的工程確保仍能正確編譯。
在這個時候你可能會疑問爲何PersistenceManager不是單例,它和LibraryAPI的關係會下下一部分的外觀(Façade)設計模式中見到。
外觀設計模式向複雜的子系統提供了簡單的接口,相比將一系列的類和他們的接口暴露給用戶,你只須要暴露一些簡單的未定義的API。
接下來的圖片解釋了這一律念。
使用這些API接口的人徹底沒有意識到你這下面隱藏的複雜性,在有一系列類,特別是他們使用很複雜或者難以理解的時候,這個模式是很是好的。
外觀設計模式使用從接口層面去使用,在實現技術上隱藏而將代碼解藕了。它也減小了你外部的代碼對於內部子系統代碼的依賴性。它在外觀模式可能要進行改變的狀況下也是頗有用的,由於外觀的類仍然能夠保持相同的API當背後的狀況發生了變化時。好比,有一天你想改變背後的服務代碼,你不用去改變這些代碼由於這些API不會改變。
目前你有PersistencyManager類去本地保存album的數據,而HTTPClient去處理遠程的數據交流。工程裏面的其餘代碼不該該意識到這個邏輯。
要實現這個LiabraryAPI你應該hold住PersistencyManager和HTTPClient的一個實例。而後LiabraryAPI會暴露一個簡單的接口去訪問這些服務。
小貼士:一般一個單例會在app的整個生命週期都會存在,你不該該持有過多的單例指針指向其餘物體,由於它們在app關閉以前不會被釋放。這個設計應該是像下面的這個圖這樣。
LiabraryAPI 會暴露給其餘代碼,可是會隱藏PersistenceManager和HTTPClient針的複雜性。
打開LiabraryAPI.h,添加#import 「album.h」,接下來添加這些方法的人聲明到裏面。
從如今開始,這些方法將會被你暴露給外部使用。
打開LiabraryAPI.m文件。加入
這是你導入這些類惟一的地方,記住:你的複雜的系統只能由你的API惟一的訪問。如今,添加這些私有變量經過類擴展。(在@implementation上方)。
isOnline決定了服務器石油應該根據album表中的變化進行更新,例如添加或者刪除albums.
你須要在init裏面對它們進行初始化。在LiabraryAPI.m文件中添加下面代碼:
HTTPClient這個並不會真正的和一個服務器配合工做,在這裏只是爲了演示外觀設計模式,因此isOnline老是NO。接下來在LiabraryAPI.m文件中添加這些方法。
看一下這個
方法,這個類首先本地更新數據,而後若是由網絡鏈接,就遠程更新,這就是外觀模式的魅力,若是在你係統之外的類加入了一些新的album,它不知道,也不須要去知道,這些複雜性都被隱藏在下面了。
提醒:當爲你的子系統中的類設計一個外觀模式,記住沒有什麼作什麼去阻止客戶端去直接訪問這些隱藏的屬性,不要假設全部的外部客戶端都須要向你在外觀模式下使用它們的方法那樣去使用它們。
編譯和運行你的程序,你會看見以下的空白的黑色屏幕。
你須要作點事情去在屏幕上去顯示album的數據-那就是接下來的下一個設計模式:裝飾設計模式。Decorator.
裝飾設計模式動態的添加一些行爲和任務到一個對象中且不須要去修改它的代碼。固然你也能夠選擇用繼承的方式-經過包裝成另外一個對象去改變它的行爲。
在objective-c中由兩個很是經常使用的實現方式:分類和代理。(Category, Delegate)
分類是一種很是有用的機制,它容許你去添加一些方法到已經存在的類中且不用去繼承它。這些新方法會在編譯的時候添加上去,且能夠像這個被擴展的類中的其餘方法同樣被執行。它和典型的裝飾設計模式由一點輕微的不一樣,由於它並不hold住它所擴展的類的實例。
小貼士:除了向你本身的類去添加方法,你還能夠向cocoa中的類去添加方法。
想像這是你想要在tableView中顯示album數據的一種方案。
這些album titles從哪裏來?Album是一個模型對象,因此它並不在乎你是怎麼樣顯示數據的,你將要須要一些額外的代碼去將這些功能添加到Album類中去,可是不要直接去修改這些類。
你將要建立一個分類去擴展Album。它要定義一個新的方法去返回一個數據結構來被UITableViews輕鬆的使用。這些數據結構看起來應該是這樣的。
爲了添加一個分類到Album中,新建一個文件選擇Objective-C category 模版,在category field中鍵入TableRepresentation,在Category On上寫入Album。
在Album+TableRepresentation.h加入如下方法的聲明。
注意到這裏有一個tr_在方法名的前面,就像一個分類的名字的前綴。這樣的命名約定有利於防止和其餘方法衝突。
注意:若是在分類中聲明的方法名字和在原來的類中的方法名字同樣,或者和另外一個類擴展中的方法名字同樣,那麼就會顯示未定義,由於這些方法的實現是在運行時,若是你用分類去擴展你本身定義的類,那麼出現問題的機率不大,可是若是你用分類添加方法到Cocoa或者CocoaTouch的類中,那有可能會產生很嚴重的問題。
在Album+TableRepresentation.m添加下面的方法
想一下這個設計模式有多強大:
1你能夠直接使用Album中的屬性。
2 你能夠不繼承就能夠向一個類添加方法。固然若是你想繼承的話,也能夠繼承。
3 它能夠簡單的讓你返回一個UITableView-ish的Album的顯示類型,而不用修改Album的代碼。
蘋果公司使用分類在不少在基礎的類上。要想去看他們是怎樣實現的,能夠打開NSString.h,找到@interface NSString ,那你將會看到這個類的定義和一下三個分類牢牢聯繫在一塊兒:
NSStirngExtensionMethods, NSExtendedStringPropertyListParsing,NSStringDeprecated。分類把這些方法有序的組織起來而又分紅幾部分。
另外一個裝飾設計模式就是代理,就是一個對象能夠表明或者協助另外一個對象的一種機制。例如,當你使用UITableView,其中你必須實現的方法就是tableView:number numberOfRowsInSection.
你不能期望UITableView去知道你想在每個分區裏面有多少行。所以,計算每一個分區有多少行的任務就交給了UITableView的代理。這讓UITableView能夠和它要顯示的數據進行獨立
UITableView 對象的工做是顯示一個table view,而後最終它仍是須要一些它根本沒有擁有的數據。那麼,它會向它的代理去發送一條消息去請求一些額外的信息。在Objective-C中的代理設計模式的實現中,一個類能夠經過協議protocol聲明必須的required或者可選的optional的方法。你將會實現這些協議在這個教程的後半部分。
彷佛看起來經過繼承一個對象而後去重寫它的須要的方法更容易,凡事考慮到你你只能繼承一個類。若是你向一個對象成爲兩個或者更多對象的代理,那你不能經過繼承去實現這個目標。
小貼士:這是一個很重要的模式,蘋果公司使用這個方法在大部分的UIKit的類中:UITableView,UITextView,UITextField,UIWebView,UIAlert,UIAction,UICollectionView,UIPickerView,UIGestureRecognizer,UIScrollerView等等。
在ViewController.m中加入導入這些文件的頭文件。
利用類擴展去添加這些私有變量。
而後在@interfaceViewController ()加上 <UITableViewDataSource,UITableViewDelegate>
這是你怎樣使你的代理聽從一個協議-想像它是一個被代理去完成方法的協議的一個約定,在這裏你讓ViewController去遵照UITableViewDataSource和UITableViewDelegate協議,這個方法使得UITableView能夠絕對保證這些必須方法會被它的代理所實現。
接下來,在viewDidLoad中加入這些代碼:
這裏是這些代碼的講解:
1首先將背景顏色改爲了一個相對友好的背景色。
2經過API而不是PersistencyManager獲得了albums的列表。
3在這裏建立了UITableView,你聲明瞭這個控制器是UITableView的delegate/dataSource,所以UITableView的全部必須的方法都會由控制器提供。
而後添加這個方法在控制器實現代碼中:
showDataForAlbumAtIndex:從albums這個數組中獲取了所需答album的數據。而後你之須要去調用reloadData.這會讓UITableView詢問它的代理諸如在table view的每一個分區中顯示多少行,多少個分區,每一行應該怎麼樣之類的事情。
在viewDidLoad的結尾處加上[self showDataForAlbumAtIndex:currentAlbumIndex];
這會在應用啓動的時候load如今的album,而後由於currentAlbumIndex原先被初始化爲0,因此只是在會顯示第一個album。
編譯運行你的工程,你會遇到一個崩潰伴隨一個異常的顯示在調試控制檯。
這怎麼了?由於你聲明瞭控制器成爲UITableView的delegate和dataSource,可是這樣的話你必須遵照去實現它的必須的方法包括numberOfRowsInSection這個你還沒實現的方法。向ViewController.m中加入這兩個方法。
前一個方法返回在table view中要顯示的行數,在這裏和數據結構中的titles的數量相同。後者建立並返回了一個帶有title和value的cell。
編譯和運行工程,你的應用應該是向這樣展現在你的面前。
到目前爲止,事情好像看起老很棒,可是你若是你調用第一張圖片去展現啓動後的app,那將會由一個水平的滾動條在屏幕的albums轉換之間的頂部。與其建立一個單一用途的水平滾動條,爲何不使它成爲一個通用的視圖。
爲了是這個視圖可重用,全部的關於它的內容的決定都該留給另外一個對象-它的代理。這個horizontal scroller應該定義一些方法讓它的代理去實現以致於去和scroller一塊兒工做,和UITableViewde的代理方法想相似。咱們將會在下一個設計模式中去討論這個設計模式。
適配器模式讓不一樣的類之間的不兼容的接口能夠一塊兒工做。它將本身包裝成一個對象,而後暴露一個標準的接口去讓外界和這個對象去交互。
若是你對適配器模式熟悉,那麼你會注意到蘋果用一個稍微不一樣的方法去實現它-蘋果使用協議去作這個工做,你也許會熟悉像UITableViewDelegate,UIScrollViewDelegate,NSCoding,NSCopying這樣的協議,例如,經過NSCopying協議,任何的類均可以提供一個標準的copy方法。
這個之間提到的horizontal scroller應該是像下面的這個圖這樣子。
首先新建一個Objective-C的類,讓它繼承於UIView,打開它的.h文件,在@end的下面寫上這行代碼。
這定義了一個名爲HorizontalScrollerDelegate的協議,繼承於 NSObject協議。這是一個很好的實踐去聽從NSObject協議-或者去聽從一個已經聽從NSObject協議的協議,這會讓你能夠向HorizontalScroller的代理對象發送NSObject中定義的消息。你將會看到這爲何是那麼的重要。
你要定義它的代理必須和可選實現的方法在@protocol和@end之間
在這裏你既有可選的也有必須的方法,必須的方法必須被代理實現,而一般這回包含一些這個類絕對須要的數據。在這個例子中,這些必須的分別是視圖的數量,在特定的位置的視圖和當一個視圖被點擊後的行爲。這個可選的方法是初始化的使用,若是它不被代理所實現,那麼默認就是第一個視圖。
接下來,你須要將這個類的定義飲用到你的代理中去。可是這個協議的定義是在累的定義之下的,所以還剩不可見的,那你該怎麼辦呢。
解決的辦法奇偶說前向的定義一個協議來讓編譯器去知道這樣一個協議是可用的,因此,添加這行代碼在@interface的上方。
而後在@interface和@end之間下入以下代碼。
這個代理的屬性是weak類型的,爲了防止循環引用這是必須的,若是一個類持有一個強指針指向它的代理,而它的代理也持有一個強的指針指向它,那麼你的應用會由於任何一個類都不能彼此釋放內存而形成內存泄漏。而id類型表示你只能夠成爲遵照HorizontalScrollerDelefate的對象的assign方,給了你必定程度上的類型安全。
這個reload方法是一個在UITableView以後被從新刷新了,它reload了全部的用於構建horizontal scroller的數據。
用下面的代碼體大地HorizontalScroller.m中全部的代碼。
看一下這些註釋:
1. 定義了一些常量去使更容易在設計時修改佈局。視圖在這個scroller中的面積是100*100有一個和它相近的矩形有一個10。
2. HorizontalScroller遵照<UIScrollViewDelegate>,這是由於HorizontalScroller適應一個UIScrollView去滾動album,它須要去知道用戶的行爲好比用戶中止了滾動。
3. 建立了一個scroll view容器。
下一步你須要去實現這個初始化方法,添加下面這個方法。
這個scroll view徹底填充了這個HorizontalScroller,一個UITapGestureRecognizer檢測有沒有在這個scroll view上有觸摸行爲和檢查一個album cover是否被點擊。若是有的話,它會通知HorizontalScroller的代理。加入這個方法。
手勢被當成locationInView的一個參數去傳遞讓你精確的知道位置,接下來你喚醒了它的代理的numberOfviewsForHorizontalScroller方法,這個HorizontalScroller的實例除了知道它能夠安全的向一個遵照了HorizontalScrollerDelegate的對象發送方法之外其餘的一無所知。對於在uiscroll view中的每個視圖,用CGRectContainsPoint方法去找出被點擊的視圖。當這個視圖找到了之後,向它的代理髮送clickViewAtIndex消息。在不跳出這個循環以前,將這個被點擊的視圖居中。添加下面這個方法去reload這個scroller。
經過逐行註釋來看看這些代碼。
1. 若是沒用代理,那麼沒用什麼能夠作的事情,那你能夠直接返回了。
2. 將原先添加到scroll view中的字視圖所有移除掉。
3. 全部的視圖都在一個給丁的距離開始,如今它是100,可是能夠輕鬆的經過改變上面那個定義的常量去改變,
4. HorizontalScroller每次詢問它的代理讓它們彼此水平的挨着在一塊。
5. 一旦全部的視圖都安置好了,設置這個滾動視圖的contenOffset讓用戶能夠滾動全部的album covers。
6. 這個HorizontalScroller檢查它的代理是否響應initialViewIndexForHorizontalScroller這個方法,若是響應的話這個代碼就會將這個滾動視圖放在它的代理定義的初始視圖的中心,不然默認的就是0
當你的數據發生了改變之後你要執行reload操做,你也能夠執行調用這個方法在你將HorizontalScroller添加到其餘的視圖上的時候。加入一下代碼到HorizontalScroller.m文件中,
didMoveToSuperview這個消息當一個視圖要添加到另外一個視圖上做爲一個子視圖上時被調用。這個時候就是reload scroller的內容的正確的時機了。HorizontalScroller的最後一個難題時確保你正在看見的album老是在scroll view的正中間。所以大家要去實現一些計算當用戶用手指拖動這個scroll view時。
將這個代碼添加到HorizontalScroller.m中
上面這段代碼計算當前的scroll view的騙一直和麪積和視圖的padding去計算當前的視圖離中心的距離。最後一行是很重要的,一旦這個視圖移動到中心了,通知它的代理這個顯示的視圖被改變了。
爲了檢測用戶在scroll view的拖動,你必須添加以下的UIScrollViewDelegate方法:
scrollViewDidEndDragging: willDecelerate:會通知它的代理當用戶結束拖動的時候。若是用戶尚未結束拖動那麼這個decelerate參數就爲真。當用戶結束拖動,那麼系統就會調用前面那個方法。兩個方法中咱們都調用了新的方法去讓當前的視圖居中由於這個當前的視圖已經在用戶拖動後發生了改變。
如今這個HorizontalScroller已經準備好使用了。回顧一下你剛纔所寫的代碼,一點都沒有說起到album和albumView這些類,這是很是好的,由於這意味着你的代碼是獨立的和可重用的。
如今編譯使工程確保沒事。
如今HorizontalScroller已經準備好了,是時候去使用它了。打開viewController.m加入以下的代碼:
添加HorizontalScrollerDelegate,使
添加下面的這個實例變量到它的類擴展中。 HorizontalScroller *scroller;
如今你能夠實現這些代理方法了,你將會驚訝只用幾行代碼就能夠實現不少的功能。
添加下面的代碼到ViewController.m中
這裏設置了這些變量去存儲暫時的album而後調用showDataForAlbumAtIndex方法去顯示一個下那的album的數據。
小貼士:將一些方法放在一塊兒經過@pragma mark是一種很好的習慣。編譯器會忽略這一行可是你會在你的Xcode的jump bar中將這些方法列起來。這會幫助你在Xcode中更好的組織代碼。而後添加下面的代碼
這就是像你能辨認出來的那樣,這是協議方法返回在scroll view中的視圖的個數。由於摺合scorll view爲全部的album 的數據顯示covers ,這個count就是album記錄的數據。接下來添加這個方法:
在這裏你建立了一個新的AlbumView而後將它傳遞給了horizontalScroller.
就是這麼多,你只用了三個很簡短的方法就顯示了一個很好看的horizontalScroller。
是的,你仍要去真正的建立一個scroller而後將它添加到你的主要的view中可是在坐這個以前。添加這個方法:
這個方法loads album的數據經過LibraryAPI而後設置當前啊的現實的視圖在機遇當前的視圖的index的值上,若是當前的view index 小於0,那麼意味着沒有當前的視圖選擇,那就將第一張album顯示,不然就是最後一張album被顯示。
如今初始化這個scroller經過添加下面的代碼到你的viewController.m中
上面的僅僅是建立了一個新的HorizontalScroller實例,添加到main view中,而後loads全部的子視圖去顯示album的數據。
小貼士:若是一個協議變得很大,而後有不少的方法,那麼你應該考慮將它分解成幾個小的協議,UITableViewDelegate和UITableViewDataSource就是一個很是好的例子。嘗試着去設計你的戲而已讓每個控制一個具體的功能。
編譯和建立米的工程,看一下你的這個很棒的新的horizontal scroller:以下圖:
等下,這個horizontal scroller是在裏面了,可是這個album cover在哪裏呢?
額,那九堆了-你尚未去實現下載cover的代碼。因此你將要去添加一個下載圖片的方法,計算你的因此訪問的服務都是經過LibraryAPI,那麼那裏就是你放這些方法的地方,而後,首先還要考慮如下幾個問題:
1 AlbumView不該該直接和LibraryAPI去搭配工做,你不要去將視圖邏輯和交流邏輯混合起來.
2 相同的緣由,LibraryAPI也不應知道有關AlbumView的事情。
3 LibraryAPI應該去通知AlbumView一旦這些covers已經下載好了由於AlbumView要去顯示它們。
聽起來像個謎,不要回信,你將會學到怎麼樣去使用Observer模式.
在觀察者模式中,一個對象將會通知其餘對象的任何狀態的改變。這些相關的對象並不須要去知道另外一個對象-這樣就形成了一個非耦合的設計。這個模式大部分用在去通知一個感興趣的對象它的一個屬性已經發生了改變。
通常的實現須要一個對象註冊成爲它感興趣的狀態的觀察者,當這個狀態改變了,全部的觀察者對象都會接收到通知。蘋果的Push Notification服務就是對這個最好的例子。
若是你想要堅持MVC設計模式的概念,你須要去容許Model對象去和View對象交流,可是它們之間並無直接的引用,這就是觀察站模式引入的緣由。
Cocoa實現觀察者模式有兩種經常使用的方法:Notification和Key-Value-Observing(KVO)
不要和和push或者本地的通知相混淆,Notifications是基於一個訂閱-分發的模型去容許一個對象發送一些消息給其餘對象。這個對象不須要去知道關於訂閱者的任何信息。Notifications被蘋果公司用的不少。例如,當鍵盤顯示或者隱藏時,系統將會發送一個UIKeyboardWillShowNotification/UIKeyboardWillHideNotification,響應的當你進入到後臺,系統將會發送一個UIApplicationDidEnterBackgroundNotification的通知.
提醒:打開UIApplication.h在文件的結尾你會看到一系列超過20條系統發送的通知。
打開AlbumView.m加入下面的代碼到[self addSubView:indicator]後面:
這一行經過NSNotificationCenter單例發送一個通知,這個通知的info裏面包括了UIImageView去計算和要下載的cover image的URL,這就是所有的你須要去實現下載任務的信息。
添加下面的代碼到LibraryAPI的init方法中,直接在isOnline = No的後面。
這就是方程式的另外一端,觀察者。每次一個AlbumView類發送一個DownloadImageNotification的通知的是很好,由於Library已經註冊成爲它的一個觀察者,因此係統會通知它,那麼它就會相應的去執行downloadImage。
然而,在你實現downloadImage以前,你必須記住當你的類deallocated的時候去取消觀察者的狀態。不然的話,一個通知可能會被髮送給一個已經被deallocated的對象,那麼就會致使app的崩潰。
添加下面的方法到Library中:
當這個類結束後,它會移除它全部的通知中的觀察者狀態。
還有一件事要去作。若是你將下載的covers保存起就是一個好主意,由於咱們不用一次又一次的去下載相同的covers。打開PersistencyManager.h而後添加下面的這兩個方法的原型:
而後將它們的實現代碼添加到.m文件中:
這幾個代碼是十分簡潔的,它下載的圖片將會被保存在Documents目錄下,而後getImage:將會是nil若是在Documents目錄下一個匹配的文件都沒有。
而後添加下面的方法到Library.m中:
這是上面代碼的講解:
1. downloadImage會經過通知被執行,因此這個方法以通知爲參數,UIImageView和image URL從通知中獲得。
2. 若是先前已經下載的話那麼就從PersistencyManager中去獲取圖片
3. 若是沒下載的話就經過HTTPClient去下載。
4. 當下載結束,在uiimage view 上顯示圖片,再用manager去將它在本地保存。
你又一次使用外觀設計模式去隱藏了下載圖片的複雜性,這個nofitifation根本不會去關心這個圖片是經過網頁下載的仍是本地得到的。
編譯和運行你的app,你將會看到下面這個美麗的covers在你的HorizentalScroller中。
中止你的app而後再次運行,只一道沒有任何的延時在加載covers的時候,你能夠斷開網絡而後你的應用仍是運行的完美無瑕。然而你會發現,這個spinning並無中止運行。這是怎麼回事?
你在開始下載的時候開始啓動這個spingning,可是你並無實如今圖片開始下載無缺的時候去中止它的邏輯。你能夠在每次圖片已經下載完的時候發送一個notification,可是另外你可使用另外一個觀察者模式-KVO。
在KVO中,一個對象能夠請求去在一個具體的屬性開始變化的時候獲得它的一個通知,不論這個屬性屬於它本身仍是另外一個對象。在這個例子裏面,你可使用KVO去觀察加載image的UIImageView中的這個image屬性的改變。
你們AlbumView.m,添加下面這個代碼到initWIthFrame:albumCover:中,添加在[selfaddSubview:indicator]以後。
這添加了self,也當前的類成爲coverImage的image屬性的一個觀察者。
你也須要吧去在你結束的時候取消成爲觀察者.仍在AlbumView.m中添加下面的代碼:
最後,添加這個方法:
你必須在每一個你做爲一個觀察者的類裏面吧去實現這個方法。每次當被觀察的屬性變化時系統就會執行這個方法,在上面的方法中當image屬性改變的時候就會調用這個方法。就這樣,當一個圖片下載完成,這個spinning就會中止.
編譯運行你的工程,這個spinnning 就會消失。
小貼士:必定要記得去remove你的observers在它們deallocated時,不然當這個對象視圖像一個不存在的觀察者發送一個消息時那麼你的app就會崩潰掉。
若是你運行如下app而後滾動一下這個covers,以後中止運行它,你會注意到app的狀態並無保存下來。你看到的最後一個視圖並無在應用再次啓動的時候成爲默認的設置。
要改正這一點,你能夠充分的利用下一個設計模式:Memento設計模式(備忘錄模式)
備忘錄設計模式將一個對象的內部狀態進行捕捉並外部化,換句話說就是你將你的東西保存在某個地方。之後這個外部話的轉檯不須要藉助封裝就能夠被回覆,也就是私有的數據仍是私有的。
接下來將下面兩個方法添加在ViewController.m中
saveCurrentState將當前的album的index保存到NSUserDefaults,NSUserDefaults是一個iOS爲了保存應用的具體的設置和數據而提供的一個標準的數據存儲的類。
loadPreviousState加載了先前保存的index這並非完整的備忘錄設計模式,可是你如今達到了目的。
如今添加下面這行代碼到ViewController的viewDidLoad中的在scroller被初始化的代碼以前。
[self loadPreviousState];
這會在app啓動的時候加載先前被保存的狀態。可是你在那裏保存從後臺回來的的app的狀態。你要使用通知去完成這個任務。iOS在app被放到後臺的時候會發送一個UIApplicationDidEnterBackgroundNotification的通知,你可使用這個通知去調用saveCurrentState,是否是很方便啊?
加入下面的這行代碼到ViewDidLoad的結尾。
[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(saveCurrentState)name:UIApplicationDidEnterBackgroundNotificationobject:nil];
當app將要被掛起到後臺的時候,ViewController就會自動地調用saveCurrentState去保存狀態。
如今添加下面的代碼:
這會保證當ViewController被deallocate時候將這個累中從觀察者去移去。
編譯運行你的應用,拖動到某一張album,使用Command+shift+H去將app設置到後臺,而後關閉你的app,從新啓動,看看原先你拖動到的那張圖片是否是設置在中間。
看起來這個album的數據是對的嘛,可是這個scroller並無將正確的album設置在中心,這是怎麼回事?
這是由於這個可選的方法: initialViewIndexForHorizontalScroller的意義所在,由於你沒有實現這個方法,因此它老是被設置在默認的第一張。
去修正它,添加下面的代碼到ViewController中:
如今這個HorizontalScroller的第一張數一被設置成表示album位置的currentAlbumIndex,這是一個很好的方法去確保這個app保持我的化和可重用的雙重便利。
再次運行你的app,滾動一個 app
,中止app,再啓動,確保問題已經被解決。
若是你看看你的PersistencyManager的init方法,你會注意到這個album的數據被寫死了,而每次當PersistencyManager被建立的時候就被從新建立,可是是否是隻建立一次albums的列表而後將它們保存在一個文件中更好呢?可是你怎麼將你的Album數據保存再一個文件中呢?
一個選擇就是使用Album的property屬性,將它們保存再一個plist文件中而後當須要的時候從新去建立Album實例,這不是最好的選擇,由於它須要你去寫一個具體的依賴於再每個類中的數據或者properties的代碼,例如說若是你後來建立了一個具備不一樣的屬性的Movie的類,那保存和從新讀取數據將要一個新的代碼去完成。
此外,你不能將一個私有變量保存在每個類的實例中由於它們是不能被外界所訪問的。這就是蘋果公司建立這個Achiving機制的緣由。
Achiving是蘋果公司具體實現備忘錄模式之一。它將一個對象轉換成一個能夠被保存後面能夠回覆可是不須要將私有變量暴露給外部類。
首先你須要去經過遵照NSCoding這個協議代表這個Album類能夠被壓縮打開Album.h而後將它變成遵照這個協議:
@interface Album :NSObject <NSCoding>
而後添加下面的代碼到Album.m中:
你調用encodeWithCoder當你壓縮一個類的實例變量的時候,相反你調用initWithCoder當你解壓一個實例去建立一個Album的實例的時候,很簡單卻很強大。
如今這個Album類能夠被壓縮那就添加時機保存和重載albums的列表的類。
添加下面的方法聲明到你的PersistencyManager.h中:
-(void)saveAlbums;
這將會在調用保存albums數據的時候被調用,下面添加這個實現到PersistencyManager.m中
NSKeyedArchiver黃這個album數組壓縮到一個albums.bin的文件中。當你u壓縮一個包含其餘對象的對象時,那麼這個Archiver會自動的嘗試去遞歸地壓縮子對象和子對象的子對象等等。在這個實例中,這個archival開始於albums-這時一個Album各類實例的數組,由於NSArray和Album都支持NSCoping接口,因此這個數組被自動的壓縮了。
如今替換PersistencyManager.m文件中的init文件。
在這個新的代碼中NSKeyedUnarchiver從文件中加載這個album的數據,若是存在的話,若是不存在,它就會建立一個album數據而後馬上保存它讓下次啓動app的時候使用。
你也想要去在每次app進入到後臺的時候保存album的數據,這彷佛不須要可是若是後面你想要添加一個去改變album數據的選擇呢?而後你想要去確保全部的變化都被保存了的。添加下面的方法聲明到LibraryAPI.h中;
-(void)saveAlbums;
既然主要的application訪問全部的服務都是經過LibraryAPI,這就是應用怎麼樣讓PersistencyManager去知道它是否須要保存數據,如今添加下面的方法實現到LibraryAPI.m中。
這個代碼僅僅傳遞了一個去讓PersistencyManager去保持albums的一個調用。
而後添加下面這行代碼到ViewController.m的saveCurrentState 中
上面的代碼使用LibraryAPI去當ViewCOntroller想要去保存數據的時候觸發保存album數據的方法。
如今變異你的app去確保編譯經過。
不幸的是,沒有什麼簡單的方法去檢查數據的固話是否徹底正確,你能夠在模擬器的Mocunments文件夾下面去檢查album數據文件是建立的,可是爲了企業看到溫和的改變你須要去添加一個能夠去改變album數據的草種。
與其去改變數據,若是你添加一個去刪除你不想讓它存在在albums中的一個album的選擇項的話是否是更好?此外添加一個撤銷的操做以避免被誤刪除呢?
這就提供了一個很好的機會去介紹最後一個設計模式:命令模式
這個設計模式將對象封裝成了一個請求獲取操做,這個封裝請求比一個原始的請求更加的靈活,且能夠在對象之間傳遞,稍後存儲,動態修改獲取放到一個隊列之中。蘋果公司是用Target-action機制和Invocation實現的,你能夠在蘋果的官方文檔中去知道更多的關於Target-Action,可是Invocation使用包含一個Target對象的NSInvocation類,一個方法選擇器和一些參數。這個對象能夠根據須要動態的改變和執行,在命令模式中這是一個完美的例子,它解除耦合了發送對象和接收對象,且能夠持續的堅持一個或者一系列請求。
早莫深刻動做的invocation以前,你須要去設置撤銷(undo action)動做的框架,因此你必須定義一個UIToolBar 和undo stack(撤銷棧)上所須要的可變數組。
在ViewController.m的擴展中鍵入這些代碼:
這建立了一個爲了這些操做而添加顯示的toolbar,還要一個數組去去做爲命令隊列。
添加下面的代碼到ViewDidLoad的結尾:
上面的代碼建立了一個toolbar,它有兩個按鈕和一個可變的空格。這個撤銷按鈕被禁止了由於這個undo stack一開始是空的。
一樣,由於這個toolbar沒有根據frame來初始化,因此這個在viewDidload中frame的大小尚未設置。因此經過下面的一些代碼在一旦視圖的frame被最終設置好了之後設置這個frame。
你將會添加下面的三個方法到ViewController中去處理album的三個管理操做:添加,刪除和撤銷。
第一個方法是增長一個新的album:
在這裏你添加了一個album,設置它的當前的album的index,而後從新加載scroller.接下來刪除方法:
在上面的這些代碼裏面有一些新的有趣的特性,因此解析一下:
1. 得到要刪除的album,
2. 定義一個NSMethodSignature類型去建立一個NSInvocation,它將用於去在用戶決定撤銷一個刪除時作和刪除相反的操做。這個NSInovation須要知道三個事情:The selector(發送的消息),目標:((發送給誰)和發送的消息的一些參數。在這個例子裏,消息時發送給刪除的相反方,由於當你撤銷一個刪除操做,你須要將它們從新添加這個刪除的album。、
3. 當這個undoAction被建立後你將它添加到undoStack中,這個動做將會被添加到一個數組的末尾,正像一個普通的堆棧同樣。
4. 使用LibraryAPI去從數據結構中去刪除album而後從新加載scroller。
5. 由於在undoStack中有了一個動做,因此你須要去將undo按鈕使能。
注意:當你使用NSInvocation,你須要去記住下面的三個點:
1. 參數必須經過指針來傳遞。
2.參數起始於index 2,由於0和1是保留給target 和action用的。
3. 若是這些參數有可能被銷燬(dealocate),你應該使用retainArguments.
最後添加下面的代碼用於撤銷:
這個撤銷操做會pops堆棧中的最後一個對象。這個對象永遠是NSInvocation類型的並且能夠經過調用invoke被激活。這回激活你你先前在album被刪除時建立的命令,而後添加背刪除的album到album列表中。由於你也能夠刪除在你撤銷後最後一個對象,因此你須要判斷這個棧是否時空的,若是是的話那麼將undo按鈕禁止交互。編譯和運行你的app去特使這個undo 機制。刪除這個album而後點擊撤銷操做去看看效果。
還有兩種設置模式沒用將它們應用在這個app中,可是也是很重要的:抽象工廠模式和責任鏈模式。但官方文檔去擴展你的設計模式水平
在這個教程你已經看到怎麼樣去將發揮ios的設計模式的威力用很簡單和耦合性地的方式去處理很複雜的任務。你已經學習了不少關於ios設計模式的概念:MVC, 但裏,代理,協議,外觀,貫徹着,備忘錄,命令。
你最後的代碼是耦合性很低的,可重用的且可讀性該,若是其餘開飯看你的代碼它會馬上明白這是怎麼回事和每一個類的做用。
關鍵不是使用設計模式去寫你的每一行代碼而是當你在解決一個困難的問題特別是在設計應用的早期的時候意識到使用什麼設計模式。它會讓你的開發工做更容易,代碼更高效。
此博文來自:http://blog.csdn.net/sanjunsheng/article/details/38071787。寫的很好做爲收藏。