引子html
无心间,看到5年前,Android大佬子勰写的,关于SDK开发方面的文章(SDK那些事(总纲)), 不禁得唤起本身开发iOS SDK的回忆;本文简单总结下本身开发SDK方面的经验;前端
SDK(Software Development Kit)能够最大程度实现代码和功能的复用,为业务开发提供一个很是好的支持;这里的业务能够是内部业务,也能够是外部业务;android
简单来讲,所谓SDK开发,本质是服务提供;不只须要写好代码,还要完善代码以外的事情,任重道远git
通常而言,新起一个SDK必然有其深入的业务背景;研发同窗对SDK要解决的问题和SDK的特殊要求,了解地越详细越好;常见的要求有:github
XX
KB:【其余】 SDK可能长期维护 或 多人开发,确立好基本代码规范,能保障SDK的代码质量;这些规范本质上是一些共识和约束,如:面试
SDK中可能包含不一样的模块功能,而不一样的业务方须要的模块可能不一样;对SDK中模块进行拆分,保证业务方尽量引入的是他们须要的代码;跨域
通常使用Cocoapods建立Pod库的,在podspec
中定义好模块,为每一个模块清晰定义好包含的代码和资源,以及外部依赖(静态库 or 静态库);这样能够将模块之间实现代码和资源的物理隔离;缓存
关于Cocoapods建立Pod库更多细节能够参考Cocoapods使用小记,至因而公有Pod仍是私有Pod根据实际状况定;安全
建立公有Pod库或者私有Pod库原理是同样的;不同的是:二者的版本索引查询方式不同,公有库的podspec由CocoaPods/Specs管理,而内部私有使用的pod库须要公司内部创建一个仓库来管理podspec微信
Pod库中,代码放在Classes
目录下,图片资源放在Assets
目录下;
Classes
目录按模块划分第一级目录,如AModule
、BModule
、CModule
等,其中每一个模块Code再划分二级目录,如ModuleService、View、Controller、Model、API等;具体的代码文件存放在这些二级目录中;其中ModuleService
中代码是要对外暴露的,其余预期外部不可见;
资源方面,也按模块细节;主要的资源是图片资源,在Assets
目录下新加AModule.xcassets
、BModule.xcassets
、CModule.xcassets
等;
SDK使用者们关注接口是够好用,设计好接口,让你的SDK使用体验加分;
接口功能尽可能职责单一,接口须要的参数不要太多;若是参数多,可使用Model将业务参数封装下;提供Model的default实现
;接口名,参数名使用驼峰命名,最好见名知义;
接口中每一个参数类型要明确,不要出现id
、NSDictionary
这样的类型;避免业务随意传参,增长SDK内部对参数校验难度;也减小业务方对参数的困惑;
接口内第一件事情是要作参数校验,不符合预期状况,Debug模式直接Assert,及早把问题抛出;Release模式要记录到日志并上报,提早返回,避免后续出现迷惑问题,增长排查问题成本;
@optional
关键字,没有的话默认要实现;协议和协议中具体方法的做用要增长上注释;NS_ENUM
)定义,对应的每一个值增长注释;WXErrCode
不要透传给业务方,封装一下,对外暴露的是咱们的状态码;podspec
中的version
、代码中的版本号;能够实现个脚本,在编译时候,统一修改这三处的版本号信息;开发SDK和开发完整项目同样,要有需求评审、技术评审、排期,开发,自测,提测、测试验收等环节 ;不一样功能在不一样feature分支上开发,每一个feature功能验证经过后能够合入主干分支;合入主干后,由主干分支发布版本;
发布的SDK使用二进制的形式;SDK使用二进制形式,不只能提示项目项目编译速度,也能保护好源码;若是业务方须要SDK源码,须要向SDK负责人申请权限;
Code Review
;其实这对业务方是个很高的要求,须要业务方至少有一我的对SDK有比较全面的了解;autorealsepool
,下降内存峰值,避免 OOMNSCache
代替 NSMutableDictionary
,使用NSPurgableData
代替NSData
;weak strong dance
来解决 Block 中的循环引用,代理(delegate)使用weak
修饰;+ (instancetype)sharedInstance
,内部使用dispatch_once
保证alloc和init只执行一次,这种是粗发式单例
,并不能保证绝对单例;+(instancetype)allocWithZone
、-(instancetype)copyWithZone
和-(instancetype)mutableCopyWithZone
方法,保证永远都只会分配一次内存空间,实现真正的单例;do{...}while(0)
构造的宏定义不会受到大括号、分号等的影响,很是建议使用。__attribute__ ((weak))
属性将其变成weak symbol;weak symbol在连接时候比较特殊:
weak symbol 不作标准方案推荐; 遇到要临时适配某些业务的特殊case,时间紧急状况下,能够"剑走偏锋";
简介:有些公开功能使用宏定义的函数形式,能够在函数中带上__FILE__
、__LINE__
和__FUNCTION__
这些C语言中预约义符;这样能够在发生问题时,更好找到使用宏函数的位置,demo以下:
#define AddFunc(a,b) do { \ addFuncImp(a,b, __FILE__,__LINE__,__FUNCTION__); \ } while(0)
有人问过:为何不使用[NSThread callStackSymbols]
获取当前线程堆栈信息,岂不是更好;不使用有3方面考虑:
[NSThread callStackSymbols]
捕获堆栈信息在符号裁剪状况下,主模块中的是内存地址信息,而不是符号信息;__FILE__
、__LINE__
和__FUNCTION__
的成本更低,性能更好;section()
函数简介:section()
函数是Clang提供的,能够读写二进制段;实际应用中,在编译阶段将一些肯定的常量写入数据段(__DATA段
),并在运行期根据须要读取出来;能够利用此能力实现延迟加载;
在阿里的iOS的BeeHive有相似的使用,以下:
#define BeeHiveMod(name) \ char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name""; #define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))
used
关键词是告诉编译器,Release下不优化,必须保留这个符号;不然Release
下。连接器会优化掉没有引用的符号;
简介:App启动耗时通常统计pre-main
阶段(T1),和main()
函数以后到didFinishLaunchingWithOptions
方法执行完这段(T2)阶段;SDK中能够利用__attribute__ ((constructor))
、__attribute__((destructor))
这两个函数属性在pre-main
和after_main
时机作一些事情;
使用这两个属性定义函数示范以下:
__attribute__((constructor)) void before_main_xxxx() { //can do something } __attribute__((destructor)) void after_main_xxxx() { //can do something }
须要说明的是:在pre-main
和after_main
时机,千万不要作耗时操做;在SDK(二进制形式)中使用比较隐蔽,通常状况下,业务方很难想到或注意到;若是在pre-main时机作了耗时的事情,宿主App启动体验就不太好了;
dyld加载过程分四步:
Load dylibs image
、Rebase/Bind image
、Objc setup
和initializers
;其中+load()
在__attribute__((constructor))
以前,他们都在initializers
阶段内完成;initializers
以后就是main
函数执行了;
Method Swizzling
是Objective-C中运行时特性之一,本质是在运行时交换方法实现(IMP);SDK有时候须要Method Swizzling
利用hook一些系统(Objective-C
)方法;Method Swizzling
的话,推荐使用RSSwizzle,他是线程安全的Method Swizzling方案,优点是:不须要在+load()
中实现方法交换 并且是 线程安全的;020 持续更新,精品小圈子每日都有新内容,干货浓度极高。
结实人脉、讨论技术 你想要的这里都有!
抢先入群,跑赢同龄人!(入群无需任何费用)
BAT大厂面试题、独家面试工具包,
资料免费领取,包括 数据结构、底层进阶、图形视觉、音视频、架构设计、逆向安防、RxSwift、flutter,
做者:南华Coder
连接:https://juejin.im/post/5e9adea16fb9a03c364f216e