API设计
考虑提供给开发者的内容
你标记为 Public 的内容都是非常重要的,它将是框架的使用者能看到的内容,提供什么样的API一定程度上决定了其他开发者如何使用你的框架。
尽可能小的访问权限
在API设计的时候,从原则上讲我应该尽可能提供较少的接口来完成必要的任务,这有助于在框架的初期控制住框架的复杂度,而之后随着逐步开发和框架使用场景的扩展,我们可以添加一些公共的接口啊,或者将原来标记为 Private 的内容标记为 Public,供外界来使用。
1 | // Do this |
命名,是否清晰易懂完整
在 Cocoa 的世界里,精确比简短更有吸引力。
举个例子,可能相较于简短的 remove,可能 removeAt 更能表达出从一个集合中移除某个元素的方法,而 remove 可能导致误解,是移除特定的 Int 呢?还是从某个 Index 去移除呢?
1 | // Do this |
同样的 recursivelyFetch 表达了递归的获取全部内容,而 fetch 可能会被理解为仅获取当前的输入。
1 | // Do this |
另外需要注意的是,方法名应该是动词或者动词开头的短语,而属性名应该是名词,当遇到冲突时,比方说这里的 displayName 既可以是名词也可以是动词短语,这个时候就应该特别注意属性和方法的上下文造成的理解不同,更好的一种方式是避免这种名动皆可的词语,比如说把 displayName 换成 screenName 可能是更好的选择。
1 | public var displayName: String |
注释文档
在命名 API 的时候一个有用的诀窍是为你的 API 编写文档,如果你不能用一句话表述清楚这个方法具体做了什么,这往往意味着你的这个 API 的名字是有改进的余地的。好的 API 呢就是可以上有经验的开发者猜的八九不离十。
理想状态:代码不需要文档就能被看懂
Swift API Design Guidelines
关于API的命名,苹果官方给出了Swift API 开发指南,Swift API Design Guidelines,遵守这个准则才能和其他开发者一道用约定俗成的一种方式来进行交流,这对提高框架的质量非常、非常、非常、重要!
优先测试,测试驱动开发
你应该是你开发的框架的第一个使用者,其实最简单的使用你的框架就是编写测试,在APP开发中,单元测试很多时候被我们忽略掉了,但是在框架开发里这是一个非常重要的细节,恐怕没有人敢使用没有经过测试的框架,除了保证功能正确以外,通过测试你还能第一时间发现框架里不合理的地方,并进行改善。
命名冲突
1 | // F1.framework |
在OC中,另一个特别突出的问题就是,静态库里一个常见的同样的符号在链接的时候导致冲突,在 Swift 中我们可以通过 model 和 import 来提供一个像类似于命名空间这样的代码隔离,从而避免这种符号冲突,但是对系统已有的类,添加 Extension 的时候还是要特别注意,比如说我们在这里,框架F1和框架F2,都是UIImage 定义的一个 method 的方法,然后分别输出自己来自哪一个框架。
1 | // app |
如果我们需要在同一个文件里同时引用F1和F2的话,编译器会很不高兴,因为它不知道具体要执行哪个方法。
1 | // app |
虽然命名空间已经被隔离了,但是这里 UIImage 是一个 NSObject,它的 Instance 实际上还是依赖于 OC 的 Runtime,这两个框架F1和F2都在 App 启动的时候被加载,运行时究竟调用了哪个,是以加载顺序为依据的,并不能确定,所以这种问题如果实际遇到的话就会非常难以调试,尽量避免。
对于 Cocoa 类型的 Extension 必须添加前缀
1 | // Do this |
发布框架
CocoaPods
1 | pod spec create MyFramework |
提交到 CocoaPods
1 | # 打 tag |
Carthage
很简单,将Target
的Scheme
设置为shared
版本管理
1 | # Podfile |
版本兼容
1 | x(major).y(minor).z(patch) |
- major - 公共 API 改动或者删减,用户必须要修改自己的代码才能够继续使用你的框架。
- minor - 新添加了公共 API,但是现有用户不需要修改代码就可以继续使用。
- patch - bug 修正等,API 没有变更。
0.x.y 只遵守最后一条,还未到达1.0.0的框架,表示还在早起开发,没有正式发布,API 在调整中,开发者想干什么就干什么。
使用Git tag 进行版本标记,框架的版本应该是和Git的tag相对应的,你的用户/包管理系统期望合理的兼容版本。
Version和Build
1 | // MyFramework.h |
框架的用户如果希望在运行时知道使用框架的版本号的话,他们会使用上面的两个属性进行访问,如果提供了的话,那么在框架迁移的时候比较有用,作为开发者,应该顺便维护下这两个值来帮助用户确定所使用的框架版本。
持续集成
在框架开发中,一个优秀的集成环境也是至关重要的,CI 可以保证潜在的贡献者在有保证的情况下对代码进行修改,可以大幅减小我们维护框架的压力。
TRAVIS CI, CIRCLE CI, COVERALLS, CODECOV…
自动化的发布流程
1 | # Fastfile |
值得参考的例子:AFNETWORKING/FASTLANE
优秀框架的特征
- 详尽的文档说明
- 可以指导后来开发者和协作者快速上手的注释
- 完善的测试保证
- 代码质量
- 让使用者了解更新变化的更新日志
- issue的相应速度
COCOAPODS QUALITY
这是一个为开源框架打分的索引类的项目,它会按照项目的受欢迎程度和上面提到的标准来为开源框架进行一个初步的评判。
可能的问题
兼容性保证
这里指的是逻辑上的兼容性,最可能出现的问题就是在不同版本中对数据持久化部分的处理是否兼容,包括数据库,属性增添,Key-archiving等等,比如说从老版本过来,添了一个属性,如何把旧版本的数据迁移过来,处理不当的话就可能导致 Crash。
重复依赖
将
EMBEDDED_CONTENT_CONTAINS_SWIFT
设置为 NO不要将依赖的 framework copy 到 你自己的 Framework 中,而是要使用如 PodFile 的方式来管理依赖
不同的框架依赖可能无法兼容
在开发框架的时候,如果要依赖别的框架,最好使用最宽松的依赖条件。