到webank以后一直在做sdk相关的开发,包括微动力,云客服,以及云刷脸项目.其中遇到一些常见的sdk中开发应该避免的误区,以及一些可能会踩到的坑.
重中之重的前缀
iOS中使用OC开发,由于OC没有命名空间概念,很大问题是重名问题.有以下几个地方都需要注意添加前缀,前缀一定要有辨识度,类名、宏定义、枚举、通知、类别等命名时加静态库统一特殊前缀,以避免命名冲突:
- 自定义的类: WBFaceSDKxxxxxxClass
- category的方法名最好添加: wbfacesdk_xxxxMethod()
- 对于项目中的c、c++中的方法,需要加前缀,全局c方法: WBFaceSDKCGRectXXXX()
- 全局常量,包括字符串等等:常见的有通知名称WBFaceSDKxxxxxNotification
- 使用typedef/NS_ENUM/NS_OPTIONS定义的struc或enum的名称
第三方公用库或静态库不要打入SDK进行编译
我们常用的网络库使用AFNetworking,SDWebImage,虽然引入到项目中,但是.m不要勾选"target membership"选项,只需要引入.h就OK.或者通过cocoapod,创建sdk工程,通过cocopads管理项目依赖.
常见的第三方框架,比如libssl.a,opencv.a,libcrypto.a等等,拖入工程的时候"Add to Target"(taget membership)都不要打勾.也就是sdk对这些公共库有依赖关系,但是最终生成的sdk不包含公共库的源码文件.
尽量减少使用Category
iOS中Category使用的很频繁,如果静态库里面使用了category,由于OC的runtime和静态度的静态资源的加载问题.需要进行以下操作:
那么需要在Build settings
->Other Linker Flags
中添加-ObjC
参数,解决运行时unrecognized selector send to instance
的crash错误.
此外,如果在category的.m文件中只有一个category分类,此时需要额外添加参数-all_load
或者使用-force_load /path/to/sdk
来强制加载sdk.为了不使用这个参数,最好的方法是在.m文件中,写一个空的Class来占位:
// Use dummy class for category in static library.
#ifndef DUMMY_CLASS
#define DUMMY_CLASS(name) \
@interface DUMMY_CLASS_ ## name : NSObject @end \
@implementation DUMMY_CLASS_ ## name @end
#endif
//使用示例:
//UIColor+YYAdd.m
#import "UIColor+YYAdd.h"
DUMMY_CLASS(UIColor+YYAdd)
@implementation UIColor(YYAdd)
...
@end
使用
-all_load
或者-force_load xxx
在编译期间非常耗时.
为了减少耗时,本人是将通用的类创建成常用的全局函数,static函数,inline函数(注意添加前缀)
支持通用平台arm,x86等
我们可以通过如下命令查询具体.a或者framework文件支持的平台:lipo -info /path/to/.a
or lipo -info /*.framework/xxx
.
平时项目开发中,可能使用第三方提供的静态库.a,如果.a提供方技术不成熟,使用的时候就会出现问题,例如:
- 在真机上编译报错:
No architectures to compile for (ONLY_ACTIVE_ARCH=YES, active arch=x86_64, VALID_ARCHS=i386)
. - 在模拟器上编译报错:
No architectures to compile for (ONLY_ACTIVE_ARCH=YES, active arch=armv7s, VALID_ARCHS=armv7 armv6)
.
要解决以上问题,就要了解一下Apple移动设备处理器指令集相关的一些细节知识。
通常我们生成.a文件或者static framework默认配置的Valid Architectures
支持arm64,armv6,ramv7s.sdk也就只能在真机上跑,如果需要在模拟器中跑,需要使其支持x86_64``i386
.
Xcode中指令集相关选项(Build Setting中):
- Architectures: 工程被编译成可支持哪些指令集类型,而支持的指令集越多,就会编译出包含多个指令集代码的数据包,对应生成二进制包就越大,也就是ipa包会变大.默认为 Standard architectures(armv7,arm64)
- Valid Architectures:限制可能被支持的指令集的范围,也就是Xcode编译出来的二进制包类型最终从这些类型产生,而编译出哪种指令集的包,将由Architectures与Valid Architectures(因此这个不能为空)的交集来确定.
例如:Valid Architectures设置的支持arm指令集版本有:armv7/armv7s/arm64,对应的Architectures设置的支持arm指令集版本有:armv7s,这时Xcode只会生成一个armv7s指令集的二进制包。
- Build Active Architecture Only:指定是否只对当前连接设备所支持的指令集编译.当这个属性设置是yes时候,一般为了debug速度更快,只编译当前的architecture版本.默认debug:yes, release:no.
在我们制作sdk时候,要做到比较大的兼容性(iPhone 4,iOS7以上),可以进行如下设置:
- ValidArchitectures设置为:armv7|armv7s|arm64|i386|x86_64
- Architectures设置不变(或根据你需要): armv7|arm64
然后分别选择iOS设备和模拟器进行编译,最后找到相关的.a进行合包.(xctool或者pod帮助打包).如果是手动创建framework,那么使用如下脚本
if [ "${ACTION}" = "build" ]
then
INSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME}.framework
DEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework
SIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
#ditto "${DEVICE_DIR}/Headers" "${INSTALL_DIR}/Headers"
lipo -create "${DEVICE_DIR}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"
open "${DEVICE_DIR}"
open "${SRCROOT}/Products"
fi
同时,如果第三方库不支持模拟式尽量使用预编译指令,屏蔽第三方库:
#if TARGET_IPHONE_SIMULATOR//模拟器
#elif TARGET_OS_IPHONE//真机
#endif
arm64:iPhone5S以上| iPad Air| iPad mini2(iPad mini with Retina Display)
armv7s:iPhone5|iPhone5C|iPad4(iPad with Retina Display)
armv7:iPhone3GS|iPhone4|iPhone4S|iPad|iPad2|iPad3(The New iPad)|iPad mini|iPod Touch 3G|iPod Touch4
i386 :5及以下模拟器
x86_64:5s及以上模拟器
Debug模式,必要Log输出与crash日志收集
在sdk中要提供完整的Log信息的输出,尤其是错误日志,并且如何处理这个错误的步骤.当要收集crash日志时,可以通过系统NSException中提供的NSSetUncaughtExceptionHandler和NSGetUncaughtExceptionHandler方法,收集一些简单的crash日志信息,然后将收集到的crash日志同步到服务端.
具体如何进行Log模块的设计,crash日志简单上报暂时没有很好的方案.
涉及UI界面问题
需要考虑到界面旋转带来的潜在问题.布局时候尽量使用autolayout.对于必须使用硬编码的地方才使用硬编码.
对于硬编码,获取屏幕的宽度和高度,与普通的位置不一样:
#define ScreenHeight MAX([[UIScreen mainScreen] bounds].size.height,[[UIScreen mainScreen] bounds].size.width)//获取屏幕高度,兼容性测试
#define ScreenWidth MIN([[UIScreen mainScreen] bounds].size.height,[[UIScreen mainScreen] bounds].size.width)//获取屏幕宽度,兼容性测试
所有的viewController的BaseVC中最好增加以下方法仅支持竖屏:
#pragma mark - viewController orientation
- (UIInterfaceOrientationMask)supportedInterfaceOrientations//支持哪些方向
{
return UIInterfaceOrientationMaskPortrait;
}
/**
初始化自己的方向, 这个在旋转屏幕时候非常重要
If you do not implement this method, the system presents the view controller using the current orientation of the status bar.
说明如果我们没有override这个方法,系统会根据当前statusbar来决定当前使用的orientation
*/
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation//默认显示的方向
{
return UIInterfaceOrientationPortrait;
}
/**
如果返回NO,则无论你的项目如何设置,你的ViewController都只会使用preferredInterfaceOrientationForPresentation的返回值来初始化自己的方向,如果你没有重新定义这个函数,那么它就返回父视图控制器的preferredInterfaceOrientationForPresentation的值。
*/
- (BOOL)shouldAutorotate//是否支持旋转屏幕
{
return NO;
}
所有资源提供到bundle中,命名问题
...
sdk测试完整性
...
SDK中多次使用缓存状态问题
在调用SDK时,一定要先清理SDK原有的配置状态,缓存状态等等!!!!
参考文献
写iOS SDK注意事项
iOS 如何创建和使用静态库
IOS生成同时支持armv7,armv7s,i386,x86_64,arm64的静态库.a文件
iOS开发~制作同时支持armv7,armv7s,arm64,i386,x86_64的静态库.a
apple官方文档-制作framework
iOS开发——创建你自己的Framework
用lipo合并模拟器Framework与真机Framework