相信做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)位置。现在的是 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 就会显示它。
新闻热点
疑难解答