三、SDK 開發中圖片資源讀取問題

1. 基本認識

1.1 幾個重要的API

獲取 bundle 的 API

NSBundle官方文檔

理解 bundle 的概念,它就是一個容器概念,NSBundle 對象不僅僅指我們可見的 XXX.bundle 文件,framework 也是屬于 bundle 范疇。

讀取圖片的 API

UIImage 官方文檔

蘋果官方提供的讀取圖片資源的方法

系統提供了兩類方法,可以讓我們讀取圖片:

  • 從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 是一個自制的動態庫;右邊是項目文件夾中的文件層級。

image.png

我們 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];

動態庫和靜態庫通用。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容