iOS SDK应该注意的问题和tips

到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

你可能感兴趣的:(iOS SDK应该注意的问题和tips)