iOS:NSBundle的一些理解

最新

iOS SDK(二):Bundle

以下內容可忽略。

參考

寫在前面

文章略長,可以先看下最后面的總結!

一般我們從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

2019年01月27日更新

評論有人問:

怎么才能獲取到靜態庫的resource.bundle文件。

問這個問題有兩個原因:

  • 1,沒搞懂靜態庫,動態庫和以及使用靜態庫/動態庫的主工程之間的意義。
  • 2、我的解釋不夠明白(畢竟是當時剛接觸就寫了這篇理解,打算另起一篇。)

一般一個項目工程,導入靜態庫,需要導入靜態庫的包和屬于靜態庫的資源文件。也就是說,靜態庫中使用的資源,是放在使用它的工程中去的,也就是framework中所使用的資源bundle,除了靜態庫中的代碼是打包在framework中,其他文件都是放在外部的bundle文件中的。

靜態庫包含到工程中了.png

以上圖項目為例,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?

如果您覺得本文對您有一定的幫助,請隨手點個喜歡,十分感謝!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容