iOS:NSBundle的一些理解

作者: 上发条的树 | 来源:发表于2017-12-22 19:24 被阅读97次

    参考

    写在前面

    文章略长,可以先看下最后面的总结!

    一般我们从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的加载,主要是对于mainBundlebundleForClass的区别分析,下面我们再做具体分析:

    mainBundle和bundleForClass

    mainBundlebundleForClass都是返回一个NSBundle对象。

    mainBundle

    • 对于所有Mach-O Type类型,也就是上面提到的五种类型,mainBundle返回的都是可执行工程的bundle

    例如:有一个Executable工程Demo,使用到了动态库工程DynamicFramework和静态库的工程StaticFramework,那么无论是在Demo中,还是DynamicFrameworkStaticFramework中,最终mainBundle返回的都是Demo的bundle!

    bundleForClass

    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.bundleMyBundle.bundle中就一张图片:

    文件结构示意图.png 图片资源.png

    仿照MJRefreshNSBundle+MJRefresh的写法,写了一个用于获取bundleimage的分类:

    #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获取大致的理解如图所示:

    静态库示意图.png

    对于动态库

    同样的,在动态库工程中,同样加入自定义bundleMyBundle

    动态工程文件示意.png

    无论是对于静态库还是动态库,将工程拖入主工程,编译的时候即可关联编译:


    关联工程.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

    如果您觉得本文对您有一定的帮助,请随手点个喜欢,十分感谢!

    相关文章

      网友评论

      • 狂奔的兔子:ios 10在真机上面加载第三方framwork会报错,显示file system sandbox blocked mmap() of framework, 怎样把从网络上下载的framwork放入.app下面,请问实现framwork动态更新呢?

      本文标题:iOS:NSBundle的一些理解

      本文链接:https://www.haomeiwen.com/subject/gnrnwxtx.html