Let us write swift(解决use_framework!时第三方库是static library)

特别感谢团队小伙伴(李龙龙、崇庆旭)

越小的团队求变化,越大的团队求稳定。作为一个小团队我们又开始折腾了。不用再赘述为什么要转swift。唯一能阻止我们的问题是:

target has transitive dependencies that include static binaries

这个问题来自于使用cocoapods组件化开发。使用swift时必须use_frameworks!,然后我们不得不依赖一些第三方库,这些第三方库是static library(哪些库就不点名了,大家心里都应该有数)。pod install时就能看到上面的内容了。

本来想先分析下问题的原因的,但是怕各位看官没有心思看。所以先讲解决,再分析吧。分别有上中下策任君选择。

上策

等cocopods官方的1.4.x正式版,会提供static_framework

CocoaPods version 1.3.1 and earlier do not support static framework dependencies.CocoaPods 1.4.0 adds thestatic_framework option in #6811 that enables you to specify building a pod as a static_framework, which unlike dynamic frameworks, can have static framework dependencies.

链接参考1
链接参考2
截止这篇文章写的时候,还不能用。

上策的好处是官方的,改动最小。

中策

如果你等不起官方,可以使用这个方案。这个方案写的非常非常之详尽,你一定能额外收获不少知识。

这个问题的思路简单讲就是用一个动态库吸附一个静态库,然后因为动态库的隔离性巧妙地解决了这个问题。

亲测,可以。但是在做的时候遇到了小问题,分享一下。

第一个问题是出现了warning:
Missing sub module
可能是我们理解能力有问题,一开始没有解决。特此分享一下,也许大家一看就明白怎么做了。

比如包装了一个XXX.framework。里面的Headers,有一个XXX.h。需要在XXX.h中导入其他头文件。

XXX.framework
├── Headers
│   ├── XXX.h
│   ├── WXApi.h
│   ├── WXApiObject.h
│   └── WechatAuthSDK.h
├── Info.plist
├── XXX
├── Modules
│   └── module.modulemap
└── _CodeSignature
    └── CodeResources

XXX.h需要这样

#import <XXX/WXApi.h>
#import <XXX/WXApiObject.h>
#import <XXX/WechatAuthSDK.h>

第二个问题是:
直接发cocopods库发出来,千万不要直接放入到一个组件项目中直接用。这样会找不到头文件。

中策的好处是还可以用组件化开发的方式依赖包装过的第三方库。改动也很小,但是需要做很多体力活包库发库。

下策

我们发现在主项目的podfile依赖第三方库(static library),用use_framework!不会有问题,原因后面分析。所以我们想把这些第三方库都放在主项目(App)中。要解决的是其他组件如何使用这些第三方库。

其实思路很简单,就是组件化开发中的业务组件相互隔离通过中间件通讯。我们用的是协议的方式。用协议的好处是比较优雅,协议方法等照抄第三库就可以了。

下策.png

首先。我们创建了一个ModuleManager类取名叫做NBUtilProtocolManager,这个manager主要负责收集协议和下发协议。这个manager就提供两个方法在NBUtilProtocolManager.h中

#import <Foundation/Foundation.h>

@interface NBUtilProtocolManager : NSObject

+ (void)registServiceProvide:(_Nonnull id)provide forProtocol:(nonnull Protocol *)protocol;

+ (_Nonnull id)serviceProvideForProtocol:(nonnull Protocol *)protocol;

@end

在NBUtilProtocolManager.m中

#import "NBUtilProtocolManager.h"

@interface NBUtilProtocolManager ()

@property (nonatomic, strong) NSMutableDictionary *serviceProvideSource;

@end

@implementation NBUtilProtocolManager

+ (NBUtilProtocolManager *)sharedInstance
{
    static NBUtilProtocolManager * instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        _serviceProvideSource = [[NSMutableDictionary alloc] init];
    }
    return self;
}

+ (void)registServiceProvide:(id)provide forProtocol:(Protocol *)protocol
{
    if (provide == nil || protocol == nil)
        return;
    [[self sharedInstance].serviceProvideSource setObject:provide forKey:NSStringFromProtocol(protocol)];
}

+ (id)serviceProvideForProtocol:(Protocol *)protocol
{
    return [[self sharedInstance].serviceProvideSource objectForKey:NSStringFromProtocol(protocol)];
}

