2017最新版iOS SDK制作 集成及上线攻略(非demo级别)

本文系原创,转载请注明出处,谢谢 !

前一段时间因公司业务需要,提了这样一个需求:要把一个早期的项目(创建于2013年,非本公司项目)整个做成SDK,集成到一个未知的项目里面去,而且还要上线.乍一听,这个好像也没什么难度,由于之前对静态库略有研究,于是爽快的跟老大说:能做!(当时我还没拿到源码,也不知道需求方到底想要如何对接,对于主工程更是一无所知).于是接下来两个月,本人由于自己的一时冲动,入了一个天坑,差点没爬出来.现在此项目已完结,在此把自己近2个月踩过的坑,做个总结,同时与大家分享下经验.

为了更好的梳理思路,我先在此抛出几个问题,大家一起思考:
  • 1.把一整个项目做成SDK,原来工程里面的哪些东西要去掉,AppDelegate还能要吗,.pch ,宏定义,Categary这些怎么处理?

  • 2.推送分享之类的功能还能用吗?

  • 3.如果源码里面用了百度地图,支付等第三方SDK,能正常的在我们自己的SDK里面调用并保证功能吗?(简言之,SDK里面能包含SDK吗)

  • 4.如果源码内容太多,模块划分清晰,我们要想按模块划分做成多个SDK,那我们自己做的SDK能互调吗?

  • 5.资源文件(图片、xib)怎么处理,源码里面有近500个,难道要一个个修改路径?

  • 6.如果原项目里面用.strings,.plist等一系列的本地文件,路径该如何加载?

  • 7.在对于主工程一无所知的情况下,库的兼容性问题如何解决?

  • 8.即使对接成功以后,SDK模块各个功能显示正常,可以成功打出ipa吗?

  • 9.即使成功的打出了ipa,可以成功的上传到AppStore吗?

  • 10.即使成功的上传到了AppStore,由于审核人员用iPad测试项目,如果因为SDK模块中使用了xib加载VC,而xib又没有适配iPad,导致VC界面加载失败,审核被拒,怎么办?

  • ......

没错,以上这些问题都遇到了,而且还不止这些.
首先,客观的说,SDK不是这么用的.

这个事情本身就是个变态的需求.其次SDK对于源码是有要求的,不是随随便便给你拉来一套代码,都能完美的把所有功能做成一个SDK,然后随便那个项目需要,就给哪个项目调(这简直万能啊,有木有) 也许有人会说了,支付宝、百度地图不就是这样吗?拜托,这些都是功能性的,没有哪个SDK是包含多个定制化界面和接口等等一堆东西的.可是,这又怎么样呢,自己挖的坑,跪着也要填完了.

前言到此结束,接下来我们言归正传.

(以上10个问题在下面都会给出答案)

(一) 方案

    1. 何种形式
      对于iOS而言,我的理解:
    • 静态库: .framework.a
      .framework = .a+bundle, so 我要做出来的,是个 XXX.framework
    • 其实.framework本质上也是一个bundle,只是把资源和二进制文件放在一起加载,主工程打包IPA的时候会有问题,如上问题8,所以这种方案我就略过了,免得误导大家
    1. 做成几个SDK
      一个! 无论主工程什么样,暴露一个接口控制器和几个属性,是最简单高效的调用方法.而且通过本人实践证明,我们自己做的SDK不能互调 !!!同时回答上面问题4
    1. 创建一个.framework工程,记得 创建以后 Build Setting-- Mach-O Type选择 Static Lib

我创建的 SDK 叫NewCityKit,测试SDK的测试工程为 testFramework,下文中皆以此为例

SDK_2.png

SDK-3.png

