引子:14天上线一款App?
14天上线一款App,相信为App应用开发的你一定类似的话不陌生。
有些老板,尤其是创业公司的老板,给出2、30个页面的设计原型,然后会用这样的言论加以鼓励”:“14天上一款App,别人可以,我们一定也可以!加油!” 而且,不太了解技术的他们期望的不是首次上线,而是每次更新都可以保持这样的速度——当然,还不能有bug,用户体验要优良……
a. 有幸 我看过几款这样短期上线的App:
Demo1:这款App14天完成开发上线,只有H5版,每次进行页面跳转,无论进入新页面还是点击返回,都是页面从右边推进来的切换效果……
Demo2:这款App快速上线,并经过了1年半时间的维护,接收到我们团队的时候,发现使用的是 “标准的MVC” 设计模式,随便找一个VC,容纳着2000多行的代码……
DemoN:………………
b. 不幸 我暂时没有看到过一款“优雅”的快速上线的App。
“速度”与“优雅”并存不可能实现么?
大公司可以实现,因为有“积累”。
小公司、个体户,也可以实现,如果去“积累”。
一、问题:关于“积累”
1.1 怎样才算“有积累”?
举例来说,一个工作几年的App开发,随便写几个页面,不应该发生类似“数组越界”,“向数组添加空数据”这种低级原因而造成的应用崩溃(若发生,他至少应该感到内疚);而且,他应该可以提供出来一套方案,让类似的问题在别人做开发的时候也不会发生。
1.2 可以从哪些方便的“积累”?
开发经验、技术选型、技术难点解决方案……方向很多。而我们今天想说的,是从基础支持模块积累的角度,并期望提供一个能快速开发一款新的App的积累方向。
二、 直入主题:App应用框架
App应用框架.png如果基于应用层面,你对此结构表示认同,那我们可以继续进行下去。首先 从底至顶 进行简单的解释:
2.1 第三方模块
第三方模块既然称之为模块,即它们是可以独立存在的,即两个第三方模块间一般没有必要的关联(排除模块声明的库间依赖)。所以将其放置到最底层,以被上层模块依赖和调用。
第三方模块的集成方式一般包含:sourceCode(源代码),lib,framwork,cocoaPod。
当然,时下最流行和推崇的集成方式是cocoaPod。
2.2 基础工具
我们姑且将工具分为 视图工具 与支持工具
我们期望支持工具不依赖视图工具。
支持工具:常见的如 防越界的取数组元素的方法支持(category)、屏幕大小获取(宏定义)、网络请求(模块)等。
控件工具:比如 Alert、Toast、网络加载控件等。没有“全局”概念的控件不建议放到这边(比如两个Vc的某个子视图“长相一样”不建议将它当做“控件工具”,太业务了)。
P.S. 我会建议将大多数的支持工具封装成一个独立的工具库,并支持pod导入。
2.3 应用配置
应用相关的配置信息随着维护迭代会变得很多,我们给出的建议分类可以cover其中的大部分内容:
xxxAppConfig:应用启动配置
xxxAppContext:应用运行上下文支持
xxxYyyDefinitions:应用宏定义,比如网络接口、H5链接等
2.4 应用页面
我们耳熟能详的 MVC / MVP / MVVM / VIPER 等设计模式即是应用在这边的每一个VC(页面)中。
无论是页面的布局,还是数据的操作,他们会大量运用 “基础工具”,“第三方模块”的支持。
页面间,基于业务需求一般会有一些相互跳转的依赖耦合(比如页面A知道自己可能会跳转到B或C),这是合理的,没必要苦思冥想去解耦。(我见过有同学盲目“解耦”,用字符串的方式去获取Vc类名,因不用再引入对应Vc头文件,美名曰“解耦”……根本没有解耦好嘛!徒增代码维护复杂性!)
但针对跳转方法,进行抽象统一,做一个路由模块,则可以思考的方向(能实现什么呢?比如服务端修改一个配置,你的某个任何一个原生页面可以动态更改为一个H5页面),非本文重点,就不赘述了。
2.5 应用控制
Appdelegate负责了整个应用层面的关键消息处理,比如 应用启动、最小化、接收推送 等。
那么,其实每一个处理流程,都会根据具体需求的复杂性抽象出一或多个模块,比如启动流程管理、配置初始化等。
三、框架分析
3.1 支持模块:第三方支持 & 应用支持
常用模块.png我们依然 自底向上 依次说起
3.1.1 建议的第三方工具库
建议各个App开发的同学根据具体业务需求参考集成下面 建议的第三方工具库。针对建议的第三方工具库,都已经支持了cocoaPod集成方式,下面是对应的pod集成代码参考。(一般建议先不加版本号集成最新版本,调通后再将版本限定死,防止不期望的更新)
pod 'AFNetworking', '~> 3.2.1' # AF网络库
pod 'WebViewJavascriptBridge', '6.0.3' # H5 & 原生 交互支持
pod 'MBProgressHUD', '0.9.2' # “转菊花”加载支持
pod 'MJRefresh', '3.1.12' # 处理scrollView头部刷新
pod 'lottie-ios', '2.5.0' # 处理json动画
pod 'Masonry', '1.0.2' # Masonry 视图自动布局支持
pod 'YYCategories', '1.0.4' # YY 应用基础工具库
pod 'SDWebImage', '4.0.0' # SD 图片缓存加载支持
pod 'MLeaksFinder', '0.2.1' # 轻量级内存泄露检查工具
pod 'tingyunApp', '2.7.0' # [收费] 听云App,应用全面监控分析
pod 'Qiniu', '7.1.8' # [收费] 七牛晕存储,大文件存储访问支持
pod 'UMCCommon', '1.5.1' # 友盟基础组件
pod 'UMCPush', '3.2.1' # 友盟推送
pod 'UMCSecurityPlugins', '1.0.6' # 友盟安全插件
pod 'UMCAnalytics' # 友盟分析
pod 'UMCErrorCatch' # 友盟异常捕获
3.1.2 建议积累的支持工具库
xxx是指你的,或你的公司的缩写,作为库的前缀,一般用大写,在3个字符长度内为益。
xxxToolkit
类似于上面提到的YYCategories,但我们总会有一些基本工具支持是YYCategories提供不完全的,我们可以在xxxToolkit中进行补充。当然,除了category之外,一些独立的小工具模块也可以考虑集成在xxxToolkit当中。
xxxShare
分享永远用直接使用友盟支持么?不一定,基于第三方基础支持封装我们的自己的一层,是常见的做法。同时,我们可以参考加入一些其他支持,比如你们公司的分享控件的展示效果(如果有统一的话)。
xxxNetwork
列举一下常见的功能吧,看看你的网络模块是否已经支持。
同步/异步 请求支持
Get/Post 请求支持
请求/答复 数据格式设置支持
公共请求头/公共请求参数 设置支持
请求参数鉴权处理 回调支持
不持有请求对象发送请求支持
多个请求同时发起,并等待所有请求结束的支持(期望能捕获失败的请求)
多个请求顺序发起,并等待最后一个请求结束的支持(期望中间请求失败的时候可以结束请求)
网络请求短期自动缓存策略
快捷请求方法的封装
请求答复的公共层自动解析的 解析方法回调设定
请求错误码与提示信息的约定设计
等 O(∩_∩)O~
所以,或许你的网络模块还有很大的潜力呦!
xxxFeedback
用户问题反馈也是常见的功能模块,当然,这个模块会需要一些产品设计甚至后台的支持。当前潮流会比较推崇截屏反馈。
xxxRouter
a. 为基本的页面跳转操作进行约定封装;
b. 实现页面基于路由约定规范的跳转(比如H5页面向原生发一个符合约定的消息就可以打开对应原生页面);
c. 实现目标页面的形式替换(比如通过在线修改一个配置就可以实现将App的某个原生页面替换成对应的H5页面)。
3.1.3 建议积累的视图工具库
xxxCustomControl
包含一些基础小控件(如toast,刷新icon)和自定义的系统控件(如 userdefTabbar, userdefNavbar, userdefPageControl, userdefSegmentControl 等)
TestEnvSwitch
需求说明
测试同学根据测试进度常常会需要不同环境的测试包(比如功能测试环境,集成测试环境,预发布环境,正式环境等),而我们的后台针对不同的环境,往往请求的主机IP 或 域名是有差异的。为此,我们每次修改了代码再打包给测试虽然不复杂,但次数多了,很是麻烦(我们自己切换环境调试也麻烦)。
所以,我们会期望针对App的测试包,集成一个 环境切换的页面 和 一个当前环境的标识(环境角标),这样,通过某种的方式打开环境切换页面(比如baseWindow悬浮一个功能按钮,又或点击5次tabbar首页按钮等),我们就可以在测试阶段,永远都只提供一个测试包。只在最终打包上线的时候,修改一个类似buildForPublish概念的BOOL变量值,将环境切换等测试支持功能全部隐藏。
TestEnvSwitch模块就是要提供一个 环境切换页面,和一个 环境角标控件。
下面是一种 实现了基本功能,但异常丑陋 的环境切换页面的参考。
环境切换页面-简单丑陋版.png根据各个公司实际情况,页面可能支持更多的讯息或变得更美观,甚至加入一些设计元素的支持,比如下面:
环境切换页面-中级版.pngVc Container
我们常常会用到Vc容器,即类似Tabbar这样的包含多个子视图的视图容器。但切换效果上,我们或许会期望一些非系统默认的,比如通过手势左右滑动可以滚动视图切换Tab,上下滑动可以切换上下页面等。 具体效果大家可以参考 【麦子金服财富】 的 投资tab页(左右切换容器子视图),点击一款投资产品进入的 投资产品详情页面(上下切换容器)。
它们的实现一般是一个容器Vc包含多个子Vc,并将子Vc的视图依次放置在一个ScrollView上面。这样做的时候,交互方面还存在着不少脱离业务的细节处理逻辑存在,所以建议能够将它们抽象出来,封装对应的Vc容器以便复用。
Share
这边的分享指分享的页面视图了,积累一个常见的吧!你会发现尤其是创业公司,他们最喜欢现成的!
分享控件.pngBanner
感念就不解释,建议可以约定一个BannerItem的数据结构,并提供pageControl的 显示/隐藏/替换支持。
3.2 应用配置
应用配置.png3.2.1 AppConfig
它可以包含:
a. App启动时需要更新的配置信息(一般运行过程中不需要改变)
b. 第三方对接的固定配置信息(比如各平台的AppId获取)
c. 一些info.plist常用信息的获取支持(也属于配置)
d. 基于 测试/正式环境 获取 测试/正式配置信息 的支持(比如请求主机地址/域名)
AppContext
应用运行过程中有一些属性要频繁使用并有可能改变,那么建议放在这边,比如登录账号的缓存(加密根据需求考虑)、登录状态的记录等。
注:如果只是局部场景的临时变量,不要放在这边,这边的概念是全局。
Definition
应用不同概念模块的宏定义,比如 网络请求业务地址的统一定义、H5跳转链接的统一定义 等。
3.3 应用页面
每款App具体的业务场景不同,但我们还是可以抽象出来一些公共的页面概念,如下:
应用页面简单分类.png3.3.1 页面支持
3.3.1.1 BaseVc or VC AOP
BaseVc不用过多赘述,即每个ViewController都要继承的Vc。但我们也可以考虑用另一种方式完成BaseVc的功能,即添加UIViewController的AOP(切面编程)。简单来说:
a. 写一个UIViewController的分类
b. 使用runTime,在UIViewController中在一些方法中(比如viewDidLoad:)添加一些公共的代码段。
3.3.1.2 测试引导页
招聘流程上我们常常看到诸如“精通xCode的单元测试”的要求,但我想问一下有过几年iOS UI开发经验的同学,你们有多少人真正在使用这样的功能?使用的同学中,又有多少感到它的性价比很高?(比如3个月后是否还可以顺利方便地使用)
单元测试(unit testing),本意是指对软件中的最小可测试单元进行检查和验证。 然而,我们想想一下,互联网时代一款频繁迭代的App,我们对代码中每一个函数去写一个测试代码的话……或许你维护测试代码的时间都比它产出的效果多了!
这里,我们不继续深入纠结“单元测试”本身,而是直接提出一种更适用于App的建议的测试方案——测试引导页。
测试引导页主功能示例.png简单说明下期中的关键功能模块
1) 主测试页面
包括 用例测试页面按钮、环境切换页面按钮、正常启动按钮、临时测试按钮组、当前环境角标。
2)用例测试页面
这边的用例一般建议分为 启动、功能、页面 三个模块。
- 启动 是因为App可能基于不同的场景有多套启动流程(咳咳,不过多说明);
- 功能 指一些独立的功能模块,比如H5 JSBridge回调接口的测试,网络接口的测试等;
- 页面,App的重中之重,期望 每个页面都可以有约定好的输入信息,可以独立打开,最起码保证不会崩溃吧!同时也可以针对页面的一些异常状态进行测试。
3)切换测试环境页面 & 环境角标
切换当前的测试环境,一般是和后台提供的服务环境支持相匹配的。
我们前面已经在【TestEnvSwitch】一节简单聊过测试环境切换的背景和需求。那么,当我们有了【测试引导页】的时候,就可以不太关注开启环境切换页的功能 的位置了。因为,测试引导页 就是环境切换功能 最好的存在位置!
4)正常启动
上线的应用包是怎样的启动流程,它就是怎样的。
5)临时测试按钮
不知道大家的习惯如何,我在拟写一个 新页面 或者 功能模块 的时候,进行简单的分析设计后,会把页面或者模块拆分成子页面、子模块,然后先从子页面、子模块开始依次实现,最后再进行组合。
那么,在组合之前(子页面、子模块还没有一个容器去测试的时候),临时的测试按钮就发挥了它们的功能,对应的实现中写一个视图或视图的操作方法(验证一些动画效果),进行测试。
P.S. 设计
这样的测试引导页的划分只是简单通用的一种实现形式。根据大家公司的具体需求和资源分配的可能,它完全可以采用其他的概念划分方式,当然,也可以更好看!
3.3.2 标配页面
时下流行的App都常常使用的页面,比如 首次启动引导页;每次启动广告页,后台切换前台 广告页;封装了 进度条 和 一些支持 的网页容器页 等。
3.3.3 业务页面
不是本文的重点,而且基于每个应用业务场景的不同,其业务页面的具体划分方式都不尽相同。
3.4 应用控制
应用控制层.png3.4.1 AppDelegate
我们知道,AppDelegete掌管整个应用生命周期的回调处理,它要处理的内容还是很多的。所以,我们有必要进行一些关键的概念模块抽象。让AppDelegate本身变得清爽一些。
说明:
我们期望借由AppDelegate知道在应用的生命周期,我们都做了哪些事情,而不是这些事情的细节。即:关注接口,不关注实现。
3.4.2 LaunchConfig
在application:didFinishLaunchingWithOptions:方法中,我们App要做很多基础配置的事情。比如 配置网络主机域名,公共请求头、公共请求参数、友盟对接、分享配置、客服配置、应用安全配置、第三方登录配置 等,这些配置的具体设置可以抽象一批方法。
3.4.3 VcLaunchManager
应用启动过程中,常常涉及多个页面,比如 首次启动引导页,广告页,登录页,手势密码页,指纹密码页 等。对启动流程相关的页面加以管理,可以让 启动流程进行变化和调整的时候,修改起来更方便。
3.4.4 ExternalMessageHandler
外部消息处理。App可能不是通过正常方式启动的,比如推送启动,3Dtouch启动,又或被其他App调起。这些场景经常会附带一些额外的信息以期望额外进行一些操作行为。这部分也建议抽象一个模块将数据解析以及具体的操作行为进行封装。
4 总结
前面的内容虽然简单,但信息量也不少,这边,将我认为最有价值的思路 & 亮点总结分享给大家,希望大家可以关注:
4.1 思路
4.1.1 时刻记得要分层
公司组织架构要分层,代码架构一样要分层,分层的思路如此经久不衰,那么就自然有它的实践的有效性!上层依赖下层,下层不关心上层——我们在实践中或许会结合时间、资源等因素有一些灵活的调整,但我们要时刻在脑海中守住那个我们约定好的层次关系,知道调整只是Patch。框架稳健才能有有效地丰富内容、扩展功能。
4.1.2 支持 & 配置概念要分开
代码支持 与 应用配置 都有“公共”的概念,但我会建议将其分开。所谓“应用配置”,是针对该应用的,根据应用的迭代更新,它是更容易发生改变(添加属性、支持方法)的;而基本的支持(如Category),我们是期望可以抽象成库(期望Pod支持)的。
4.1.3 将"支持模块"封装成库
业务层面,公司规模不到,不要轻易尝试抽库去做所谓的“解耦”;
但针对公共支持,无论网络、分享还是基本工具,建议我们有一套和业务无关的功能库。
4.1.4 一定要使用CocoaPod
Pod集成方式统一,集成速度快,版本更新快捷方便,支持多种版本选择方式,设置支持简单,而且主流的第三方代码库都已经支持了Pod……这么好用的工具,给我一个理由不去使用!对应的库不支持Pod,好吧,这是个例外!
4.1.5 自定制你的App框架
关于我建议的App框架中包含的内容,是最基础和通用的内容,我们在具体的开发中,想要做到极致,会涉及到更多开发模块的需求,比如“日志模块”,比如“音频视频模块”,比如“测试浮层管理模块”,比如“应用数据收集模块”……
他们都将成为属于你们公司的积累、财富!当然,也是属于你的!
4.1.6 积累模块就积累精致的
我们积累的模块要极尽可能地精致合理(*效果理想,接口简单清晰8),如果我们积累的模块是文章开头那种页面切换无论进入还是返回都是从右边推进来的效果的模块(引子-Demo1,效果很不理想),我就只能翻个小小的白眼咯🙄……
4.2 亮点
4.2.1 环境切换功能(页面+角标)
如果你们公司的后台测试环境有多个,还要针对每个环境打对应的App包的话,建议考虑下环境动态切换功能的集成,方便下测试同学吧!当然,也方便了产品、运营包括我们开发自己。
详情参考【3.1.3-TestEnvSwitch】
4.2.2 测试引导页
最早我只是写了个独立的页面,在进行新页面开发的时候可以进行通过替换启动页的方式方便小模块的测试(真实的页面场景太复杂,不方便简单功能的测试);后来,用得多了,就进行了进一步的设计:
a. 非线上包环境跳转到测试引导页面;
b. 线上包(只需要代码中修改一个YES / NO)跳转到正式页面;
c. 同时测试引导页包含 模块测试入口、简单测试预留按钮、线上包页面的跳转功能 等支持。
我不知道这种方式是不是全行业的首创,但在我认识的人中绝对是首创了,而且,用了的人都感觉:嗯…不错!
正式/测试包启动逻辑.png结语
小公司的第一版App真的无法集开发快速、稳定、可维护、可扩展于一体么?不然,只要从现在开始做一些有价值的积累就行!我们换到Android、后台呢?其思路也是一样的!
理清框架层次,积累关键模块!
快尝试一下吧!
遇到任何细节问题,也欢迎在评论区与我讨论:)
网友评论