最新
以下內容可忽略。
參考
寫在前面
文章略長,可以先看下最后面的總結!
一般我們從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有如下類型:
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
無論是對于靜態庫還是動態庫,將工程拖入主工程,編譯的時候即可關聯編譯:
對于動態庫的加載,這里提供一個思路,之后另起一篇(鏈接暫時無效)。
- 獲取動態庫的路徑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
。
因此,對于動態庫的理解,可以大致如下圖:
總結
- 可執行工程,動態庫工程,都可以獲取到獨立的
bundle
,靜態庫不行。 -
mainBundle
無論寫在哪里,都是獲取主工程的main bundle
。而bundleForClass得區別對待,如果傳入的是庫中的class
,靜態庫中獲取的是主工程的bundle
,動態庫中獲取的是動態庫的bundle
。
2019年01月27日更新
評論有人問:
怎么才能獲取到靜態庫的resource.bundle文件。
問這個問題有兩個原因:
- 1,沒搞懂靜態庫,動態庫和以及使用靜態庫/動態庫的主工程之間的意義。
- 2、我的解釋不夠明白(畢竟是當時剛接觸就寫了這篇理解,打算另起一篇。)
一般一個項目工程,導入靜態庫,需要導入靜態庫的包和屬于靜態庫的資源文件。也就是說,靜態庫中使用的資源,是放在使用它的工程中去的,也就是framework中所使用的資源bundle,除了靜態庫中的代碼是打包在framework中,其他文件都是放在外部的bundle文件中的。
以上圖項目為例,SDK001.framework
如何使用SDK001.bundle
中的圖片資源:
SDK001.framework中的代碼:
NSString *bundlePath = [[NSBundle mainBundle]pathForResource:@"SDK001" ofType:@"bundle"];
NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
NSString *file = [bundle pathForResource:"imageName" ofType:@"imageFormat"];
UIImage *image = [UIImage imageWithContentsOfFile:file2];
是不是so easy?
如果您覺得本文對您有一定的幫助,請隨手點個喜歡,十分感謝!