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

最近,这个思路被我们当做了一种设计模式,即赋予一个 Widget 更大的权利,让其自己负责自己的事件。
举个简单的栗子,这种负责展示头像的视图:

它的职责包括:

  1. 通过传入的 URL,加载并展示头像图片
  2. 显示一些附属信息,比如大V的标志
  3. 将用户点击头像的事件传递给外层的 View Controller 跳转到用户信息页面

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

@interface FDAvatarView : UIView
// 假设 VIPInfo 是某个 Entity
- (void)configureWithAvatarURL:(NSURL *)URL VIPInfo:(id)info tapped:(void (^)(void))block;
@end

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

  1. configure 的调用者是 superview,上面的例子中也就是一个 UITableViewCell,但 Cell 这层并不知道自己的 ViewController 是谁,于是乎还得向上一级传递这个点击事件,直到能获取到 NavigationController,然后 Push 一个用户信息的页面。
  2. 这个 Avatar View 在 App 的各个地方都可能粗线,而且行为一致,那就意味着事件处理的 block,要散落在各个页面中,同时也带来了很多“只是为向上一层级转发事件”的 “Middle Man”

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

@interface FDAvatarView (FDAvatarViewSelfManager)
- (void)selfManagedConfigureWithAvatarURL:(NSURL *)URL VIPInfo:(id)info uid:(NSString *)uid;
@end

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

@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. 显示已有的点赞数
  2. 点击按钮后执行一个小动画,点赞数 +1,同时发送网络请求。
  3. 若已经点赞,点击执行反向操作
  4. 若网络请求发送失败,则回退成点击前的状态

@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

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

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

  1. 让一大坨恶心的代码变成几小坨不那么恶心的代码。
  2. 让恶心的代码只在一个地方恶心。

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

iOS开发中的Self-Manager 模式的更多相关文章

  1. 总结iOS开发中的断点续传那些事儿

    前言 断点续传概述 断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始.当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很 ...

  2. iOS开发中调试小技巧

    对于软件开发而言,调试是必须学会的技能,重要性不言而喻.对于调试的技能,基本上是可以迁移的,也就是说你以前在其他平台上掌握的很多调试技巧,很多也是可以用在iOS开发中.不同语言.不同IDE.不同平台的 ...

  3. IOS开发中设置控件内容对齐方式时容易混淆的几个属性

    IOS开发中四个容易混淆的属性: 1. textAligment : 文字的水平方向的对齐方式 1> 取值 NSTextAlignmentLeft      = 0,    // 左对齐 NST ...

  4. 在iOS开发中使用FMDB

    在iOS开发中使用FMDB 前言 SQLite (http://www.sqlite.org/docs.html) 是一个轻量级的关系数据库.iOS SDK 很早就支持了 SQLite,在使用时,只需 ...

  5. iOS开发中使用[[UIApplication sharedApplication] openURL:]加载其它应用

        iOS 应用程序之间(1)  在iOS开发中,经常需要调用其它App,如拨打电话.发送邮件等.UIApplication:openURL:方法是实现这一目的的最简单方法,该方法一般通过提供的u ...

  6. 深入理解 iOS 开发中的锁

    来源:伯乐在线 - 夏天然后 链接:http://ios.jobbole.com/89474/ 点击 → 申请加入伯乐在线专栏作者 摘要 本文的目的不是介绍 iOS 中各种锁如何使用,一方面笔者没有大 ...

  7. iOS 开发中常见的设计模式

    最近有小伙伴问到在iOS开发中的几种设计模式,这里摘录一下别人的总结(因为已经感觉总结得差不多了,适用的可以阅读一下) 首先是开发中的23中设计模式分为三大类:1.创建型 2.结构型 3.行为型 (i ...

  8. iOS开发中的MVC设计模式

    我们今天谈谈cocoa程序设计中的 模型-视图-控制器(MVC)范型.我们将从两大方面来讨论MVC: 什么是MVC? M.V.C之间的交流方式是什么样子的? 理解了MVC的概念,对cocoa程序开发是 ...

  9. iOS开发中遇到的一些问题及解决方案【转载】

    iOS开发中遇到的一些问题及解决方案[转载] 2015-12-29 [385][scrollView不接受点击事件,是因为事件传递失败] // //  MyScrollView.m //  Creat ...

  10. 【转】在iOS开发中使用FMDB

    本文转载自:唐巧的博客 在iOS开发中使用FMDB APR 22ND, 2012 前言 SQLite (http://www.sqlite.org/docs.html) 是一个轻量级的关系数据库.iO ...

随机推荐

  1. poj1273 Drainage Ditches (最大流模板)

    http://poj.org/problem?id=1273 Dinic算法 这是一道最大流的经典题 最大流尽量应该用边表,优于邻接矩阵(所以我写了邻接矩阵版的之后又写了个边表) 用了新学的Dinic ...

  2. 为HTML5添加新样式标签

    为 HTML 添加新元素 该实例向 HTML 添加的新的元素,并为该元素定义样式,元素名为 <myHero> : 实例 <!DOCTYPE html> <html> ...

  3. 黑马学习Ajax 概念和基本使用

  4. I.Algorithm Choosing Mushrooms

    链接:https://ac.nowcoder.com/acm/contest/908/I 题意: Baby bear and his good friends are very fond of mus ...

  5. B.Icebound and Sequence

    链接:https://ac.nowcoder.com/acm/contest/903/B 题意: Icebound hates math. But Imp loves math. One day, I ...

  6. Net Core下通过Proxy 模式

    Net Core下通过Proxy 模式 NET Core下的WCF客户端也是开源的,这次发布.NET Core 2.0,同时也发布了 WCF for .NET Core 2.0.0, 本文介绍在.NE ...

  7. (办公)ssm发送邮件

    1.添加jar包 <!-- Javamail API --> <dependency> <groupId>javax.mail</groupId> &l ...

  8. 初学Android,创建,启动,停止Service(五十八)

    Service跟Windows系统里的服务概念差不多,都在后台执行,它跟Activity的最大区别就是,它是无界面的 开发Service与开发Activity的步骤类似 1.定义一个继承Service ...

  9. SpringBoot环境中使用MyBatis代码生成工具

    一.Maven配置文件中添加如下依赖 <dependency> <groupId>org.mybatis.generator</groupId> <artif ...

  10. Random类、ThreadLocalRandom类

    Random和ThreadLocalRandom类均用于生成伪随机数. Random的构造函数: Random()     默认以系统当前时间为种子,相当于Random(System.currentT ...