@end

manager创建完成后发布,作为基础组件给个个业务组件使用。

有个这个manager以后,我们就开始定义一个协议NBShareSDKProtocol.h这个协议里面有一个分享方法,协议里面的方法无论是返回值还是参数都是我们自己组件能识别的 和shareSDK没有关系.代码如下

- (nonnull RACSignal *)shareContentWithInfo:(nonnull NSDictionary *)info;

协议定义完成后发布,作为基础组件给个个业务组件使用。

这个时候我们就开始定义一个NBShareSDKObject的类来实现协议中的方法。代码如下

@interface NBShareSDKObject () <NBShareSDKProtocol>

@end

@implementation NBShareSDKObject

@synthesize defaultShareImageUrl;

+ (void)load
{
    [NBUtilProtocolManager registServiceProvide:[[self alloc] init] forProtocol:@protocol(NBShareSDKProtocol)];
}

- (nonnull RACSignal *)shareContentWithInfo:(nonnull NSDictionary *)info {
    return ....;
}

注意在这个类中的load方法中使用NBUtilProtocolManager将这个协议的实现者给加入进去。load 方法会在加载类的时候就被调用,也就是 ios 应用启动的时候,就会加载所有的类,就会调用每个类的 + load 方法。在这个时机注入协议的实现者是比较合适的时机。这样的话在项目启动的时候manager里面就已经包含了这个协议的实现者。协议的实现都在主项目里面完成.这样所有的准备工作都已经完成。接下来就是如何使用的问题。

在业务组件的podfile中我们要依赖NBUtilProtocolManager和NBShareSDKProtocol这个自己发布的库。然后在代码中导入头文件后按照如下代码:

    id <NBShareSDKProtocol> obj = [NBUtilProtocolManager serviceProvideForProtocol:@protocol(NBShareSDKProtocol)];

    [obj shareContentWithInfo:@{@"url" : @"www.baidu.com"}];

下策是保底方案。修改多,体力活多。

这个下策也是这个issue的下策解决方案

Pod lint fails when containing dynamic-frameworks without simulator architectures

这个问题的上策是等官方PR最终发出来。

中策是用skip-import-validation

下策的方案本质是隔离。不经感叹组件化开发的本质也是隔离。代码隔离,逻辑隔离,业务隔离,人员隔离,开发隔离,发布隔离。对内自洽,对外隔离。

分析

target has transitive dependencies that include static binaries

这个错误,owner早在2014.11.24在这个issue下进行了说明。

Static libraries and frameworks are only linked to the user target by classic CocoaPods. That approach will not work if we are building frameworks and have a vendored static library as a
because the linker expects all symbols to be resolved in this case.

Using -undefined dynamic_lookup or linking to both the dynamic framework and the user target could be solutions to pursue for the future, but as a first step, we should reject these cases.

To reject vendored_libraries we can simply use the file extension, for vendored_frameworks we have to check if the included binary is statically or dynamically linked.

owner鲜明的指出了"我们应当拒绝这种情况"。最后一段讲了:对于vendored_libraries(.a),我们可以直接判断文件后缀名,对于framework,需要检查是statically or dynamically。我们如果需要自己检查的话可以使用file xxx.framework/xxx
第一段的问题用图来讲解

transitive_dependencies.png

还有个问题是, umbrella header中有传递头文件。

I guess, that happens because public headers are imported by the umbrella header and will have implicit clang module exports. That also includes transitive imported headers. If the imported header is not within the same target, it can't probably be exported as nested module, if it isn't already part of a clang module itself.

为什么在podfile依赖中可以,在pod spec中不可以呢?因为pod spec中写dependency是transitive dependencies。


podfile_dependency.png

图不一定很准确,大概是这个意思。

那为什么动态库依赖动态库就可以呢?具体理论可以参考这篇博文(也就是中策)中说的动态库的隔离性,和动态库在链接时不copy而是在启动时交给dyld。

为什么中策可以呢?来个图。


solution.png

最后

苹果官方在XCode9支持了static swift library。这说明swift版本已趋于稳定了。附上链接在Building and Linking章节。也许也是因为这一点cocoapods官方也开始想要提供static_framework这个配置吧。

没有什么点可以阻止我们了,让我们开始写swift吧。

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