(二) 源码的准备

    1. 准备源码
    • 下图箭头指向的这些文件统统都不要(AppDelegate,Assets.xcassets,Base.lproj,main,Resource,info.plist )
    • 把项目里面除了这些以外的源文件,添加到你创建的SDK工程里面,
    • 另外,需要把项目用到的图片全部放到一个文件夹里面(如图一中的pic),方便打包成bundle时选择源文件
    • 建议源码里面用到的xib越少越好,如果像我遇到的这样,代码老旧,模块耦合性高,还非本公司的源码,对业务逻辑不熟还不给时间重构着急要上线的,那只能硬着头皮弄了(出坑的关键还在于对于工作量的准确评估和有一个给力的队友)
SDK-1.png
    1. NewCityKit 里面解决报错问题,目标: 编译通过
    • 在此回答问题1: AppDelegate ,.pch不能用,宏定义可以,分类也可以,但需要在主工程 Build Setting-- Other Linker Flags添加 -Objc、 -all -load
SDK-4.png
  • 问题2:本人认为只在SDK里面配置,这些功能实现的可能性基本为0,除非主工程配合
  • 问题3:可以,本人做项目期间已经把百度地图集成进SDK里面,并且可以正常调用,实践证明可行(但是这也说明那些开源成熟的SDK可以被我们自己制作的SDK包含,但是我们自己做的就不行,这个本人目前还未想通,或许是都可以,还需要更多的尝试).
  • 在源码较多的情况下,要进行到编译通过这一步,还是颇费周折的,具体的问题有很多,比如MRC问题啦,.m重复或者找不到啦等等的,总之根据Xcode报错信息去解决,基本都没什么问题的

(三) 资源的处理

    1. 处理资源文件 (本人创建的资源bundle 名为NewCityAsset,下文中都以此为例)
bundle-1.png
bundle-2.png
bundle-3.png
bundle-4.png
bundle-5.png
 * 在此回答`问题5`: 图片不需要一个个修改路径,但是xib需要
iOS的资源后缀都是@2X,@3X的格式,编译成为bundle以后,会变成.tiff格式,这样你原来写的路径图片名称后面必须在再上这个后缀才能取到,这样就又麻烦了,这个问题我们可以在资源的target修改一个地方,就可以完美解决,如下图:
bundle-6.png
  • 图片用以下代码做处理
写 一个加载bundle的工具类

#import "BundleTools.h"
#define BUNDLE_NAME @"NewCityAsset"

@implementation BundleTools
+ (NSBundle *)getBundle{
    
    return [NSBundle bundleWithPath: [[NSBundle mainBundle] pathForResource: BUNDLE_NAME ofType: @"bundle"]];
}

+ (NSString *)getBundlePath: (NSString *) assetName{
    
    NSBundle *myBundle = [BundleTools getBundle];
    
    if (myBundle && assetName) {
        
        return [[myBundle resourcePath] stringByAppendingPathComponent: assetName];
    }
    
    return nil;
}
 

给UIImage写一个分类,用运行时替换掉系统的 imageNamed: 方法


#import "UIImage+load.h"
#import "BundleTools.h"
#import <objc/runtime.h>
@implementation UIImage (load)

+(void)load{

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
        Method m2 = class_getClassMethod([UIImage class], @selector(WB_imageNamed:));
        method_exchangeImplementations(m1, m2);
    });
}

+ (UIImage *)WB_imageNamed:(NSString *)name{

    UIImage *image = [UIImage WB_imageNamed:name];
    
    if (image) {
        return image;
    }else{
        return [UIImage imageNamed:name inBundle:[BundleTools getBundle] compatibleWithTraitCollection:nil];
    }

}

