iOS 开发中的 Self-Manager 模式

Self-Manager 源于我们团队内部的黑话,“诶?你刚去的创业公司有几个 iOS 开发啊?” “就我一个” “靠,你这是 Self-Manager 啊”

最近,这个思路被我们当做了一种设计模式,即赋予一个 Widget 更大的权利,让其自己负责自己的事件。

举个简单的栗子,这种负责展示头像的视图:

它的职责包括:

通过传入的 URL,加载并展示头像图片

显示一些附属信息,比如大V的标志

将用户点击头像的事件传递给外层的 View Controller 跳转到用户信息页面

于是乎这个 Widget 的 API 可以长这个样子:

@interfaceFDAvatarView:UIView

// 假设 VIPInfo 是某个 Entity

- (void)configureWithAvatarURL:(NSURL*)URL VIPInfo:(id)info tapped:(void(^)(void))block;

@end

使用这个控件的人只需要调用这个 configure 方法就可以配置入参和事件处理。但随之而来的就是一些蛋疼的问题:

configure 的调用者是 superview,上面的例子中也就是一个 UITableViewCell,但 Cell 这层并不知道自己的 ViewController 是谁,于是乎还得向上一级传递这个点击事件,直到能获取到 NavigationController,然后 Push 一个用户信息的页面。

这个 Avatar View 在 App 的各个地方都可能粗线,而且行为一致,那就意味着事件处理的 block,要散落在各个页面中,同时也带来了很多“只是为向上一层级转发事件”的 “Middle Man”

为解决这个问题,就需要给这个 View 放权,让其自己 Handle 自己的事件,也就是 Self-Managed,为了不破坏 View 的纯洁性,比较好的实践是在 Category 中实现:

@interfaceFDAvatarView(FDAvatarViewSelfManager)

- (void)selfManagedConfigureWithAvatarURL:(NSURL*)URL VIPInfo:(id)info uid:(NSString*)uid;

@end

实现时最好要调用 View 主类提供的 API:

@implementationFDAvatarView(FDAvatarViewSelfManager)

// 为后一个页面的创建增加了个 UID 参数

- (void)selfManagedConfigureWithAvatarURL:(NSURL*)URL VIPInfo:(id)infoUID:(NSString*)UID{

[selfconfigureWithAvatarURL: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,同时发送网络请求。

若已经点赞,点击执行反向操作

若网络请求发送失败,则回退成点击前的状态

这个控件的 API 可以设计成这样:

@interfaceFDLikeButton:UIButton

- (void)configureLikeStatus:(BOOL)likeOrNot count:(NSInteger)count animated:(BOOL)animated;

@end

因为继承自 UIButton,所以外部可以直接设置其 action,就不增加 tappedHandler 的参数了。外部在点击事件中需要调用这个配置方法,播放点赞动画,紧接着发送一个网络请求,若网络请求失败,可以再次调用这个 API 的无动画版本回滚状态。但像上一个例子一样,网络请求和事件处理逻辑相同,但代码却分部在各个页面中,于是给这个 View 增加 Self-Managed 模式的 Category:

@interfaceFDLikeButton(FDLikeButtonSelfManager)

- (void)selfManagedConfigureWithLikeStatus:(BOOL)likeOrNot count:(NSInteger)count;

@end

伪代码的实现如下:

@implementationFDLikeButton(FDLikeButtonSelfManager)

- (void)selfManagedConfigureWithLikeStatus:(BOOL)likeOrNot count:(NSInteger)count {

[selfconfigureLikeStatus:likeOrNot count:count animated:NO];

[selfaddTarget:selfaction:@selector(likeButtonTapped:) forControlEvents:UIControlEventTouchUpInside];

}

- (void)likeButtonTapped:(id)sender {

// +1 or -1 with animation

// Network request ^(NSError *error) {

//    if (error) {

//        rollback

//    }

// }

}

@end

记得面试题的那篇文章里还调侃说 “面试的时候聊聊设计、架构挺好的,但别整出个往 UIButton 的子类里搞网络请求的奇葩结构就行”,结果就被自己打了个脸。不过从设计上,Self-Manager 模式并没有破坏原有的 MVC 结构,上面两个例子中的 View 依然可以不耦合具体业务逻辑的单拿出来用。使用 Category 的方式把应该写在 ViewController 中的代码移动到 View 的文件中,让功能更加的内聚。

程序的复杂度并不会因哪种酷炫的设计模式所减少,能做到的只是对复杂度的切分和控制,即:

让一大坨恶心的代码变成几小坨不那么恶心的代码。

让恶心的代码只在一个地方恶心。

Self-Manager 模式我们实践的时候写起来很开心,抛砖引玉一下,希望也能解决你的苦恼。

你可能感兴趣的:(iOS 开发中的 Self-Manager 模式)