iOS:NSBundle的一些理解
参考
写在前面
文章略长,可以先看下最后面的总结!
一般我们从bundle中获取一张图片,可以有这样的获取思路:
- 1)获取主bundle
- 2)获取自定义bundle
- 3)获取自定义bundle中的资源
通常可以这样写:
//主bundle,也就是可执行的工程的bundle
NSBundle *mainBundle = [NSBundle mainBundle];
//NSBundle *mainBundle = [NSBundle bundleForClass:[self class]];
//放在主工程中的自定义bundle
NSString *myBundlePath = [mainBundle pathForResource:@"MyBundle" ofType:@"bundle"];
NSBundle *myBundle = [NSBundle bundleWithPath:myBundlePath];
//放在自定义bundle中的图片
NSString *imagePath = [myBundle pathForResource:@"123" ofType:@"png"];
self.image = [UIImage imageWithContentsOfFile:imagePath];
关于NSBundle
对于bundle
可以理解为一个捆绑包,个人理解bundle为一个独立的空间,而我们的可执行(executable
)工程,打包完之后,也是一个捆绑包,我们称之为主bundle
,这个主bundle
包含了可执行代码,如各个viewcontroller的可执行代码,和相关资源例如图片资源等。
NSBundle
这个类其实就是用来定位可执行资源的。获取到具体的可执行文件的位置,然后再加载。因此,NSBundle
的使用,只限制于拥有独立的bundle空间的(为什么不是:只限制于executable
的工程呢?因为对于动态库,也可以看成是拥有独立的bundle的对象。后面仔细分析)。
从NSBundle的文档中可以看到这么一句:
Any executable can use a bundle object to locate resources, either inside an app’s bundle or in a known bundle located elsewhere. You don't use a bundle object to locate files in a container directory or in other parts of the file system.
大概翻译一下的意思就是:
任何可执行文件可以用来使用
NSBundle
对象来定位资源。无论是在应用程序的包中,还是其他地方的已知包中。您不使用NSBundle
对象来在容器目录或文件系统的其他部分中定位文件。
要求可执行,我理解为运行时的可执行,executable
是运行时,Dynamic Library
也是运行时加载,因此这两个应该符合上述的可用bundle
定位文件位置的要求
以上这段话如何理解呢?
在我们的APP工程中-->TARGETS -->Build Settings --> Linking -->Mach-Type有如下类型:
项目类型.png
Executable
类型,也就是我们的可执行类型,这样的类型,通常都是需要有一个main
入口的。也就是我们常规的运行在手机上的每一个APP。在工程中我们找到main.m
文件:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
也就是我们的APP运行入口了。
除了Executable
类型,以下四种做区别对待:
动态:
Dynamic Library
静态:
Bundle
Static Library
Relocatable Object File
静态和动态的区别,就在于是否是运行时加载,静态的在编译时已经决定了,编译时将静态的文件编译进可执行的工程;而动态的,只有在运行时,可执行工程才会去加载。
关于bundled的加载,主要是对于mainBundle
和bundleForClass
的区别分析,下面我们再做具体分析:
mainBundle和bundleForClass
mainBundle
和bundleForClass
都是返回一个NSBundle
对象。
mainBundle
- 对于所有Mach-O Type类型,也就是上面提到的五种类型,
mainBundle
返回的都是可执行工程的bundle
。
例如:有一个
Executable
工程Demo
,使用到了动态库工程DynamicFramework
和静态库的工程StaticFramework
,那么无论是在Demo
中,还是DynamicFramework
和StaticFramework
中,最终mainBundle
返回的都是Demo
的bundle!
bundleForClass
Return Value
The NSBundle object that dynamically loaded aClass (a loadable bundle), the NSBundle object for the framework in which aClass is defined, or the main bundle object if aClass was not dynamically loaded or is not defined in a framework.
This method creates and returns a new NSBundle object if there is no existing bundle associated with aClass. Otherwise, the existing instance is returned.
大致的意思就是说,可以通过bundleForClass
获取class所在的bundle,
特别是其中的这一句:
or the main bundle object if aClass was not dynamically loaded or is not defined in a framework.如果class是非动态的或者它不是定义在动态库中,那么返回的是main bundle。
可以这样理解:如果是对于Executable
类型的工程,或者是静态的工程,无论class是属于可执行Executable
类型的工程,还是属于其他的静态库,最终返回的是main bundle,相当于我们上面的[NSBundle mainBundle]
的返回结果。相反的,对于动态的工程,可以获取到该工程的bundle
。
个人理解:动态的可以自成bundle(有属于自己的空间)。因为静态的在编译期间,就已经被打入主工程,主工程也就是(Executable
)工程。因此,bundleForClass
可以获取到动态库的bundle,而对于静态库,bundleForClass
获取的是使用该静态库的主工程的bundle!
对于静态库
我们有这个一个可执行工程(主工程)WxxDynamicDepotDemo
,一个静态库工程WxxStaticLibFramework
,静态库工程中有这个MyBundle.bundle
。MyBundle.bundle
中就一张图片:
仿照MJRefresh
中NSBundle+MJRefresh
的写法,写了一个用于获取bundle
和image
的分类:
#import <UIKit/UIKit.h>
@interface NSBundle (mybundle)
+(instancetype)my_bundle;
+(UIImage *)my_image;
@end
#import "NSBundle+mybundle.h"
#import "FrameworkBundleManager.h"
@implementation NSBundle (mybundle)
+(instancetype)my_bundle{
static NSBundle *myBundle = nil;
if (myBundle == nil) {
NSBundle *mainBundle = [NSBundle bundleForClass:[FrameworkBundleManager class]];
NSString *myBundlePath = [mainBundle pathForResource:@"MyBundle" ofType:@"bundle"];
myBundle = [NSBundle bundleWithPath:myBundlePath];
}
return myBundle;
}
+(UIImage *)my_image{
static UIImage *myImage = nil;
if (myImage == nil) {
NSString *path = [[self my_bundle]pathForResource:@"123" ofType:@"png"];
myImage = [UIImage imageWithContentsOfFile:path];
}
return myImage;
}
@end
FrameworkBundleManager
是静态库内部的文件:
NSBundle *mainBundle = [NSBundle bundleForClass:[FrameworkBundleManager class]];
在静态库的工程WxxStaticLibFramework
内部,写了这样一句:
NSLog(@"static framework内部获取:%@",[NSBundle my_bundle].bundlePath);
最终的结果是:
static framework内部获取:/Users/hncy-ios/Library/Developer/CoreSimulator/Devices/F4962723-AF32-44D2-A5DC-142DFDA30B4D/data/Containers/Bundle/Application/E6D53415-ED10-458F-997C-E49FAA590B7C/WxxDynamicDepotDemo.app/MyBundle.bundle
可以看到WxxDynamicDepotDemo.app
,那么这个就充分说明了以上的观点。同样的,从主工程获取静态库中的一个bundle
。其实是获取不到的,因为,编译的后,静态库中的class都归属于主工程,而通过bundleForClass
去获取,只能获取主工程的bundle
。
所以导致的结果是:
静态库中放了一个
bundle
,可是静态库中通过bundleForClass
或者mainBundle
去获取,却是主工程(可执行工程)中的bundle,访问不到静态库内部的bundle
(或许说,静态库就没有bundle
)。
对于静态库的bundle获取大致的理解如图所示:
对于动态库
同样的,在动态库工程中,同样加入自定义bundle
:MyBundle
无论是对于静态库还是动态库,将工程拖入主工程,编译的时候即可关联编译:
关联工程.png
对于动态库的加载,这里提供一个思路,之后另起一篇(链接暂时无效)。
- 获取动态库的路径path,有可能是工程的bundle中(这个需要解压ipa包,加入动态库,并且重签名),也有可能从沙盒加载(从网络下载,存进进沙盒)。
- 判断动态库是否存在,如果存在,根据动态库名字加载。
- 如果加载动态库成功,使用performSelector调用动态库中的方法。
在动态库内部,写了获取图片资源的方法如下:
-(UIImage*)dynamic_image{
NSBundle *b = [NSBundle bundleForClass:[self class]];
// NSBundle *b = [NSBundle mainBundle];
NSLog(@"动态库获取bundle路径:%@",b.bundlePath);
NSString *path = [b pathForResource:@"MyBundle" ofType:@"bundle"];
NSBundle *bundle = [NSBundle bundleWithPath:path];
NSString *imagePath = [bundle pathForResource:@"123" ofType:@"png"];
return [UIImage imageWithContentsOfFile:imagePath];
}
结果打印了:
动态库获取bundle路径:/Users/hncy-ios/Library/Developer/Xcode/DerivedData/WxxDynamicDepotDemo-dfhfmsnghwoiifgyyovgmpbnfoan/Build/Products/Debug-iphonesimulator/WxxDynamicDepotFramework.framework
可以看到,路径是WxxDynamicDepotFramework.framework
,而不是WxxDynamicDepotDemo.app
。
因此,对于动态库的理解,可以大致如下图:
动态库示意图.png总结
- 可执行工程,动态库工程,都可以获取到独立的
bundle
,静态库不行。 -
mainBundle
无论写在哪里,都是获取主工程的main bundle
。而bundleForClass得区别对待,如果传入的是库中的class
,静态库中获取的是主工程的bundle
,动态库中获取的是动态库的bundle
。
如果您觉得本文对您有一定的帮助,请随手点个喜欢,十分感谢!