1. 基本認識
1.1 幾個重要的API
獲取 bundle 的 API
理解 bundle 的概念,它就是一個容器概念,NSBundle 對象不僅僅指我們可見的 XXX.bundle 文件,framework 也是屬于 bundle 范疇。
讀取圖片的 API
蘋果官方提供的讀取圖片資源的方法
系統提供了兩類方法,可以讓我們讀取圖片:
- 從bundle中讀取圖片
- 從指定的文件路徑讀取圖片
這兩類方法的本質區別在于,前者會在內存中緩存圖片的二進制文件,后者不會,所以在使用的時候,這個可以按需選擇。
從 bundle 中讀取圖片
系統提供了兩個從 bundle 中獲取圖片的方法:
+ imageNamed:inBundle:compatibleWithTraitCollection:
+ imageNamed:
我們最常用的 + imageNamed:
,從 main bundle 中讀取圖片。
對于其它 bundle 中的圖片資源,我們需要使用上面的第一個方法,指定圖片資源所在的 bundle。
從指定的文件路徑讀取圖片
如果我們知道圖片資源的文件路徑,我們可以通過如下方法來讀取圖片:
imageWithContentsOfFile:
傳入的參數是圖片的路徑。
1.2 cocopods 相關基礎
cocopods 中資源使用文章。
2. APP 工程中資源文件的讀取方法
SDK 開發中,我們最常用的場景是,讀取圖片將其存在 UIImage 對象中,在某個地方顯示出來。
所以,這里我們介紹一下,將圖片讀取到 UIImage 對象中的一些基礎知識。
本文演示了,我們如何以下四種場景下,圖片的讀取方式:
- 讀取 project 下的圖片,如圖中的編號1
- 讀取 project 下文件夾中的圖片,如圖中的編號2
- 讀取
Assets.xcassets
中的圖片,如圖中的編號3 - 讀取
動態庫 WBDynamic.framework
中的圖片,如圖中的編號4
下圖左邊是 project 的文件目錄結構,其中,WBDynamic.framework
是一個自制的動態庫;右邊是項目文件夾中的文件層級。
我們 build 工程,查看輸出的 WBDemo.app
文件,其中的文件目錄結構如下:
├── 1.png
├── 2.png
├── Assets.car
├── Base.lproj
│ ├── LaunchScreen.storyboardc
│ └── Main.storyboardc
├── Frameworks
│ └── WBDynamic.framework
│ ├── Info.plist
│ ├── WBDynamic
│ ├── WBDynamic.bundle
│ └── _CodeSignature
├── Info.plist
├── PkgInfo
├── WBDemo
└── _CodeSignature
└── CodeResources
我們發現:
- 編號1、編號2.編號3中的圖片資源,最終都會在 main bundle 下
- 動態庫單獨放在 Framewokrs這個目錄下面,其中編號為4 的圖片資源放在
WBDynamic.framework/WBDynamic.bundle
這個路徑下面
我們先演示從 main bundle 中讀取圖片的情況。
基本步驟如下:
- 獲取到圖片資源所在的 bundle
- 使用
imageNamed:inBundle:compatibleWithTraitCollection:
方法獲取 UIImage 對象
- (UIImage *)imageFromBundle{
NSBundle *bundle = [NSBundle mainBundle];
return [UIImage imageNamed:@"1.png"
inBundle:bundle
compatibleWithTraitCollection:nil];
}
對于 main bundle 中的圖片,可以直接使用 [UIImage imageNamed:@"1.png"];
來讀取。
對于動態庫中的圖片,我們使用另外一種方式來演示,也就是通過圖片的路徑來讀取圖片。
基本步驟如下:
- 拼接圖片的文件路徑
- 使用
imageWithContentsOfFile:
方法來獲取 UIImage 對象
- (UIImage *) imageFromPath{
NSString *mainBundlePath = [[NSBundle mainBundle] bundlePath];
NSString *imgPath = [mainBundlePath stringByAppendingString:@"/Frameworks/WBDynamic.framework/WBDynamic.bundle/1.png"];
return [UIImage imageWithContentsOfFile:imgPath];
}
通過測試,我們可以正常讀取到上述編號為1、2、3、4的圖片資源。
2. cocopods 中的圖片資源引用方式分析
這里的 cocopods 指的是私有庫,也就是我們需要開發的組件或者SDK。
我們知道,SDK不能獨立運行,需要借助一個 APP project。
我們在 SDK 中使用圖片資源,按照圖片的最終位置存在來分,圖片資源只能存在于兩個位置:
- 直接存放在 APP 的 main bundle 下面
- APP 的 main bundle 下的 其他 bundle 中
我們開發過程中,每個 SDK 我們都會獨立使用一個 bundle 來存放我們的資源圖片,這樣方便管理,也可以避免和外部圖片沖突的問題。
下面來描述整個開發流程。
2.1 編寫 podspec 文件
以 WBDynamic.podspec
這個倉庫為例,來演示如何在 framework 中使用圖片資源。
我們一般按照如下的方式來索引資源文件。
s.resource_bundles = {
'WBDynamic' => ['WBDynamic/Assets/*.png'],
}
2.2 執行 pod install 的變化
在使用 WBDynamic 的地方,執行 pod install
命令之后,APP 工程會發生一些變化,這個變化和 use_frameworks!
這個標記有關系。
由于我們在前面已經知道了,使用 use_frameworks!
這個標記,APP 項目會以動態庫的方式集成 WBDynamic,否則,就會以靜態庫的方式集成。
下面,我們來各自分析一下,這兩種情況下資源的引用情況。
動態庫方式集成
podfile 中添加 use_frameworks!
,運行 pod install
,buid 工程,查看 WBDynamic_Example.app
的文件結構,如下所示。
├── Base.lproj
│ ├── LaunchScreen.storyboardc
│ └── Main.storyboardc
├── Frameworks
│ └── WBDynamic.framework
│ ├── Info.plist
│ ├── WBDynamic
│ ├── WBDynamic.bundle
│ │ ├── 1.png
│ │ ├── 2.png
│ │ └── Info.plist
│ └── _CodeSignature
│ └── CodeResources
├── Info.plist
├── PkgInfo
├── WBDynamic_Example
├── _CodeSignature
│ └── CodeResources
└── en.lproj
└── InfoPlist.strings
我們從中精簡出資源相關的結構:
├── Frameworks
│ └── WBDynamic.framework(動態庫)
│ ├── WBDynamic(動態庫可執行文件)
│ └── WBDynamic.bundle(動態庫中的資源文件bundle)
│ ├── 1.png
│ └── 2.png
└── WBDynamic_Example (app 可執行文件)
也就說,我們需要按照上面所示的路徑讀取資源文件。
要讀取到圖片,現在,冒在腦海中有兩個思路:
- 思路一:通過 main bundle 來定位 WBDynamic.bundle ,其相對路徑為
./Frameworks/WBDynamic.framework/WBDynamic.bundle
,也就是我們上面示例演示的,這種思路不好,我們會在下一節中講述原因 - 思路二:通過可執行文件 WBDynamic 來定位,SDK 中的代碼最終會打包到
WBDynamic
中,WBDynamic
的位置也就是當前 class 的位置,這里我們演示這種思路
我們的做法如下:
// [NSBundle bundleForClass:[self class]] 獲取可執行文件的路徑
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"WBDynamic" ofType:@"bundle"];
NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
UIImage *img = [UIImage imageNamed:@"1.png" inBundle:bundle compatibleWithTraitCollection:nil];
整個流程我們可以分解為以下幾個步驟:
-
WBDynamic.bundle
和 可執行文件WBDynamic
在同一級目錄下面,我們可以通過可執行文件的路徑來定位WBDynamic.bundle
的路徑 - 通過 bundle 路徑獲取 NSBundle 對象
- 從 bundle 中獲取圖片資源
測試通過,我們可以正常獲取圖片。
當然,也還有另一種實現方法,就是通過圖片的路徑來獲取圖片,實現如下:
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"WBDynamic" ofType:@"bundle"];
NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
NSString *imgPath = [bundle pathForResource:@"1" ofType:@"png"];
UIImage *img = [[UIImage imageWithContentsOfFile:imgPath] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
這兩種方法的區別在前面講了,一個緩存圖片,一個不緩存,按需使用。
靜態庫方式集成
podfile 中注釋掉 use_frameworks!
,運行 pod install
,buid 工程,查看 WBDynamic_Example.app
的文件結構,如下所示。
├── Base.lproj
│ ├── LaunchScreen.storyboardc
│ └── Main.storyboardc
├── Info.plist
├── PkgInfo
├── WBDynamic.bundle
│ ├── 1.png
│ ├── 2.png
│ └── Info.plist
├── WBDynamic_Example
├── _CodeSignature
│ └── CodeResources
└── en.lproj
└── InfoPlist.strings
我們發現,以靜態庫的形式集成 SDK,cocopods 會自動在 APP 的 main bundle 中生成一個 WBDynamic.bundle
。
這里我們也有兩個思路:
- 思路一:
WBDynamic.bundle
在 main bundle 中,我們通過相對于 main bundle 的路徑,來獲取WBDynamic.bundle
,其相對路徑為./WBDynamic.bundle
- 思路二:SDK 中的代碼打包到靜態庫中,靜態庫被打包到 可執行文件
WBDynamic_Example
中去了,這就和動態庫的思路二相同了
總結
所以,我們在SDK開發的時候,我們讀取圖片的最佳方式如下:
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"WBDynamic" ofType:@"bundle"];
NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
UIImage *img = [UIImage imageNamed:@"1.png" inBundle:bundle compatibleWithTraitCollection:nil];
動態庫和靜態庫通用。