引子

iOS开发中,我们封装SDK给第三方使用通常采用.a或.framework + .bundle的形式。相信封装过这种带bundle资源文件的SDK的同学们一定都会遇到这样一个小麻烦。那就是加载自定义Bundle里的资源的代码写起来和我们平时开发App时加载mainBundle里的资源的代码是不同的,前者写起来要麻烦一些。

如果你正在封装带资源的SDK,那我相信BundleLoader应该可以帮助到你。它可以帮你消除这种调用上的不同,你只需要简单的调用两个方法就可以像加载App里的资源那样『无缝』的加载自定义Bundle里的资源。既有代码无需修改,后续代码你也可以继续用最简洁最熟悉的方式开发。

项目地址: BundleLoader

问题

最近,本人碰到了这样一个需求。我是做直播APP的,老板要求我从APP里把直播间相关的部分分离出来封装成SDK给第三方使用,并且今后要做到SDK和APP能够同步开发,同步更新。

这种情况下,这种调用不同对我来说就是个大麻烦了。 其一,直播间及相关部分的代码量非常庞大,各种资源各种形式的调用,改起来很麻烦。 其二,改动了以后今后同步开发也是个麻烦。

要解决这个问题,我们先来看看代码上会有何不同。比如图片,我们知道加载App主包里的图片代码只需要简单的一句:

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];
复制代码

或者简化一点:

NSString *file2 = [[NSBundle mainBundle] pathForResource:@"myBundle.bundle/pic" ofType:@"png"];
UIImage *img2 = [UIImage imageWithContentsOfFile:file2];
复制代码

再简化一点:

UIImage *img3 = [UIImage imageNamed:@"myBundle.bundle/pic"];
复制代码

但是还是都没有mainBundle里的简单。于是,我就想,能不能不改代码就可以加载自定义Bundle里的资源呢?方法肯定有,OC强大的Runtime出马,没有搞不定的事情,哈哈。

特性

BundleLoader的Demo里目前测试了下列几种情况的自定义bundle资源无缝加载:

  • 图片
  • xib
  • storyboard
  • xcssets图片
  • 普通资源文件

xib或storyboard里用到的图片和xcssets图片也都可以正常显示。 同时,Demo还提供了一个简单的Framework + Bundle的工程模版,可以供大家参考。

其他资源,如CoreData模型,本地化字符串等应该也可以加载,如果不行的话大家也可以依葫芦画瓢,自行实现。

实现

具体的实现其实并不复杂,最关键的一点是:我发现,App里不论加载什么类型的资源,调用什么接口,系统内部都会去调用NSBundle的这个方法:

- (nullable NSString *)pathForResource:(nullable NSString *)name ofType:(nullable NSString *)ext;
复制代码

这个方法就是突破口,我们只要在这个方法上去想办法,做文章,再用上灵活强大的Runtime,应该就能达到我们的目的。

实现的步骤如下:

  • 获取自定义资源Bundle的对象
  • 把这个对象关联到mainBundle对象上
  • 把mainBundle对象的Class设为自定义Bundle子类的Class
  • 在Bundle子类里重写pathForResource:ofType:方法
  • 这个方法里拿到关联的自定义Bundle对象
  • 判断自定义Bundle对象里该文件是否存在,存在则返回其路径
  • 不存在则去mainBundle里找

上代码:

@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子类的Classobject_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 methodsmethod_exchangeImplementations(originalMethod, customMethod);
}+ (nullable UIImage *)imageNamedCustom:(NSString *)name {//Call original methodsUIImage *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的图片只能用这个方法elsereturn nil;
}@end
复制代码

先调用imageNamed:获取图片,如果拿到则直接返回;失败则调用imageNamed:inBundle:compatibleWithTraitCollection:方法去获取图片,并传入自定义Bundle对象。这样Bundle里的xcassets图片也可以简单加载了。

至于xib和storyboard也是同样的做法。

总结

实现还是比较简单的,用到了三个Runtime方法,分别是:

  1. 关联对象 objc_setAssociatedObject
  2. 改变对象类型 object_setClass
  3. Method Swizzling method_exchangeImplementations

通过自定义的子类和自定义方法让系统先从我们的资源Bundle里加载文件,找不到再去主包里加载。

如果这个库对你有用,请各位赏个赞吧,谢谢。

