特別感謝團隊小伙伴(李龍龍、崇慶旭)
越小的團隊求變化耘柱,越大的團隊求穩(wěn)定猎物。作為一個小團隊我們又開始折騰了。不用再贅述為什么要轉(zhuǎn)swift淮椰。唯一能阻止我們的問題是:
target has transitive dependencies that include static binaries
這個問題來自于使用cocoapods組件化開發(fā)五慈。使用swift時必須use_frameworks!
,然后我們不得不依賴一些第三方庫主穗,這些第三方庫是static library(哪些庫就不點名了泻拦,大家心里都應(yīng)該有數(shù))。pod install
時就能看到上面的內(nèi)容了黔牵。
本來想先分析下問題的原因的,但是怕各位看官沒有心思看爷肝。所以先講解決猾浦,再分析吧陆错。分別有上中下策任君選擇。
上策
等cocopods官方的1.4.x正式版金赦,會提供static_framework
CocoaPods version 1.3.1 and earlier do not support static framework dependencies.CocoaPods 1.4.0 adds the
static_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
截止這篇文章寫的時候音瓷,還不能用。
上策的好處是官方的夹抗,改動最小绳慎。
中策
如果你等不起官方,可以使用這個方案漠烧。這個方案寫的非常非常之詳盡杏愤,你一定能額外收獲不少知識。
這個問題的思路簡單講就是用一個動態(tài)庫吸附一個靜態(tài)庫已脓,然后因為動態(tài)庫的隔離性巧妙地解決了這個問題珊楼。
親測,可以度液。但是在做的時候遇到了小問題厕宗,分享一下。
第一個問題是出現(xiàn)了warning:
Missing sub module
可能是我們理解能力有問題堕担,一開始沒有解決已慢。特此分享一下,也許大家一看就明白怎么做了霹购。
比如包裝了一個XXX.framework佑惠。里面的Headers,有一個XXX.h厕鹃。需要在XXX.h中導(dǎo)入其他頭文件兢仰。
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>
第二個問題是:
直接發(fā)cocopods庫發(fā)出來,千萬不要直接放入到一個組件項目中直接用剂碴。這樣會找不到頭文件把将。
中策的好處是還可以用組件化開發(fā)的方式依賴包裝過的第三方庫。改動也很小忆矛,但是需要做很多體力活包庫發(fā)庫察蹲。
下策
我們發(fā)現(xiàn)在主項目的podfile依賴第三方庫(static library),用use_framework!不會有問題催训,原因后面分析洽议。所以我們想把這些第三方庫都放在主項目(App)中。要解決的是其他組件如何使用這些第三方庫漫拭。
其實思路很簡單亚兄,就是組件化開發(fā)中的業(yè)務(wù)組件相互隔離通過中間件通訊。我們用的是協(xié)議的方式采驻。用協(xié)議的好處是比較優(yōu)雅审胚,協(xié)議方法等照抄第三庫就可以了匈勋。
首先。我們創(chuàng)建了一個ModuleManager類取名叫做NBUtilProtocolManager,這個manager主要負責(zé)收集協(xié)議和下發(fā)協(xié)議膳叨。這個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創(chuàng)建完成后發(fā)布洽洁,作為基礎(chǔ)組件給個個業(yè)務(wù)組件使用。
有個這個manager以后菲嘴,我們就開始定義一個協(xié)議NBShareSDKProtocol.h這個協(xié)議里面有一個分享方法饿自,協(xié)議里面的方法無論是返回值還是參數(shù)都是我們自己組件能識別的 和shareSDK沒有關(guān)系.代碼如下
- (nonnull RACSignal *)shareContentWithInfo:(nonnull NSDictionary *)info;
協(xié)議定義完成后發(fā)布,作為基礎(chǔ)組件給個個業(yè)務(wù)組件使用龄坪。
這個時候我們就開始定義一個NBShareSDKObject的類來實現(xiàn)協(xié)議中的方法昭雌。代碼如下
@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將這個協(xié)議的實現(xiàn)者給加入進去。load 方法會在加載類的時候就被調(diào)用悉默,也就是 ios 應(yīng)用啟動的時候城豁,就會加載所有的類,就會調(diào)用每個類的 + load 方法抄课。在這個時機注入?yún)f(xié)議的實現(xiàn)者是比較合適的時機唱星。這樣的話在項目啟動的時候manager里面就已經(jīng)包含了這個協(xié)議的實現(xiàn)者。協(xié)議的實現(xiàn)都在主項目里面完成.這樣所有的準備工作都已經(jīng)完成跟磨。接下來就是如何使用的問題间聊。
在業(yè)務(wù)組件的podfile中我們要依賴NBUtilProtocolManager和NBShareSDKProtocol這個自己發(fā)布的庫。然后在代碼中導(dǎo)入頭文件后按照如下代碼:
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最終發(fā)出來僵蛛。
中策是用skip-import-validation
尚蝌。
下策的方案本質(zhì)是隔離。不經(jīng)感嘆組件化開發(fā)的本質(zhì)也是隔離充尉。代碼隔離飘言,邏輯隔離,業(yè)務(wù)隔離驼侠,人員隔離姿鸿,開發(fā)隔離,發(fā)布隔離倒源。對內(nèi)自洽苛预,對外隔離。
分析
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鮮明的指出了"我們應(yīng)當(dāng)拒絕這種情況"。最后一段講了:對于vendored_libraries(.a),我們可以直接判斷文件后綴名昔馋,對于framework芜繁,需要檢查是statically or dynamically。我們?nèi)绻枰约簷z查的話可以使用file xxx.framework/xxx
第一段的問題用圖來講解
還有個問題是绒极, 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。
圖不一定很準確周拐,大概是這個意思铡俐。
那為什么動態(tài)庫依賴動態(tài)庫就可以呢?具體理論可以參考這篇博文(也就是中策)中說的動態(tài)庫的隔離性妥粟,和動態(tài)庫在鏈接時不copy而是在啟動時交給dyld审丘。
為什么中策可以呢?來個圖勾给。
最后
蘋果官方在XCode9支持了static swift library滩报。這說明swift版本已趨于穩(wěn)定了。附上鏈接在Building and Linking章節(jié)播急。也許也是因為這一點cocoapods官方也開始想要提供static_framework
這個配置吧脓钾。
沒有什么點可以阻止我們了,讓我們開始寫swift吧桩警。