内容概述
SDK定义上是指软件开发包,对应iOS端来说就包含了库文件、头文件、资源文件等文件的集合。SDK开发就是在保证sdk源代码安全的情况,给开发者一个可方便快速接入的,兼容多个iOS系统、便于真机模拟器调试、可以上线AppStore的库。这句话包含的几个重要的信息将在本文中逐步详解。
本文将从iOS系统中SDK开发过程中需要考虑和注意这两方面入手,讲述SDK的设计:
1. 工程设置
2. 接口设计
3. SDK需要考虑的方面
4. checklist
工程设置
创建一个SDK工程通常通过下面方式:
目前SDK主流开发方式是第一种,即工程的产物提供framework,因为可以将库和头文件、资源都放在其中,当然可以选择第二种,工程的产物是静态库,如果分发需要将头文件与库一并提供。
对于framework来说,需要注意xcode6以后framework在Xcode默认配置的是动态库:
“Frameworks for iOS. iOS developers can now create dynamic frameworks. Frameworks are a collection of code and resources to encapsulate functionality that is valuable across multiple projects. Frameworks work perfectly with extensions, sharing logic that can be used by both the main application, and the bundled extensions.”
大意是xcode6(iOS8)后开发者可以创建自己的动态framework,其中包含可以跨工程使用的代码、资源。动态framework只能用于企业证书分发的ipa包中,对于上传appstore的所有产品必须使用静态framework,否则会被拒绝。但是系统提供的动态库dylib和framework是可以使用的, 更正为:iOS8开始,apple放开了动态库的使用,用户可以通过xcode创建动态库的framework,并且集成到app中,提交到appstore已经不会被拒绝。动态库和静态库的区别可参考之前的文章, 下图是静态framework的使用:
所以注意工程需要配置一下:
对于动态framework的使用稍有区别,需要单独将framework嵌入到ipa安装包中,因为动态库的特点就是运行时加载,所以app的可执行程序中并不会链接其使用的动态库,需要将动态库和可执行程序同时放在安装包中:
此工程编译打包出来的app文件中,如下图所示,会增加一个Frameworks文件夹,里面存放的正式嵌入的动态库:
接口设计
总体来说SDK接口设计和类的设计相似:
1. 首先需要根据SDK中模块划分根据“单一职责原则”、代码的依赖性分为不同的类;
所谓单一职责其实就是高内聚,保证一个类、一个接口只完成一个职责,不贪大贪全。
2. 接口设计也要满足“单一职责原则”;
例如即时通信的群组消息中一个群大概有群名称、群成员、群主、群头像、群主题等几个信息要素,我们设计接口时不能因为简便而用一个或两个接口提供所有这些信息,而是应该根据常见业务将上面信息分为以下接口: 获取群信息(群主、名称、头像), 获取群成员、获取群主题 这三个接口,或者根据情况将获取群信息分解为三个独立的接口。
这样设计的好处就是业务调用方可以根据实际情况某些case只调用其中一个接口,等到其他case再调用其他接口,这样可以保证每个接口的信息量没那么大,保证请求的及时响应和信息的及时展示。
3. 接口命名可读性强,拼写正确;
要避免以下几种形式的接口命名:
`createGroup:(NSString *)title`
`GroupWithTitle:(NSString *)title`
`creatGroupWithTitle:(NSString *)title`
三种命名分别的问题是: 第一种命名没有对参数进行描述, 第二种命名没有对接口的动作进行描述,第三种命名create单词拼写错误。
所以一个正确的命名应该是保证拼写正确的情况下,按照(省略主语)谓宾这样的格式:
`createGroupWithTitle:(NSString *)title`
4. 接口参数校验:考虑用户使用情况,给予充分提示,减少问答
有些接口图方便可能设置成这样:
- (void)queryGroupMasterViaGroupId:(NSString *)groupId requestSucess:(RequestGroupMasterSucess)success requestError:(void(^)(NSString *errorType)) error;
可以看出对于错误的反馈是通过参数error的block回调出来,block中的errorType对应错误信息,但这样的接口设计可能无法满足开发者的需求:只有错误描述字符串,实际开发者可能使用一个int型code用于判断错误性能更高,代码更清晰,即使直接将errorType字符串的错误以toast形式展示出来,这个字符串也有可能不是开发者实际想要的提示语。
所以最好的错误回调应该是至少包含错误码,最好包含错误描述(中英文两种),如下面例子:
-(void)requestGroupMasterViaGroupId:(NSString *)groupId requestSucess:(RequestGroupMasterSucess)success requestError:(RequestGroupMasterError) error;
其中回调的枚举定义如下:
typedef void (^RequestGroupMasterError) (CMIMError * error);//获取群主详情失败回调
回调的参数是一个CMIMError实例:
@interface CMIMError : NSObject
@property (nonatomic, assign) CMIMErrorCode code;
@property (nonatomic, readwrite, copy) NSString *codeDescription;
@property (nonatomic, readwrite, copy) NSString *detailDescription;
+ (instancetype)errorWithCode:(CMIMErrorCode)aCode;
- (instancetype)initWithCode:(CMIMErrorCode)aCode;
从该实例的定义可知道,实例中包含错误码和错误简单描述、详细描述。
需要考虑的方面:
SDK开发就是在保证sdk源代码安全的情况,给开发者一个可方便快速接入的,兼容多个iOS系统、便于真机模拟器调试、可以上线AppStore的库。这句话包含的几个重要的信息将在本文中逐步详解。
还记得开篇时说的这个定义? 这个定义对应了下面几个需要注意的地方:
1. 必要调试信息
对于我们提供给别人的库,默认应该是关闭log选项的,但是有些时候为了协助客户或者开发者定位SDK中问题,可能需要log信息,这样我们在设计时需要在代码中配置log,可以参考大牛的框架,有整个log的各个级别的开关,控制log的总量和详细程度。
2. category、第三方库、类的命名、宏定义命名需要独一无二
我们xcode编译链接时经常会报出这样的错误:盗个图:
这类问题都是由于重复的定义导致,包括类名的同名、宏定义的同名、category的同名等,解决方法最简单的就是将同名之一改成其他名字,如果无法修改则需要通过lipo指令来解决。
3. 头文件、属性暴露合理
保证代码安全就是保证头文件暴露合理,并且头文件中只暴露开发者需要用到的属性(定义好readonly属性)和接口,这样可以保证开发者不会错误调用其他接口或属性使SDK内部状态或逻辑错乱。
4. 支持的最低系统
通过app可能根据用户群体有不同的最低系统支持,例如目前很多app已经不再支持ios8以下系统,但是SDK作为app的内核,需要保证不同客户的需求都能满足,所以SDK应该尽可能保证支持最低系统,一般到iOS6.
5. 支持模拟器调试
为了便于开发者调试,需要支持模拟器调试,所以在打包framework时需要有x86和i386版本,并通过lipo指令将二者合并到armv7 v7s arm64中去。
6. crash跟踪
为了及时定位客户使用SDK导致的crash,SDK中也要处理crash信息,并及时上报给我们。不建议使用友盟或bugly等商用SDK来收集crash,因为开发者一般也会集成这类SDK,所以很容易冲突,最好是自己写代码实现,原理首先是收集到程序的exception或signal,这时将相应的堆栈信息打印输出上传到服务器或者我们的邮箱中。
发布前checklist
为了保证每次上线的准确无误,必须制定一个完备的checklist,每次发布之前对照checklist来逐个检查,才能保证各项准备达到。下面列出几个基本的检查项:
1. 系统架构是否覆盖完全,
使用lipo指令查看支持的架构,模拟器是否支持? arm64必须支持,否则无法上架
2. 版本号及发布日志
确定准确的版本号,并根据当前版本详细描述此次发布的更新点,这样方便开发者熟知改动的地方,便于评估对以前版本影响和使用新版本的成本,也便于我们定位问题。
3. log系统
关闭log输出,并屏蔽一些开发的调试代码或者提示框。对于一些尚不稳定的初始几个版本,由于问题比较多,开发者可以暂时打开log开关,这样在出现问题的时候便于定位,等一些版本后相对稳定之后,关闭log。
注意:日志需要制定相应的清除策略,最基本的包含每个log文件多大,最多几个log文件,log文件总大小达到多少时开始覆盖之前的文件等,其实这些方面大神的源码都考虑好了,直接用轮子更省心。
4. 冒烟测试
发布之前需对预发布版本进行主流程的测试,例如对于一个即时通信SDK需要测试普通消息互通、文件类消息互通、离线消息收取、apns推送正常、群消息正常等。
p.s. 本文提到的一些[命令]将在后面文章中单独讲一下。