避免 iOS 組件依賴衝突的小技巧

問題原因

本文以 YBImageBrowser 組件舉例。html

YBImageBrowser 依賴了 SDWebImage,在使用 CocoaPods 集成到項目中時,可能會出現一些依賴衝突的問題,最近社區提了多個 Issues 而且在 Insights -> Traffic -> Popular content 中看到了此類問題很高的關注度,因此不得不着手解決。git

嚴格的版本限制

一個開源組件的迭代過程當中,保證上層接口的向下兼容就不錯了。爲了優化性能而且控制內存,YBImageBrowser 沒有直接用其最上層的接口,而是單獨使用了下載模塊和緩存模塊,SDWebImage 的迭代升級很容易致使筆者的組件兼容不了,因此以前一直是相似這樣依賴的:github

s.dependency 'SDWebImage', '~> 5.0.0'
複製代碼

這樣作的好處是限制足夠小版本範圍,下降 SDWebImage 接口變更致使組件代碼錯誤的風險。但若是 SDWebImage 升級到 5.1.0,無論相關 API 是否變更,CocoaPods 都視爲依賴衝突。web

其它組件依賴了不一樣版本的 SDWebImage

當兩個組件依賴了同一個組件的不一樣版本,而且依賴的版本沒有交集,好比:json

A.dependency 'SDWebImage', '~> 4.0.0'
B.dependency 'SDWebImage', '~> 5.0.0'
複製代碼

那麼 A 和 B 同時集成進項目會出現依賴衝突。緩存

解決方案

使用 CocoaPods 集成項目很是便捷,對於組件使用者來講,老是想在任何場景下都能輕易集成,而且能在未來享受組件的更新優化,顯然前面提到的問題可能會影響集成的便捷性。bash

更模糊的版本限制

不少時候一個大版本的組件不會改動 API,而且對於社區流行的組件咱們能夠寄必定但願於其作好向下兼容,因此放寬依賴的版本限制能覆蓋未來更多的版本(規則參考:podspec dependency):ide

s.dependency 'SDWebImage', '>= 5.0.0'
複製代碼

爲何不乾脆去掉版本限制呢? 由於 YBImageBrowser 3.x 是基於 SDWebImage 5.0.0 開發的,筆者能夠明確不兼容 5.0.0 以前的版本,因此在 SDWebImage 未來迭代版本出現相關 API 不兼容以前,這個限制都是「完美」覆蓋全部版本的。性能

避免依賴衝突的暴力方案

當有其它組件依賴了不一樣版本的 SDWebImage,粗暴的解決方案以下:優化

  • 直接修改其它組件依賴的 SDWebImage 版本。
  • 將 YBImageBrowser 手動導入項目,而且修改代碼去適應當前的 SDWebImage 版本。
  • 社區朋友一個 Issue 中提到的方法:在 ~/.cocoapods/repos 目錄下找到 YBImageBrowser 文件夾,更改對應版本的 podspec.json 文件裏對 SDWebImage 的依賴版本。

顯然,上面的幾種方案不太優雅,手動導入項目難以享受組件的更新優化,修改本地 repo 信息會由於 repo 列表的更新而復位。

避免依賴衝突的優雅方案

出現依賴衝突是必需要解決的問題,其它組件依賴的版本限制能夠視爲不變量,解決方案能夠從組件的製做方面考慮。

要作到的目標是,既知足部分用戶快速集成組件,又能讓部分用戶解決依賴衝突的前提下保證能享受組件未來的更新優化。

答案就是subspec,如下是 YBImageBrowser.podspec 部分代碼(完整代碼):

s.subspec "Core" do |core|
    core.source_files   = "YBImageBrowser/**/*.{h,m}"
    core.dependency 'SDWebImage', '>= 5.0.0'
  end
  s.subspec "NOSD" do |core|
    core.source_files   = "YBImageBrowser/**/*.{h,m}"
    core.exclude_files  = "YBImageBrowser/WebImageMediator/YBIBDefaultWebImageMediator.{h,m}"
  end
複製代碼

由此,用戶能夠自由的選擇是否須要依賴 SDWebImage,在 Podfile 裏的觀感大體是這樣:

// 依賴 SDWebImage
pod 'YBImageBrowser'  
// 不依賴 SDWebImage
pod 'YBImageBrowser/NOSD'
複製代碼

那麼在 YBImageBrowser 代碼中應該如何區分是否依賴了 SDWebImage 而且提供默認實現呢?

第一步是設計一個抽象接口(這個接口不依賴 SDWebImage):

@protocol YBIBWebImageMediator <NSObject>
// Download methode, caching methode, and so on.
@end
複製代碼

第二步是在YBImageBrowser.h中定義一個遵循該接口的屬性:

/// 圖片下載緩存相關的中介者(賦值可自定義)
@property (nonatomic, strong) id<YBIBWebImageMediator> webImageMediator;
複製代碼

第三步是實現一個默認的中介者(這個類依賴了 SDWebImage):

@interface YBIBDefaultWebImageMediator : NSObject <YBIBWebImageMediator>
@end
@implementation YBIBDefaultWebImageMediator
//經過 SDWebImage 的 API 實現 <YBIBWebImageMediator> 協議方法
@end
複製代碼

第四步是在內部代碼中經過條件編譯導入並初始化默認中介者:

#if __has_include("YBIBDefaultWebImageMediator.h")
#import "YBIBDefaultWebImageMediator.h"
#endif
...
#if __has_include("YBIBDefaultWebImageMediator.h")
    _webImageMediator = [YBIBDefaultWebImageMediator new];
#endif
複製代碼

第五步在 YBImageBrowser.podspec 中也能夠看到,在不依賴 SDWebImage 的集成方式時排除了兩個文件:YBIBDefaultWebImageMediator.{h.m}

由此便實現了目標:

  • 用依賴 SDWebImage 的集成方式快速集成。
  • 使用不依賴 SDWebImage 的集成方式避免各類狀況下的依賴衝突,但注意這種狀況須要自行實現一個遵循<YBIBWebImageMediator>協議的中介者。

以上即是避免依賴衝突的小技巧,但願讀者朋友能提出更好的建議或意見😁。

相關文章
相關標籤/搜索