iOS 开发中的 Self-Manager 模式
给某个视图更大的权力,让它自己负责处理自己的事件。
有一个例子:负责展示头像的视图
它的职责包括:
- 1、通过传入的 URL,加载并展示头像图片
- 2、显示一些附属信息,比如大V的标志
- 3、将用户点击头像的事件传递给外层的 View Controller 跳转到用户信息页面
代码是这样的:
@interface FDAvatarView : UIView
// 假设 VIPInfo 是某个 Entity
- (void)configureWithAvatarURL:(NSURL *)URL VIPInfo:(id)info tapped:(void (^)(void))block;
@end
使用这个控件的人只要调用该方法传入参数就ok。但此时会有个问题:
- 1、上面的例子,
UITableViewCell
会调用这个方法,但是cell并不知道自己的ViewController
是谁,于是乎事件得往上传,直到能找到自己的navigation
,然后跳转到下一个界面。 - 2、这个
FDAvatarView
出现在多个
界面,而且行为一致的话,这样多个界面就都要处理该事件。还会多出来只是向上一层传递事件的中间人
。
为解决这个问题,就需要给这个 View 放权
,让其自己 Handle
自己的事件,也就是 Self-Managed
,为了不破坏 View 的纯洁性
,比较好的实践是在 Category
中实现:
@interface FDAvatarView (FDAvatarViewSelfManager)
- (void)selfManagedConfigureWithAvatarURL:(NSURL *)URL VIPInfo:(id)info uid:(NSString *)uid;
@end
实现:
@implementation FDAvatarView (FDAvatarViewSelfManager)
// 为后一个页面的创建增加了个 UID 参数
- (void)selfManagedConfigureWithAvatarURL:(NSURL *)URL VIPInfo:(id)info UID:(NSString *)UID {
[self configureWithAvatarURL:URL VIPInfo:info tapped:^{
// 假设 App 结构是 Root -> TabBar -> Navigation -> ViewController
UITabBarController *tabBarControler = (id)[UIApplication.sharedApplication.delegate.window.rootViewController;
UINavigationController *navigationController = tabBarControler.selectedViewController;
// 创建用户信息 View Controller
FDUserProfileViewController *profileViewController = [FDUserProfileViewController viewControllerWithUID:UID];
[navigationController pushViewController:profileViewController animated:YES];
}];
}
@end
这里用到了类似
AOP
(面向对象) 的思路,添加了对 App 层级的耦合(调用者只要调用了该方法就会直接跳转到用户界面
),如果觉得这样的耦合方式不妥的话,也可以封装
个全局方法去取到当前顶层的Navigation Controller
。
这样,FDAvatarView 的调用者只需要配置入参,其余的它自己全能搞定了,即使 App 内很多处出现头像,逻辑代码也只有一份。
接下来再看一个例子:
这个点赞的按钮功能上有几个职责:
显示已有的点赞数
点击按钮后执行一个小动画,点赞数 +1,同时发送网络请求。
若已经点赞,点击执行反向操作
若网络请求发送失败,则回退成点击前的状态
这个控件的 代码 可以设计成这样:
@interface FDLikeButton : UIButton
- (void)configureLikeStatus:(BOOL)likeOrNot count:(NSInteger)count animated:(BOOL)animated;
@end
因为继承自 UIButton
,所以外部可以直接设置其 action
,就不增加 tappedHandler 的参数了。外部直接
在点击事件中需要调用这个配置方法,播放点赞动画,紧接着发送一个网络请求,若网络请求失败,可以再次调用这个 API 的无动画版本回滚状态。
有个问题
,像上面的例子一样,网络请求
和事件处理逻辑相同
,但代码却分部在各个页面中,于是给这个 View 增加Self-Managed
模式的Category
:
@interface FDLikeButton (FDLikeButtonSelfManager)
- (void)selfManagedConfigureWithLikeStatus:(BOOL)likeOrNot count:(NSInteger)count;
@end
@implementation FDLikeButton (FDLikeButtonSelfManager)
- (void)selfManagedConfigureWithLikeStatus:(BOOL)likeOrNot count:(NSInteger)count {
[self configureLikeStatus:likeOrNot count:count animated:NO];
[self addTarget:self action:@selector(likeButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)likeButtonTapped:(id)sender {
// +1 or -1 with animation
// Network request ^(NSError *error) {
// if (error) {
// rollback
// }
// }
}
@end
设计上,
Self-Manager
模式并没有破坏原有的 MVC 结构,上面两个例子中的 View 依然可以不耦合
具体业务逻辑的单拿出来用
。使用 Category 的方式把应该写在 ViewController 中的代码移动到 View 的文件中,让功能更加的内聚
。
程序的复杂度并不会因哪种酷炫的设计模式所减少,能做到的只是对复杂度的切分
和控制
,即:
- 1、让
一大坨
恶心的代码变成几小坨
不那么恶心的代码。 - 2、让恶心的代码只在
一个地方恶心
。
来源于 http://blog.sunnyxx.com/ 博客。