BundleLoader:帮你无缝加载自定义Bundle里的资源文件相关推荐

  1. Android插件化开发之用DexClassLoader加载未安装的APK资源文件来实现app切换背景皮肤

    第一步.先制做一个有我们需要的图片资源的APK 如下图,这里有个about_log.png,我们需要生成apk文件. 生成的apk文件如果你不到项目的文件夹里面去取apk,想通过命令放到手机里面去可以 ...

  2. 加载PNG、ANI等资源文件

    加载PNG BOOL LoadPicture(ATL::CImage *img, UINT nResID, LPCTSTR szType) {if (img == NULL){return FALSE ...

  3. php定义一个名为Vehicles,[PHP][Yii2.0] 以Yii 2.0风格加载自定义类或命名空间 [配置使用Yii2 autoloader]...

    Yii 2.0最显著的特征之一就是引入了命名空间,因此对于自定义类的引入方式也同之前有所不同.这篇文章讨论一下如何利用Yii 2.0的自动加载机制,向系统中引入自定义类和命名空间.本文旨在抛砖引玉,如 ...

  4. Android开发之WebView加载自定义scheme报错net::ERR_UNKNOWN_URL_SCHEME(附带源码标题下面可点击下载)

    咱们先看下报错结果图: 我这边是华为荣耀7i手机才出现这个情况,总结得出结论android6.0以上加载自定义scheme会报错如下,6.0以下貌似不会 三星和模拟器没有可以直接加载这个url ,网页 ...

  5. AutoCAD C# 自动加载自定义RibbonUI界面

    目录 实现效果: 开发环境: 开发流程: 打开Visual Studio ,创建 .NET FrameWork 类库项目 填写项目名称 "RibbonUI",选择.NET 版本为 ...

  6. iOS在Xib加载自定义Xib视图

    iOS中在Xib或者Storyboard中加载自定义的Xib视图 最近都在做Android项目的开发,许久没有捣腾iOS开发了,今天接到一个旧项目功能的开发,为了快速开发出来决定使用Xib或者Stor ...

  7. 【ClassLoader】实现自定义类加载器加载指定路径下的Class文件和Jar包

    文章目录 前言 自定义类加载器加载.class文件 自定义类加载器加载jar包文件 前言 在web开发中,一般我们是不需要去自己实现类加载器的,常见的web容器已经帮我们实现了指定路径下的加载,比如我 ...

  8. Visual C++——加载自定义光标

    基本概念 自定义光标:自定义光标保存在扩展名为.cur的文件中 光标名 CURSOR 光标文件(.cur) 采用自定义光标时,需在资源文件中定义光标资源. API LoadCursor:第一个参数是要 ...

  9. springboot yml怎么建常量_Springboot中加载自定义的yml配置文件

    有一些配置需要单独提出来时,如果是properties文件可以@PropertySource注解直接进行加载,但如果是yml文件就需要进行处理 1.创建你的配置文件,比如config.yml,写入配置 ...

最新文章

  1. 通俗易懂!《图机器学习导论》(附链接)
  2. 利用content为伊特元素追加三个小点
  3. mysql拷贝恢复.frm_通过.frm .ibd文件恢复MySQL数据
  4. CUBA平台–用于快速应用程序开发的开源Java框架
  5. 昂首阔步:让开发人员喜欢使用您的REST API
  6. Java 并发(JUC 包-04)
  7. jmeter压测前清理内存
  8. 进击的爱奇艺文学:如何成为苹果园生态的重要一环?
  9. mysql not regexp_Mysql必知必会——使用正则表达式搜索(REGEXP)
  10. MNIST数据集下载与读取
  11. Vscode关闭自动更新
  12. 什么是路由守卫?有什么用?
  13. 2021年中国兽医热疗室市场趋势报告、技术动态创新及2027年市场预测
  14. 自媒体会否是独立游戏的出路?
  15. python tkinter学习6 scale滑条
  16. 淘宝新版打标足迹在哪里浏览?
  17. [杂记]LeTeX模板——ppt
  18. java加载tensorflow训练的PB模型记录
  19. 02-Lynda备用
  20. 什么是kotlin?

热门文章

  1. tortoisegit 代码的回滚方式 --两种
  2. JavaScript实现了网页的行为
  3. 【IE】IE对line-height 失效的的解决方案
  4. 在Fedora上搭建GTK+的开发环境
  5. 使用SPA/GPA 参数--SAP内存参数设置SET /GET PARAMTER ID
  6. 数据结构笔记(二十九)--最小生成树(prim算法思想)
  7. java的if else if_java,if else和if else if else区别?
  8. cas4.0 mysql_【SSO单点系列】:CAS4.0 CAS整合SpringMVC+MyBatis实现数据库校验(04)
  9. linux pwm测试程序,DM8168 PWM驱动与测试程序
  10. 框架下cookie的使用_aspnetcore自带cookie的认证期限分析