iOS開發中,咱們封裝SDK給第三方使用一般採用.a或.framework + .bundle的形式。相信封裝過這種帶bundle資源文件的SDK的同窗們必定都會遇到這樣一個小麻煩。那就是加載自定義Bundle裏的資源的代碼寫起來和咱們平時開發App時加載mainBundle裏的資源的代碼是不一樣的,前者寫起來要麻煩一些。css
若是你正在封裝帶資源的SDK,那我相信BundleLoader應該能夠幫助到你。它能夠幫你消除這種調用上的不一樣,你只須要簡單的調用兩個方法就能夠像加載App裏的資源那樣『無縫』的加載自定義Bundle裏的資源。既有代碼無需修改,後續代碼你也能夠繼續用最簡潔最熟悉的方式開發。git
項目地址: BundleLoadergithub
最近,本人碰到了這樣一個需求。我是作直播APP的,老闆要求我從APP裏把直播間相關的部分分離出來封裝成SDK給第三方使用,而且從此要作到SDK和APP可以同步開發,同步更新。bash
這種狀況下,這種調用不一樣對我來講就是個大麻煩了。 其一,直播間及相關部分的代碼量很是龐大,各類資源各類形式的調用,改起來很麻煩。 其二,改動了之後從此同步開發也是個麻煩。markdown
要解決這個問題,咱們先來看看代碼上會有何不一樣。好比圖片,咱們知道加載App主包裏的圖片代碼只須要簡單的一句:oop
UIImage *img = [UIImage imageNamed:@"pic"]; 複製代碼
而加載自定義Bundle裏的圖片則要麻煩一些:測試
NSString *path = [[NSBundle mainBundle] pathForResource:@"myBundle" ofType:@"bundle"]; NSBundle *bundle = [NSBundle bundleWithPath:path]; NSString *file = [bundle pathForResource:@"pic" ofType:@"png"]; UIImage *img = [UIImage imageWithContentsOfFile:file]; 複製代碼
或者簡化一點:spa
NSString *file2 = [[NSBundle mainBundle] pathForResource:@"myBundle.bundle/pic" ofType:@"png"]; UIImage *img2 = [UIImage imageWithContentsOfFile:file2]; 複製代碼
再簡化一點:code
UIImage *img3 = [UIImage imageNamed:@"myBundle.bundle/pic"]; 複製代碼
可是仍是都沒有mainBundle裏的簡單。因而,我就想,能不能不改代碼就能夠加載自定義Bundle裏的資源呢?方法確定有,OC強大的Runtime出馬,沒有搞不定的事情,哈哈。orm
BundleLoader的Demo裏目前測試了下列幾種狀況的自定義bundle資源無縫加載:
xib或storyboard裏用到的圖片和xcssets圖片也均可以正常顯示。 同時,Demo還提供了一個簡單的Framework + Bundle的工程模版,能夠供你們參考。
其餘資源,如CoreData模型,本地化字符串等應該也能夠加載,若是不行的話你們也能夠依葫蘆畫瓢,自行實現。
具體的實現其實並不複雜,最關鍵的一點是:我發現,App裏不論加載什麼類型的資源,調用什麼接口,系統內部都會去調用NSBundle的這個方法:
- (nullable NSString *)pathForResource:(nullable NSString *)name ofType:(nullable NSString *)ext;
複製代碼
這個方法就是突破口,咱們只要在這個方法上去想辦法,作文章,再用上靈活強大的Runtime,應該就能達到咱們的目的。
實現的步驟以下:
pathForResource:ofType:
方法上代碼:
@implementation BundleLoader + (void)initFrameworkBundle:(NSString*)bundleName { refCount++; NSBundle* bundle = objc_getAssociatedObject(self, NSBundleMainBundleKey); if (bundle == nil) { //獲取自定義資源Bundle的對象 NSString *path = [[NSBundle mainBundle] pathForResource:bundleName ofType:@"bundle"]; NSBundle *resBundle = [NSBundle bundleWithPath:path]; //把這個對象關聯到mainBundle對象上 objc_setAssociatedObject([NSBundle mainBundle], NSBundleMainBundleKey, resBundle, OBJC_ASSOCIATION_RETAIN_NONATOMIC); //把mainBundle對象的Class設爲自定義Bundle子類的Class object_setClass([NSBundle mainBundle], [FrameworkBundle class]); } } 複製代碼
@interface FrameworkBundle : NSBundle @end @implementation FrameworkBundle //系統底層加載圖片,xib都會進這個方法 - (nullable NSString *)pathForResource:(nullable NSString *)name ofType:(nullable NSString *)ext { NSBundle* bundle = objc_getAssociatedObject(self, NSBundleMainBundleKey); if (bundle) { NSString *path = [bundle pathForResource:name ofType:ext]; if (path) return path; } return [super pathForResource:name ofType:ext]; } 複製代碼
運行代碼,發現[UIImage imageNamed:@"crown"]
已經能夠拿到UIImage對象了。原覺得能夠打完收工了,結果高興的太早了。若是圖片在xcassets裏,那這樣調用仍是會失敗。 加載自定義Bundle的xcassets方法只能用下面的方法:
[UIImage imageNamed:name inBundle:bundle compatibleWithTraitCollection:nil]; 複製代碼
繼續折騰,此次該Method Swizzling大法上場了。還不瞭解這個黑魔法的能夠看這裏。咱們給UImage的imageNamed:
方法作了Method Swizzling。代碼以下:
@implementation UIImage (FrameworkBundle) #pragma mark - Method swizzling + (void)load { Method originalMethod = class_getClassMethod([self class], @selector(imageNamed:)); Method customMethod = class_getClassMethod([self class], @selector(imageNamedCustom:)); //Swizzle methods method_exchangeImplementations(originalMethod, customMethod); } + (nullable UIImage *)imageNamedCustom:(NSString *)name { //Call original methods UIImage *image = [UIImage imageNamedCustom:name]; if (image != nil) return image; NSBundle* bundle = objc_getAssociatedObject([NSBundle mainBundle], NSBundleMainBundleKey); if (bundle) return [UIImage imageNamed:name inBundle:bundle compatibleWithTraitCollection:nil];//加載bundle裏xcassets的圖片只能用這個方法 else return nil; } @end 複製代碼
先調用imageNamed:
獲取圖片,若是拿到則直接返回;失敗則調用imageNamed:inBundle:compatibleWithTraitCollection:
方法去獲取圖片,並傳入自定義Bundle對象。這樣Bundle裏的xcassets圖片也能夠簡單加載了。
至於xib和storyboard也是一樣的作法。
實現仍是比較簡單的,用到了三個Runtime方法,分別是:
objc_setAssociatedObject
object_setClass
method_exchangeImplementations
經過自定義的子類和自定義方法讓系統先從咱們的資源Bundle里加載文件,找不到再去主包里加載。
若是這個庫對你有用,請各位賞個贊吧,謝謝。