Bundle 是 Swift 和 Objective-C 中用于访问资源和模块的类型。它表示一个资源包,通常用于加载应用程序中嵌入的文件和数据(如图片、JSON 文件、音频文件、故事板等)。
什么是Bundle?
在 iOS 和 macOS 应用中,每个应用在编译完成后,并不是一个“可执行文件”,而是一个Bundle目录结构。
例如,macOS App 轻压图片:
在 macOS 上,显示:
轻压图片.app
.app 目录本身就是一个 Bundle,当右键 – 「显示包内容」时,可以看到:
轻压图片.app
└── Contents
├── Info.plist
├── MacOS
│ └── 轻压图片 ← 可执行文件
├── Resources
│ ├── Assets.car
│ ├── *.png
│ ├── *.md
│ └── ...
└── Frameworks
整个轻压图片.app就是Bundle,系统识别Bundle是根据后缀名为 .app,存在Info.plist等多种条件进行判断的。
在Swift中:
Bundle.main.bundlePath
返回的路径,指向的是:
.../轻压图片.app
Resources是Bundle的一个子目录,用于存放图片、JSON、字体等文件。
主Bundle
Bundle.main 是指当前运行应用的主资源包,每个iOS/macOS都只有一个Bundle.main,所有被Xcode打包进App的资源都在这个Bundle中。
它包含应用程序的所有资源,例如图片、JSON 文件、声音文件、字体,以及 Info.plist 文件等。
在Swift中,通过使用Bundle.main访问:
let mainBundle = Bundle.main
注意:无法直接访问Assets默认资源文件夹中的图片资源。
最常用的操作是,在主Bundle中根据文件名获取单个资源。
例如:
let url = Bundle.main.url(forResource: "config", withExtension: "json")
let path = Bundle.main.path(forResource: "logo", ofType: "png")
// Swift 显示 Bundle 中的图片
if let imagePath = Bundle.main.path(forResource: "logo", ofType: "jpeg"),
let image = UIImage(contentsOfFile: imagePath) {
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(width: 200)
}
url(forResource:) 返回 URL?,path(forResource:) 返回 String?,两个返回的类型不同,在现代Swift中,推荐使用URL API。
其他Bundle
在macOS App中,通常会存在多个Bundle。
例如,Framework Bundle,路径通常为:
轻压图片.app
└── Contents
└── Frameworks
└── SomeFramework.framework
└── Versions/A/Resources
其中的framework就是一个Bundle。
在Swift中,访问Framework中的类,就可以得到这个Framework的Bundle:
let frameworkBundle = Bundle(for: SomeClass.self)
创建 Bundle
以下是创建Bundle的方法,其中常用的是访问主包获取资源。
1、主包 (Bundle.main)
表示当前运行应用的主资源包,通常用于访问应用中嵌入的资源。
let mainBundle = Bundle.main
2、指定类的模块包 (Bundle(for:))
用于访问特定类所在的资源包,常用于加载动态库或自定义模块的资源。
let classBundle = Bundle(for: MyClass.self)
特定类所在的资源包指的是,在某些情况下,资源可能不在主资源包中,而是存在于动态加载的模块、库或框架中(例如,CocoaPods 或其他第三方库的资源)。这时,可以通过 Bundle(for:) 方法找到特定类所在的资源包。
如果在自定义模块(如动态库或框架)中放置了资源文件,可以使用此方法访问这些资源。它会根据指定类 MyClass 的位置,找到该类所在的 Bundle,即MyClass 所在的框架的资源包。
3、通过资源文件路径获取 Bundle
指定资源所在的路径,创建对应的 Bundle 实例。
// 通过资源文件路径获取 Bundle
if let resourceBundle = Bundle(path: "/path/to/bundle") {
// 使用 resourceBundle
}
// 在实际使用中,iOS/macOS 应用沙盒中无法得知框架的绝对路径
// 通常先通过 Bundle.main.path 方法查找 Framework 路径
// 再创建 Bundle
if let frameworkPath = Bundle.main.path(forResource: "MyFramework", ofType: "framework"),
let frameworkBundle = Bundle(path: frameworkPath) {
print("动态框架加载成功:\(frameworkBundle)")
}
尝试加载指定路径上的资源包 (Bundle),如果成功,就可以通过 resourceBundle 访问该资源包中的文件或资源。
4、通过URL创建Bundle
Bundle(url:) 是 Swift 中 Bundle 类的一个初始化方法,用于通过一个 URL 创建或访问一个 Bundle 对象。
Bundle(url: URL)
接受一个 URL 参数,表示一个资源包的路径。返回一个可选的 Bundle? 对象。如果路径有效且指向一个资源包,返回对应的 Bundle 对象。如果路径无效(例如路径不存在或不是资源包),返回 nil。
常用方法
以下是 Bundle 中常用的属性和方法:
1、查找单一文件路径/URL访问资源
1)path(forResource:ofType:):返回资源在 bundle 中的路径(String?),属于较早期的API。
// 获取路径
let path = Bundle.main.path(forResource: "config", ofType: "json")
2)url(forResource:withExtension:):返回资源在 bundle 中的 URL(URL?),适合现代Swift。
// 获取 URL
let url = Bundle.main.url(forResource: "logo", withExtension: "png")
path方法和url方法的主要区别是返回String和返回URL,在现代Swift中,推荐使用url(forResource:withExtension:)方法获取单文件。
3)paths(forResource:ofType:inDirectory:):返回资源在bundle的指定子目录中,匹配名称和扩展名的资源。
// 获取子目录中的资源
let pathInDir = Bundle.main.path(forResource: "pngquant", ofType: "txt", inDirectory: "Tools")
注意:在Xcode16项目中,实际测试发现,inDirectory即使指定文件夹,实际上并不能正确的匹配到资源文件,需要将inDirectory改为nil,才可以查找到文件。建议使用url(forResource:withExtension:)方法获取单文件。
2、获取多个文件路径/URL访问资源
1)paths(forResourcesOfType:inDirectory:):返回匹配类型的所有资源路径。
// 批量获取某一类型的资源(这里加载Markdown文件)
let mdFiles = Bundle.main.paths(
forResourcesOfType: "md",
inDirectory: nil
)
2)urls(forResourcesWithExtension:subdirectory:):返回匹配扩展名的所有资源 URL。
// 批量获取某一类型的资源(这里加载Markdown文件)
let mdFiles = Bundle.main.urls(
forResourcesWithExtension: "md",
subdirectory: nil
) ?? []
3、获取Bundle 信息(Info.plist 等)
1)infoDictionary:读取 Info.plist 内容为字典。
2)object(forInfoDictionaryKey:):获取 Info.plist 中某个键的值,例如显示App名称和版本号。
// 获取应用程序的名称和版本:
let appName = Bundle.main.object(
forInfoDictionaryKey: "CFBundleName"
) as? String
let version = Bundle.main.object(
forInfoDictionaryKey: "CFBundleShortVersionString"
) as? String
print("应用名称:\(appName ?? "未知"),版本:\(version ?? "未知")")
// 访问 Info.plist 中的自定义键:
if let customValue = Bundle.main.object(forInfoDictionaryKey: "MyCustomKey") as? String {
print("自定义值:\(customValue)")
}
3)bundleIdentifier:获取 Bundle ID。
4)bundlePath / resourcePath:获取 Bundle 路径或资源目录路径。
5)executablePath:获取主可执行文件路径。
6)resourcePath:获取Resources目录的真实磁盘路径(String?)。
if let resourcePath = Bundle.main.resourcePath {
print("资源目录路径:", resourcePath)
}
4、本地化资源
1)localizedString(forKey:value:table:):获取本地化字符串。
let localized = Bundle.main.localizedString(forKey: "hello", value: nil, table: nil)
2)preferredLocalizations:获取用户首选语言。
3)localizations:获取支持的所有本地化语言。
4)path(forResource:ofType:inDirectory:forLocalization:):查找特定语言下的资源路径。
5、其他高级功能
1)Bundle(identifier:):获取指定 bundle ID 的 bundle 实例。
2)Bundle(url:):从路径或 URL 加载自定义 bundle(插件、Framework)。
3)main:当前 App 的主 bundle。
4)allBundles:所有已加载的 bundle。
5)allFrameworks:所有已加载的 Framework bundle。
常见用例
1、加载图片:
if let imagePath = Bundle.main.path(forResource: "icon", ofType: "png") {
let image = UIImage(contentsOfFile: imagePath)
}
2、加载自定义字体:
if let fontPath = Bundle.main.url(forResource: "CustomFont", withExtension: "ttf"),
let fontData = try? Data(contentsOf: fontPath),
let font = CGDataProvider(data: fontData as CFData),
let newFont = CGFont(font) {
CTFontManagerRegisterGraphicsFont(newFont, nil)
}
3、从主包中加载 JSON 文件并解析的完整示例:
import Foundation
struct Person: Codable {
let name: String
let age: Int
}
if let fileURL = Bundle.main.url(forResource: "data", withExtension: "json"),
let data = try? Data(contentsOf: fileURL) {
let decoder = JSONDecoder()
if let person = try? decoder.decode(Person.self, from: data) {
print("解析成功:\(person)")
} else {
print("解析失败")
}
} else {
print("文件未找到")
}
总结
Bundle 是一个访问资源包类型,可以返回主资源包中的文件,也可以返回版本号等内容。支持动态资源加载以及模块化开发。
