相信做App開發的同學,對于一些第三方的統計分析、錯誤收集等SDK應該都不陌生。就目前而言市面上也有許多相同功能的產品,眼花繚亂,讓人無法抉擇選哪一款SDK才是最靠譜的。那就隨便先選一款試試用吧!
那么問題來了:如果項目都快做完了結果發現這款SDK實在坑爹,不僅擴展性差,還經常讓App Crash,那你是不是會想到替換掉這個SDK?
OK,那我們就換另一個試試,下載SDK下來,一看,傻眼了,設計風格,封裝模塊完全不一樣,于是乎我們就到項目中全局搜索找到之前的SDK代碼干掉,然后重新再到各種地方用新的SDK來寫新的邏輯來替換,關鍵的是,中間還不知道會產生多少bug,漏掉多少未修改的代碼,總之始終會有一種不靠譜的感覺。
換一次還算好的,如果之后團隊壯大了,這些數據分析之類的東西突然想自己做了,畢竟這些有價值的數據并不想這么拱手讓給一個第三方的公司嘛~這個時候你是不是就只想說:『呵呵』
所以這個時候適配器模式就起到作用了~
何為適配器模式
GoF對于適配器模式的解釋如下:
將一個類的接口轉換成客戶希望的另外一個接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以在一起工作。
個人通俗理解:
適配器:顧名思義,將不兼容的轉換為兼容,如電源適配器,將全世界各種不相同的電壓轉換成相同的電壓輸出給目標設備。
這里可以將目標設備理解為『接口』,世界各種電壓可以理解為『產生相同功能的類』,電源適配器可以理解為『需要實現的適配器類』。
適配器模式產生的效果是:在不修改代碼或者修改極少代碼的情況下,快速的切換源(數據源、內容源等)。
就像電源適配器一樣,去到不同國家,同一個設備只需要不同的電源適配器就可以使用當前國家的電源,而不需要取拆卸機器。
使用真實場景
如文章開頭所講,被某盟的SDK坑了之后(確實在某些狀況下讓App Crash,產生原因初步判斷是濫用performSelector,不考慮對象被釋放的情況而產生的Crash),產生替換念想而思考,如果將來替換豈不是又要苦逼我們自己?
于是乎為了將來的輕松就必須動動腦子去設計代碼了,于是有了今天的適配器模式實戰。
如何使用適配器模式
一個適配器允許接口不兼容的類在一起工作。它把它自己包裹成一個對象,公開一個與這個對象相互作用的標準接口。
如果你熟習適配器模式,你會注意到蘋果實施它的時候有一點不同的習慣─蘋果使用協議 (protocols)。你可能熟習像 UITableViewDelegate, UIScrollViewDelegate, NSCoding 和 NSCopying 這樣的協議。例子,NSCopying 的協議 (protocol),任何類都可以提供這樣一個標準的復制方法。
我們提到的滾動區域是這樣的:
現在開始,在項目導航的 View 文件夾上右擊鼠標,選擇 New File…,用 iOS/Cocoa Touch/Object-C class 模板創建一個新類。新類的名字叫 HorizontalScroller,選擇它的子類為 UIView。
打開 HorizontalScroller.h 文件在 @end 后面插入如下代碼:
定義個代理執行的方法,要在 @protocol 和 @end 之間,它們分為必要方法和可選方法。添加下面協議方法:
// 返回索引是 index 的視圖
- (UIView*)horizontalScroller:(HorizontalScroller*)scroller viewAtIndex:(int)index;
// 當索引是 index 的視圖被點擊了,通知 delegate
- (void)horizontalScroller:(HorizontalScroller*)scroller clickedViewAtIndex:(int)index;
@optional
// 通知 delegate,顯示初始化時索引是 Index 的視圖。這個方法是可選的
// ask the delegate for the index of the initial view to display. this method is optional
// 如果沒有被 delegate 執行,默認值是 0
- (NSInteger)initialViewIndexForHorizontalScroller:(HorizontalScroller*)scroller;
接下來,你需要在 HorizontalScroller 內部定義你的新代理。但是協議的定義在類的定義下面,因此在這點上它是不可見的。你該怎么辦?
解決辦法就是在前面聲明協議以便于編譯器(和Xcode)知道這個協議是可用的。好了,在 @interface 上面加入下面代碼:
[/ode]
@protocol HorizontalScrollerDelegate;
[/code]
還是 HorizontalScroller.h,在 @interface 和 @end 之間加入下面代碼:
id 的意思是把這個代理指定給一個類,它遵照 HorizontalScrollerDelegate,給你一些類型安全。
reload 方法是模仿 UITableView 類的 relaodData;它重新加載所有數據用來創建一個水平移動視圖。
用下面代碼替換 HorizontalScroller.m 的內容:
#define VIEW_PADDING 10
#define VIEW_DIMENSIONS 100
#define VIEW_OFFSET 100
@interface HorizontalScroller () <UIScrollViewDelegate>
@end
常量定義,在設計時間可以方便修改布局。在滾動視圖內,每個圖片的大小在一個 100×100 內邊距為 10 點(point) 的矩形內。
HorizontalScroller 遵照 UIScrollViewDelegate 協議。因為 HorizontalScroller 使用一個 UIScrollView 來滾動專輯封面,它需要知道用戶什么時候停止滾動。
創建一個包含圖片的滾動視圖。
接下來你需要執行初始化。添加下面的方法:
現在添加下面方法:
接下來,調用委托的 numberOfViewForHorizontalScroller: 方法。它必須遵照 HorizontalScrollerDelegate 的協議安全發送消息,否則 HorizontalScroller 實例的代理是沒法使用這些信息。
滾動視圖里的每個視圖,用 CGRectContainsPoint 執行一個點擊測試,找到那個被點擊的視圖。當視圖被找到,發送給委托一個消息 horizontalScroller:clickedViewAtIndex:。當你跳出這個循環后,設置被點擊的視圖滾動到視圖中間。
現在添加下面的代碼,用來刷新滾動視圖(scroller):
// 2 - remover all subviews
[scroller.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[obj removeFromSuperview];
}
// 3 - xValue is the starting point of the views inside the scroller
CGFloat xValue = VIEWS_OFFSET;
for (int i=0; i<[self.delegate numberOfViewsForHorizontalScroller:self]; i++) {
// 4 - add a view at the right position
xValue += VIEW_PADDING;
UIView *view = [self.delegate horizontalScroller:self viewAtIndex:i]
view.frame = CGRectMake(xValue, VIEW_PADDING, VIEW_DIMENSIONS, VIEW_DIMENSIONS);
xValue += VIEW_DIMENSIONS + VIEW_PADDING;
}
// 5
[scroller setContentSize:CGSizeMake(xValue+VIEWS_OFFSET, self.frame.size.height)];
// 6 - if an initial view is defined, center the scroller on it
if (self.delegate respondsToSelector:@select(initialViewIndexForHorizontalScroller:)]) {
int initialView = [self.delegate initialViewIndexForHorizontalScroller:self];
[scroller setContentOffset:CGPointMake(initialView*(VIEW_DIMENSIONS+(2*VIEW_PADDING)), 0) animated:YES];
}
}
如果沒有代理,這里什么事情也不做。
移除之前添加的所有的子視圖。
給所有視圖設置一個偏移(offset)位置?,F在的是 100,但是通過頂部的 #define,它很容易修改。
HorizontalScroller 通過它的委托一次請求一個視圖,用之前定義的 padding 值把它們依次的一個個放置下來。
當所有的視圖都生成好,通過設置滾動視圖內容的偏移量以達到用戶能過滾動可以看到所有專輯封面的目的。
HorizontalScroller 的委托需要驗證是否響應了 initialViewIndexForHorizontalScroller: 方法。這個驗證是必需的,因為這個特別的協議方法是可選性的。如果代理沒有執行這個方法,它的默認值會是 0。最終,通過委托,這塊代碼會在滾動視圖中間設置一個初始化好的視圖。
當數據發生改變的時候執行 reload 方法。當添加 HorizontalScroller 到別個一個視圖時,你同樣可以執行這個方法。在 HorizontalScroller.m 添加下面的代碼替換后面的方案:
HorizontalScroller 的最后一個難題就是,如何設置你看到的專輯總是在滾動視圖的中間。為了這些,當用戶通過他們的手指拖動滾動視圖的時候你就需要做一些計算了。
添加下面方法(同樣在 HorizontalScroller.m):
為了偵測用戶在滾動視圖內完成拖拽的動作,你需要添加 UIScrollViewDelegate 方法:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self centerCurrentView];
}
HorizontalScroller 現在可以使用了。瀏覽你剛剛寫的代碼;這里沒有一處提到 Album 和 AlbumView 類。這非常棒,說明這個新的滾動視圖是真正的完全獨立的和可重用的。
Build 項目,確保所有的代碼編譯正確。
現在 HorizontalScroller 完成了,是時候在你的 APP 中使用了。打開 ViewController.m 添加如下引用:
在 ViewController.m 添加如下代碼:
提示:一般在方法代碼的前面放置 #pragma mark 指示符。編譯器會忽略這一行,當你在使用 Xcode 的跳轉工具欄(Xcode's jump bar)查看你的方法列表時,你會看到一個分隔符和個加粗的指示標題。在 Xcode 里,這可以幫助你很容易的組織代碼。
下面,添加如下代碼:
現在,添加這些代碼:
就是這樣,通過三個這么短的方法就可以顯示一個漂亮的滾動視圖。
實際上,你仍需要創建一個真正的滾動視圖,然后添加到你的主視圖上,但是在這之前,先添加下面的方法:
[self showDataFroAlbumAtIndex:currentAlbumIndex;
}
現在,在 viewDidLoad 里 [self showDataForAlbumIndex:0] 前面添加下面代碼來初始化滾動視圖:
[self reloadScroller];
提示:如果一個協議變得很大,里面有很多方法,你應該考慮把它們分散到幾個小的協議里去。UITableViewDelegate 和 UITableViewDataSource 就是一個很好的例子,因為它們都是 UITablveView 的協議。設計協議的時候,最好一個名稱引導一個功能。
構建和運行你的項目,你會看到一個新的很了不起的水平滾動視圖:
啊嗯,等等。水平滾動的視圖已經有了,可是專輯封面在哪里?
對了,你還沒有代碼來執行下載圖片的功能。你需要添加一個下載圖片的方法。查檢 LibraryAPI 服務的所有接口,這里需要添加一個新的方法。不管怎樣,現在還有幾件事情需要考慮:
AlbumView 并沒沒有通過 LibraryAPI 立即工作。你沒有給視圖添加通信邏輯。
相同的原因,LibraryAPI 并不認識 AlbumView。
LibraryAPI 需要通知 AlbumView,一旦封面下載完成,AlbumView 就會顯示它。
新聞熱點
疑難解答