美文网首页iOS SDK 开发收藏ionic
2017最新版iOS SDK制作 集成及上线攻略(非demo级

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

作者: 阡陌不言 | 来源:发表于2017-09-19 17:37 被阅读199次

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

    前一段时间因公司业务需要,提了这样一个需求:要把一个早期的项目(创建于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的关键坑,都已经罗列完毕!!!

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

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

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

    相关文章

      网友评论

      • 筑梦师Winston:我处理过一个环信视频聊天和腾讯云直播的冲突问题,他们当中对FFmpeg都有封装,两个SDK同时集成就会崩溃,所以我感觉SDK应该是调用自己包里封装的,但因为两个SDK封装了同一个FFmpeg单例对象,所以导致崩溃了。后来的解决方案是,腾讯云直播SDK将自己封装的FFmpeg全部做了差异处理(也就是对FFmpeg进行了重命名)
      • 无星灬:第4个问题,我理解的sdk其实可以互调的。。其实所谓的互调就是依赖关系,两个sdk有强依赖关系的,必须两个存在才能互调,就比如很多二次封装支付宝或微信支付的sdk包就对支付宝和微信支付的sdk有强依赖关系,这些二次封装库就可以调用支付宝的sdk等
      • Renbq:哈哈哈

      本文标题:2017最新版iOS SDK制作 集成及上线攻略(非demo级

      本文链接:https://www.haomeiwen.com/subject/coxwsxtx.html