至此,图片资源处理完毕.

  • xib 分为2种:
    • 显示cell的xib(此种没有更好的办法,只能替换[NSBundle mainBundle] 为 [BundleTools getBundle]
  
//替换前
    [self.collectionView registerNib:[UINib nibWithNibName:@"CollectionViewCell" bundle: [NSBundle mainBundle]] forCellWithReuseIdentifier:@"CollectionViewCell"];
//替换后
    [self.collectionView registerNib:[UINib nibWithNibName:@"CollectionViewCell" bundle:[BundleTools getBundle]] forCellWithReuseIdentifier:@"CollectionViewCell"];
 
  • 显示与VC同名view的xib (此种可以用以下代码来解决,原理同样是运行时替换系统方法)
#import "UIViewController+Bundle.h"
#import "BundleTools.h"
#import <objc/runtime.h>

@implementation UIViewController (Bundle)

+(void)load{
    
    Method m1 = class_getInstanceMethod([self class], @selector(init));
    Method m2 = class_getInstanceMethod([self class], @selector(v_init));
    method_exchangeImplementations(m1, m2);
    
}
- (instancetype)v_init {
    
    NSString *path = [[BundleTools getBundle] pathForResource:NSStringFromClass([self class]) ofType:@"nib"];
    
    if (path == nil)
        return [self v_init];
    else
        return [self initWithNibName:NSStringFromClass([self class]) bundle:[BundleTools getBundle]];
    
}

至此,资源文件处理完毕,但是mainBundle的坑到此还不算完......

  • 接着,我们用上面的问题6,来引出bundle的坑
    先上图本地配置文件:
SDK-5.png
SDK-6.png
  • 先说 .strings,如果.string是以这样的形式加载的,我们直接就简单粗暴,直接把这个文件拖入宿主工程,什么都不用改,.plist同理
 #define mLocalization(key, ...)     [NSString stringWithFormat: [[NSBundle mainBundle] localizedStringForKey:key value:@"" table:@"Localization"], ##__VA_ARGS__, nil]
  • 但是如果你不想这样加载(不想向主工程暴露太多东西),你想加载SDK包里面的那个本地文件,那么,就要像加载图片那样,先找到mainBundle,再找到.framework,再找到这个文件,大体是这么个路径

// 注意:二进制文件的路径,从这里找 @"NewCityKit.framework" 
NSBundle *bundle = [NSBundle bundleWithPath:[[NSBundle mainBundle]pathForResource:@"NewCityKit.framework" ofType:nil]];
NSString* path = [bundle pathForResource:@"APIDecryptConfig" ofType:@"plist"];

// 资源的路径,从这找 @"NewCityAsset.bundle"
NSBundle *bundle = [NSBundle bundleWithPath:[[NSBundle mainBundle]pathForResource:@"NewCityAsset.bundle" ofType:nil]];
NSString* path = [bundle pathForResource:@"new_loading_black" ofType:@"gif"];

// 这个非常关键,千万不要弄混了!!!

至此,问题6告一段落

  • 然后我们来说一下用运行时替换系统方法 imageNamed:造成的坑
    • 这个当时和主工程对接时直接导致的后果是:由于SDK和主工程都用了MJRefresh,直接造成两边的刷新的那个提示文字变成了英文的,如图
SDK-7.png

这个问题的解决方案也有2个:

  • 1.简单粗暴的,单独把里面的.strings 文件在主工程重新拖一份,就是这个东西
SDK-8.png
  • 2.我们来看一下MJ 是怎么加载这个bundle的

 + (instancetype)mj_refreshBundle
{
    static NSBundle *refreshBundle = nil;
    if (refreshBundle == nil) {
        // 这里不使用mainBundle是为了适配pod 1.x和0.x
        refreshBundle = [NSBundle bundleWithPath:[[NSBundle bundleForClass:[MJRefreshComponent class]] pathForResource:@"MJRefresh" ofType:@"bundle"]];
    }
    return refreshBundle;
}

我们再来看一下这个东西最终出现在了哪里

SDK-9.png

对,是. framework!!! 而不是我们自制的.bundle大家知道怎么做了吗,原理同上,不再一一赘述
至此,bundle的坑基本罗列完毕,在我集成SDK期间,这是出bug最多的地方,也是困扰时间最长的,解决的关键就在于找对路径,参考经典库,不得不说,MJ对于bundle的处理容错性还是很高的,值得我们深究一下原理

(四) 库的兼容性问题

终于进行到这个最大的坑:问题7

关于这个我又想抛出一个问题了:
  • 比如AFN,SDK和主工程都用了,那最终集成以后,主工程调用的是它自己的库还是SDK的库?
    其实这个问题我也不太确定,只能根据实际猜测一下,我觉得他是根据加载顺序来的,加载到SDK模块的时候,主工程的代码也优先调用SDK里面的库(这是库的版本不一样的情况下,如果一样,我也不知道了,反正一样的话不会报错)
    当时我们遇到这样一个问题:
    主工程用CocoaPods管理第三方库,版本都是最新的,而我们的SDK没有用pod ,库的版本不详,不过看样子像是13年下的,然后,合到一起,就崩到了sessionManager的get方法那里......(unrecognized selector sent to class) 我们的网络用的还是AFHTTPRequestOperation,AFHTTPSessionManager的get方法不一样,怎么办?
    • 改AFN方法? 改动较大,时间不够,隐患略高,而且SDK里面的网络请求嵌套了加解密,牵一发而动全身啊,不妥
    • 后经高人指点,找到了一个完美的解决办法:修改SDK里面AFHTTPSessionManager的类名和文件名! OC为面向对象的语言,类名改掉了,就不是同一个对象了,怎么也调不到我们的方法了吧,而且基本不影响我们原来的代码!简直完美!!!
最后,关于这个问题做个小结:在SDK和主工程不是同一拨人在临近时间段内开发的情况下,除非SDK源码有时间重写或者主工程照着SDK库的版本来用,否则统一库的版本几乎是不可能的,那么这时解决兼容性问题我觉得可以用上述方法,简单而高效.
但是,SDK工程和主工程能否共用一套第三方库呢,本人没有尝试pod是否可以用于SDK工程,如果可以的话,那么这个猜想是可行的.本人认为,这是一个比较理想的集成SDK的方案,避免了库版本不统一的问题,同时大大减小了主工程安装包的大小,但是这种方案需要在客观条件允许的情况下,才能实现

(五) 打包上线的问题

  • 1.打包ipa出错: Found an unexpected Mach-O header code: 0x72613c21
    这个错误请参考以下链接
    iOS 打包 "Found an unexpected Mach-O header code: 0x72613c21"报错

    • 原因基本上就是主工程的这个地方添加了二进制文件(copy bundle resources只能添加资源,不能添加二进制文件),导致打包失败
  • 2 . upload to AppStore 失败
    item 90171 ,item 90166,大家可以自行Google,资源NewCityAsset.bundle里有plist,可执行文件(.m,.h,.exe,.o等等),去掉就好了

    1. 审核被拒
      这个真没有啥好办法呢,老老实实修改xib(主要是和VC同名的xib),适配iPad,这也是一个比较坑的地方,如果用的地方太多的话,那就要哭了......
最后给大家看一下SDK集成以后的文件目录:
SDK-11.png

二进制文件和资源分开的,虽然.framework里面包含了.bundle,但是主工程里2个都要拖进来,为了能正常的在主工程添加资源(这个一定要添加,否则主工程调用SDK会出错的)

SDK-13.png
至此,本人将近2个月踩过的关于制作iOS SDK的关键坑,都已经罗列完毕!!!

本人绞尽脑汁编写了将近一天的时间,如果看完对你有帮助,请点个赞!

另外,关于如何暴露接口控制器,如何调用传参等问题,本人觉得比较简单,就不细说了,不会的同学请自行搜索,也欢迎大家加我的微信,随时交流探讨

文章如有错误之处,欢迎大家批评指正,谢谢 !

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,413评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 1. 我是谁? 我问自己。四周没有任何东西,始终没有。 好吧,那我就叫...迭涟吧。 ...
    边城十三少阅读 234评论 0 1
  • 文:双木林兮 在去往无锡的路上,我的脑海中一直萦绕着《雷雨》中周朴园的低吟:无锡是个好地方,不由得心中对它充满了无...
    双木林兮阅读 184评论 0 1