BundleLoader:帮你无缝加载自定义Bundle里的资源文件
引子
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方法,分别是:
- 关联对象
objc_setAssociatedObject
- 改变对象类型
object_setClass
- Method Swizzling
method_exchangeImplementations
通过自定义的子类和自定义方法让系统先从我们的资源Bundle里加载文件,找不到再去主包里加载。
如果这个库对你有用,请各位赏个赞吧,谢谢。
BundleLoader:帮你无缝加载自定义Bundle里的资源文件相关推荐
- Android插件化开发之用DexClassLoader加载未安装的APK资源文件来实现app切换背景皮肤
第一步.先制做一个有我们需要的图片资源的APK 如下图,这里有个about_log.png,我们需要生成apk文件. 生成的apk文件如果你不到项目的文件夹里面去取apk,想通过命令放到手机里面去可以 ...
- 加载PNG、ANI等资源文件
加载PNG BOOL LoadPicture(ATL::CImage *img, UINT nResID, LPCTSTR szType) {if (img == NULL){return FALSE ...
- php定义一个名为Vehicles,[PHP][Yii2.0] 以Yii 2.0风格加载自定义类或命名空间 [配置使用Yii2 autoloader]...
Yii 2.0最显著的特征之一就是引入了命名空间,因此对于自定义类的引入方式也同之前有所不同.这篇文章讨论一下如何利用Yii 2.0的自动加载机制,向系统中引入自定义类和命名空间.本文旨在抛砖引玉,如 ...
- Android开发之WebView加载自定义scheme报错net::ERR_UNKNOWN_URL_SCHEME(附带源码标题下面可点击下载)
咱们先看下报错结果图: 我这边是华为荣耀7i手机才出现这个情况,总结得出结论android6.0以上加载自定义scheme会报错如下,6.0以下貌似不会 三星和模拟器没有可以直接加载这个url ,网页 ...
- AutoCAD C# 自动加载自定义RibbonUI界面
目录 实现效果: 开发环境: 开发流程: 打开Visual Studio ,创建 .NET FrameWork 类库项目 填写项目名称 "RibbonUI",选择.NET 版本为 ...
- iOS在Xib加载自定义Xib视图
iOS中在Xib或者Storyboard中加载自定义的Xib视图 最近都在做Android项目的开发,许久没有捣腾iOS开发了,今天接到一个旧项目功能的开发,为了快速开发出来决定使用Xib或者Stor ...
- 【ClassLoader】实现自定义类加载器加载指定路径下的Class文件和Jar包
文章目录 前言 自定义类加载器加载.class文件 自定义类加载器加载jar包文件 前言 在web开发中,一般我们是不需要去自己实现类加载器的,常见的web容器已经帮我们实现了指定路径下的加载,比如我 ...
- Visual C++——加载自定义光标
基本概念 自定义光标:自定义光标保存在扩展名为.cur的文件中 光标名 CURSOR 光标文件(.cur) 采用自定义光标时,需在资源文件中定义光标资源. API LoadCursor:第一个参数是要 ...
- springboot yml怎么建常量_Springboot中加载自定义的yml配置文件
有一些配置需要单独提出来时,如果是properties文件可以@PropertySource注解直接进行加载,但如果是yml文件就需要进行处理 1.创建你的配置文件,比如config.yml,写入配置 ...
最新文章
- 通俗易懂!《图机器学习导论》(附链接)
- 利用content为伊特元素追加三个小点
- mysql拷贝恢复.frm_通过.frm .ibd文件恢复MySQL数据
- CUBA平台–用于快速应用程序开发的开源Java框架
- 昂首阔步:让开发人员喜欢使用您的REST API
- Java 并发(JUC 包-04)
- jmeter压测前清理内存
- 进击的爱奇艺文学:如何成为苹果园生态的重要一环?
- mysql not regexp_Mysql必知必会——使用正则表达式搜索(REGEXP)
- MNIST数据集下载与读取
- Vscode关闭自动更新
- 什么是路由守卫?有什么用?
- 2021年中国兽医热疗室市场趋势报告、技术动态创新及2027年市场预测
- 自媒体会否是独立游戏的出路?
- python tkinter学习6 scale滑条
- 淘宝新版打标足迹在哪里浏览?
- [杂记]LeTeX模板——ppt
- java加载tensorflow训练的PB模型记录
- 02-Lynda备用
- 什么是kotlin?
热门文章
- tortoisegit 代码的回滚方式 --两种
- JavaScript实现了网页的行为
- 【IE】IE对line-height 失效的的解决方案
- 在Fedora上搭建GTK+的开发环境
- 使用SPA/GPA 参数--SAP内存参数设置SET /GET PARAMTER ID
- 数据结构笔记(二十九)--最小生成树(prim算法思想)
- java的if else if_java,if else和if else if else区别?
- cas4.0 mysql_【SSO单点系列】:CAS4.0 CAS整合SpringMVC+MyBatis实现数据库校验(04)
- linux pwm测试程序,DM8168 PWM驱动与测试程序
- 框架下cookie的使用_aspnetcore自带cookie的认证期限分析