Article / 2026/05/29
Swift Bundle 完全指南
🚀 Swift Bundle 完全指南
目录
概述
Bundle 是 Foundation 框架中的核心类,表示代码和资源的目录结构,用于定位和加载应用程序的资源文件、可执行代码和本地化字符串。
Bundle 的作用
- 📦 管理应用程序的资源文件(图片、音频、视频、JSON 等)
- 🌍 处理本地化资源和多语言支持
- ℹ️ 提供应用程序和框架的元数据(版本号、标识符等)
- 🔧 加载动态库、框架和插件
- 🎯 在运行时访问编译后的资源
获取 Bundle 实例
1. main - 主 Bundle ⭐ 最常用
// 获取应用程序的主 bundle
let mainBundle = Bundle.main
// 基本信息
print("Bundle ID: \(mainBundle.bundleIdentifier ?? "Unknown")")
print("App Path: \(mainBundle.bundlePath)")
参数:aClassName: String - 类的完全限定名称
返回:AnyClass? - 找到的类,失败返回 nil
3. executableArchitectures - 可执行文件架构
// 获取 bundle 支持的 CPU 架构
if let architectures = Bundle.main.executableArchitectures {
for arch in architectures {
if let archValue = arch as? Int {
switch archValue {
case NSBundleExecutableArchitectureARM64:
print("Supports ARM64")
case NSBundleExecutableArchitectureX86_64:
print("Supports x86_64")
default:
print("Architecture: \(archValue)")
}
}
}
}
类型:[NSNumber]?
说明:bundle 支持的处理器架构列表
实用方法
1. url(forResource:withExtension:subdirectory:localization:) - 高级资源定位
// 同时指定子目录和本地化
if let imageURL = Bundle.main.url(
forResource: "welcome_banner",
withExtension: "png",
subdirectory: "Images/Onboarding",
localization: "zh-Hans"
) {
let image = UIImage(contentsOf: imageURL)
}
2. 检查资源是否存在
extension Bundle {
func resourceExists(name: String, type: String) -> Bool {
return path(forResource: name, ofType: type) != nil
}
func urlExists(forResource name: String, withExtension ext: String) -> Bool {
return url(forResource: name, withExtension: ext) != nil
}
}
// 使用
if Bundle.main.resourceExists(name: "config", type: "json") {
print("Config file exists")
}
3. 获取所有特定类型的资源
extension Bundle {
func allResources(ofType type: String, inDirectory directory: String? = nil) -> [URL] {
return urls(forResourcesWithExtension: type, subdirectory: directory) ?? []
}
}
// 使用
let allImages = Bundle.main.allResources(ofType: "png", inDirectory: "Images")
print("Found \(allImages.count) images")
常见使用场景
场景 1:加载 JSON 配置文件
struct AppConfig: Codable {
let apiBaseURL: String
let apiKey: String
let timeout: Int
}
func loadConfig() -> AppConfig? {
guard let url = Bundle.main.url(forResource: "config", withExtension: "json") else {
print("Config file not found")
return nil
}
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let config = try decoder.decode(AppConfig.self, from: data)
return config
} catch {
print("Failed to load config: \(error)")
return nil
}
}
// 使用
if let config = loadConfig() {
print("API URL: \(config.apiBaseURL)")
}
场景 2:批量加载图片资源
class ImageManager {
static let shared = ImageManager()
private var imageCache: [String: UIImage] = [:]
func preloadImages(inDirectory directory: String) {
let imageURLs = Bundle.main.urls(
forResourcesWithExtension: "png",
subdirectory: directory
) ?? []
for url in imageURLs {
if let image = UIImage(contentsOfFile: url.path) {
let imageName = url.deletingPathExtension().lastPathComponent
imageCache[imageName] = image
print("Preloaded: \(imageName)")
}
}
}
func image(named name: String) -> UIImage? {
return imageCache[name]
}
}
// 使用
ImageManager.shared.preloadImages(inDirectory: "Onboarding")
let image = ImageManager.shared.image(named: "welcome")
场景 3:多语言支持
// Localizable.strings (en)
// "welcome_title" = "Welcome";
// "welcome_message" = "Welcome to %@!";
// Localizable.strings (zh-Hans)
// "welcome_title" = "欢迎";
// "welcome_message" = "欢迎来到%@!";
class LocalizationManager {
static func localizedString(_ key: String, comment: String = "") -> String {
return NSLocalizedString(key, comment: comment)
}
static func localizedString(_ key: String, _ args: CVarArg...) -> String {
let format = NSLocalizedString(key, comment: "")
return String(format: format, arguments: args)
}
}
// 使用
let title = LocalizationManager.localizedString("welcome_title")
let message = LocalizationManager.localizedString("welcome_message", "MyApp")
// 输出(中文环境):
// title = "欢迎"
// message = "欢迎来到MyApp!"
场景 4:加载音频资源
import AVFoundation
class AudioManager {
private var audioPlayers: [String: AVAudioPlayer] = [:]
func loadSound(named name: String, type: String) -> Bool {
guard let url = Bundle.main.url(forResource: name, withExtension: type) else {
print("Sound file not found: \(name).\(type)")
return false
}
do {
let player = try AVAudioPlayer(contentsOf: url)
player.prepareToPlay()
audioPlayers[name] = player
return true
} catch {
print("Failed to load sound: \(error)")
return false
}
}
func playSound(named name: String) {
audioPlayers[name]?.play()
}
}
// 使用
let audioManager = AudioManager()
audioManager.loadSound(named: "click", type: "mp3")
audioManager.playSound(named: "click")
场景 5:读取文本文件
extension Bundle {
func loadTextFile(named name: String, type: String) -> String? {
guard let path = self.path(forResource: name, ofType: type) else {
return nil
}
return try? String(contentsOfFile: path, encoding: .utf8)
}
func loadTextFileURL(named name: String, withExtension ext: String) -> String? {
guard let url = self.url(forResource: name, withExtension: ext) else {
return nil
}
return try? String(contentsOf: url, encoding: .utf8)
}
}
// 使用
if let licenseText = Bundle.main.loadTextFile(named: "LICENSE", type: "txt") {
print("License:\n\(licenseText)")
}
if let readmeContent = Bundle.main.loadTextFileURL(named: "README", withExtension: "md") {
print(readmeContent)
}
场景 6:在框架中使用 Bundle
// MyFramework.swift
public class MyFramework {
// 获取框架的 bundle
static var bundle: Bundle {
return Bundle(for: MyFramework.self)
}
// 加载框架资源
public static func loadFrameworkImage(named name: String) -> UIImage? {
return UIImage(named: name, in: bundle, compatibleWith: nil)
}
// 加载框架中的本地化字符串
public static func localizedString(_ key: String) -> String {
return NSLocalizedString(key, bundle: bundle, comment: "")
}
// 加载框架中的 XIB
public static func loadView<T: UIView>(_ viewType: T.Type) -> T? {
let nibName = String(describing: viewType)
let nib = UINib(nibName: nibName, bundle: bundle)
let views = nib.instantiate(withOwner: nil, options: nil)
return views.first as? T
}
}
// 使用框架
let icon = MyFramework.loadFrameworkImage(named: "framework_icon")
let title = MyFramework.localizedString("framework_title")
let customView = MyFramework.loadView(CustomView.self)
场景 7:动态插件系统
protocol PluginProtocol {
var name: String { get }
var version: String { get }
func execute()
}
class PluginManager {
private var loadedPlugins: [String: PluginProtocol] = [:]
func loadPlugin(at path: String) -> Bool {
guard let bundle = Bundle(path: path) else {
print("Failed to load bundle at: \(path)")
return false
}
do {
try bundle.loadAndReturnError()
guard let pluginClass = bundle.principalClass as? PluginProtocol.Type else {
print("Principal class does not conform to PluginProtocol")
return false
}
let plugin = pluginClass.init()
loadedPlugins[plugin.name] = plugin
print("Loaded plugin: \(plugin.name) v\(plugin.version)")
return true
} catch {
print("Failed to load plugin: \(error)")
return false
}
}
func executePlugin(named name: String) {
loadedPlugins[name]?.execute()
}
func listPlugins() -> [String] {
return Array(loadedPlugins.keys)
}
}
// 使用
let pluginManager = PluginManager()
pluginManager.loadPlugin(at: "/path/to/MyPlugin.bundle")
pluginManager.executePlugin(named: "MyPlugin")
场景 8:版本检查和更新提示
class VersionManager {
static let shared = VersionManager()
var currentVersion: String {
return Bundle.main.appVersion ?? "1.0.0"
}
var buildNumber: String {
return Bundle.main.buildNumber ?? "1"
}
func checkForUpdate(completion: @escaping (Bool, String?) -> Void) {
// 从服务器获取最新版本
fetchLatestVersion { latestVersion in
guard let latest = latestVersion else {
completion(false, nil)
return
}
let needsUpdate = self.compareVersions(
current: self.currentVersion,
latest: latest
)
completion(needsUpdate, latest)
}
}
private func compareVersions(current: String, latest: String) -> Bool {
return current.compare(latest, options: .numeric) == .orderedAscending
}
private func fetchLatestVersion(completion: @escaping (String?) -> Void) {
// 实际实现从服务器获取版本号
// 这里只是示例
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
completion("1.1.0")
}
}
}
// 使用
VersionManager.shared.checkForUpdate { needsUpdate, latestVersion in
if needsUpdate {
print("New version available: \(latestVersion ?? "Unknown")")
// 显示更新提示
}
}
场景 9:环境配置管理
enum Environment {
case development
case staging
case production
static var current: Environment {
#if DEBUG
return .development
#else
if let env = Bundle.main.infoDictionary?["Environment"] as? String {
switch env {
case "Staging": return .staging
case "Production": return .production
default: return .development
}
}
return .production
#endif
}
var apiBaseURL: String {
switch self {
case .development:
return "https://dev-api.example.com"
case .staging:
return "https://staging-api.example.com"
case .production:
return "https://api.example.com"
}
}
var isDebugEnabled: Bool {
return self == .development
}
}
// 使用
print("Current environment: \(Environment.current)")
print("API URL: \(Environment.current.apiBaseURL)")
场景 10:资源完整性检查
class ResourceValidator {
static func validateRequiredResources() -> [String] {
var missingResources: [String] = []
let requiredResources: [(String, String)] = [
("config", "json"),
("AppIcon", "png"),
("Localizable", "strings")
]
for (name, type) in requiredResources {
if Bundle.main.path(forResource: name, ofType: type) == nil {
missingResources.append("\(name).\(type)")
}
}
return missingResources
}
static func printResourceReport() {
let missing = validateRequiredResources()
if missing.isEmpty {
print("✅ All required resources are present")
} else {
print("❌ Missing resources:")
missing.forEach { print(" - \($0)") }
}
// 打印所有可用资源统计
let images = Bundle.main.paths(forResourcesOfType: "png", inDirectory: nil)
let jsons = Bundle.main.paths(forResourcesOfType: "json", inDirectory: nil)
let sounds = Bundle.main.paths(forResourcesOfType: "mp3", inDirectory: nil)
print("\n📊 Resource Statistics:")
print(" Images (PNG): \(images.count)")
print(" JSON files: \(jsons.count)")
print(" Sound files (MP3): \(sounds.count)")
}
}
// 使用(在应用启动时)
#if DEBUG
ResourceValidator.printResourceReport()
#endif
最佳实践
1. 使用扩展封装常用功能
extension Bundle {
// 简化版本信息访问
var appVersion: String? {
return infoDictionary?["CFBundleShortVersionString"] as? String
}
var buildNumber: String? {
return infoDictionary?["CFBundleVersion"] as? String
}
// 简化资源加载
func loadJSON<T: Decodable>(_ type: T.Type, from file: String) -> T? {
guard let url = url(forResource: file, withExtension: "json"),
let data = try? Data(contentsOf: url) else {
return nil
}
return try? JSONDecoder().decode(type, from: data)
}
// 检查资源存在性
func hasResource(named name: String, ofType type: String) -> Bool {
return path(forResource: name, ofType: type) != nil
}
}
2. 统一资源管理
struct Resources {
// 配置文件
struct Config {
static let main = "config"
static let server = "server_config"
}
// 图片目录
struct Images {
static let icons = "Images/Icons"
static let backgrounds = "Images/Backgrounds"
}
// 音频文件
struct Audio {
static let click = "click"
static let success = "success"
static let error = "error"
}
}
// 使用
if let configURL = Bundle.main.url(forResource: Resources.Config.main, withExtension: "json") {
// 加载配置
}
3. 类型安全的资源访问
enum Asset {
case image(String)
case json(String)
case audio(String, String) // name, extension
var url: URL? {
switch self {
case .image(let name):
return Bundle.main.url(forResource: name, withExtension: "png")
case .json(let name):
return Bundle.main.url(forResource: name, withExtension: "json")
case .audio(let name, let ext):
return Bundle.main.url(forResource: name, withExtension: ext)
}
}
func load() -> Data? {
guard let url = url else { return nil }
return try? Data(contentsOf: url)
}
}
// 使用
let configData = Asset.json("config").load()
let imageURL = Asset.image("logo").url
4. 错误处理
enum BundleError: Error {
case resourceNotFound(String)
case invalidData
case decodingFailed(Error)
}
extension Bundle {
func loadResource(named name: String, withExtension ext: String) throws -> Data {
guard let url = url(forResource: name, withExtension: ext) else {
throw BundleError.resourceNotFound("\(name).\(ext)")
}
do {
return try Data(contentsOf: url)
} catch {
throw BundleError.invalidData
}
}
func decodeJSON<T: Decodable>(_ type: T.Type, from file: String) throws -> T {
let data = try loadResource(named: file, withExtension: "json")
do {
return try JSONDecoder().decode(type, from: data)
} catch {
throw BundleError.decodingFailed(error)
}
}
}
// 使用
do {
let config = try Bundle.main.decodeJSON(AppConfig.self, from: "config")
print("Config loaded: \(config)")
} catch BundleError.resourceNotFound(let file) {
print("Resource not found: \(file)")
} catch BundleError.decodingFailed(let error) {
print("Failed to decode: \(error)")
} catch {
print("Unexpected error: \(error)")
}
5. 缓存管理
class ResourceCache {
static let shared = ResourceCache()
private var cache: [String: Any] = [:]
private let queue = DispatchQueue(label: "com.app.resource.cache")
func loadImage(named name: String) -> UIImage? {
let key = "image_\(name)"
return queue.sync {
// 检查缓存
if let cached = cache[key] as? UIImage {
return cached
}
// 从 bundle 加载
guard let url = Bundle.main.url(forResource: name, withExtension: "png"),
let image = UIImage(contentsOfFile: url.path) else {
return nil
}
// 存入缓存
cache[key] = image
return image
}
}
func clearCache() {
queue.sync {
cache.removeAll()
}
}
}
// 使用
let image = ResourceCache.shared.loadImage(named: "logo")
6. 调试辅助
#if DEBUG
extension Bundle {
func printAllResources() {
print("=== Bundle Resources ===")
print("Bundle Path: \(bundlePath)")
let types = ["png", "jpg", "json", "mp3", "txt", "plist"]
for type in types {
let resources = paths(forResourcesOfType: type, inDirectory: nil)
if !resources.isEmpty {
print("\n\(type.uppercased()) files (\(resources.count)):")
resources.forEach { path in
let name = URL(fileURLWithPath: path).lastPathComponent
print(" - \(name)")
}
}
}
}
}
// 在开发模式下打印所有资源
Bundle.main.printAllResources()
#endif
7. 单元测试支持
class BundleTests: XCTestCase {
func testRequiredResourcesExist() {
let requiredResources: [(String, String)] = [
("config", "json"),
("Localizable", "strings")
]
for (name, type) in requiredResources {
XCTAssertNotNil(
Bundle.main.path(forResource: name, ofType: type),
"\(name).\(type) not found in bundle"
)
}
}
func testBundleInfo() {
XCTAssertNotNil(Bundle.main.bundleIdentifier, "Bundle ID is nil")
XCTAssertNotNil(Bundle.main.appVersion, "App version is nil")
XCTAssertNotNil(Bundle.main.buildNumber, "Build number is nil")
}
func testConfigFileIsValid() throws {
let config = try Bundle.main.decodeJSON(AppConfig.self, from: "config")
XCTAssertFalse(config.apiBaseURL.isEmpty, "API URL is empty")
XCTAssertGreaterThan(config.timeout, 0, "Timeout must be positive")
}
}
性能优化建议
1. 延迟加载资源
class LazyResourceLoader {
private lazy var config: AppConfig? = {
return try? Bundle.main.decodeJSON(AppConfig.self, from: "config")
}()
private lazy var largeImage: UIImage? = {
guard let url = Bundle.main.url(forResource: "large_image", withExtension: "png") else {
return nil
}
return UIImage(contentsOfFile: url.path)
}()
}
2. 异步加载大文件
extension Bundle {
func loadLargeFile(named name: String,
withExtension ext: String,
completion: @escaping (Data?) -> Void) {
DispatchQueue.global(qos: .utility).async {
guard let url = self.url(forResource: name, withExtension: ext) else {
DispatchQueue.main.async {
completion(nil)
}
return
}
let data = try? Data(contentsOf: url)
DispatchQueue.main.async {
completion(data)
}
}
}
}
// 使用
Bundle.main.loadLargeFile(named: "video", withExtension: "mp4") { data in
guard let videoData = data else {
print("Failed to load video")
return
}
print("Video loaded: \(videoData.count) bytes")
}
3. 批量预加载
class ResourcePreloader {
static func preloadEssentialResources(completion: @escaping () -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
// 预加载配置
_ = try? Bundle.main.decodeJSON(AppConfig.self, from: "config")
// 预加载关键图片
let essentialImages = ["logo", "placeholder", "icon"]
for imageName in essentialImages {
_ = Bundle.main.url(forResource: imageName, withExtension: "png")
}
DispatchQueue.main.async {
completion()
}
}
}
}
// 在应用启动时调用
ResourcePreloader.preloadEssentialResources {
print("Essential resources preloaded")
}
常见问题和解决方案
问题 1:资源文件找不到
// ❌ 错误:文件名包含扩展名
let path = Bundle.main.path(forResource: "config.json", ofType: "json") // nil
// ✅ 正确方法 1:分开指定
let path = Bundle.main.path(forResource: "config", ofType: "json")
// ✅ 正确方法 2:不指定扩展名
let path = Bundle.main.path(forResource: "config.json", ofType: nil)
问题 2:框架资源访问失败
// ❌ 错误:使用主 bundle
let image = UIImage(named: "framework_icon") // nil
// ✅ 正确:使用框架的 bundle
let bundle = Bundle(for: MyFrameworkClass.self)
let image = UIImage(named: "framework_icon", in: bundle, compatibleWith: nil)
问题 3:本地化字符串不生效
// 确保 Localizable.strings 文件在正确的 .lproj 目录下
// 检查文件是否添加到 target
// 验证本地化设置
// 调试本地化
print("Preferred languages: \(Locale.preferredLanguages)")
print("Bundle localizations: \(Bundle.main.localizations)")
print("Current localization: \(Bundle.main.preferredLocalizations.first ?? "none")")
总结
最常用的 API:
- ✅
Bundle.main- 获取主 bundle - ✅
path(forResource:ofType:)- 获取资源路径 - ✅
url(forResource:withExtension:)- 获取资源 URL - ✅
NSLocalizedString- 本地化字符串 - ✅
infoDictionary- 访问 Info.plist
记住要点:
- 🎯 优先使用 URL API 而不是路径字符串
- 🌍 正确处理本地化资源
- 📦 在框架中使用正确的 bundle
- ⚡ 对大资源使用延迟加载
- 🔍 使用类型安全的资源访问模式
- ✅ 添加资源完整性检查
附录:快速参考
// 获取 Bundle
Bundle.main // 主 bundle
Bundle(for: MyClass.self) // 类所在 bundle
Bundle(path: "/path/to/bundle") // 指定路径
// 资源加载
.path(forResource:ofType:) // 获取路径
.url(forResource:withExtension:) // 获取 URL
.paths(forResourcesOfType:inDirectory:) // 批量路径
.urls(forResourcesWithExtension:subdirectory:) // 批量 URL
// 信息访问
.bundleIdentifier // Bundle ID
.infoDictionary // Info.plist
.appVersion // 版本号(扩展)
.buildNumber // 构建号(扩展)
// 本地化
NSLocalizedString(_:comment:) // 本地化字符串
.localizedString(forKey:value:table:) // 完整方法
.preferredLocalizations // 首选语言
.localizations // 所有语言
// 路径属性
.bundleURL / .bundlePath // Bundle 路径
.resourceURL / .resourcePath // 资源路径
.executableURL / .executablePath // 可执行文件路径
文档版本:1.0
最后更新:2025-10-10
适用于:Swift 5.0+, iOS 13.0+, macOS 10.15+
本文档涵盖了 Swift Bundle 的所有主要 API 和常见使用场景。建议根据实际项目需求选择合适的方法。类型:Bundle(类属性)
返回:应用程序的主 bundle
使用场景:
- 读取应用资源文件(图片、音频、配置文件)
- 获取应用版本号和构建号
- 加载本地化字符串
- 读取 Info.plist 信息
示例:
// 获取应用版本
if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
print("App Version: \(version)")
}
// 加载资源文件
if let path = Bundle.main.path(forResource: "config", ofType: "json") {
let data = try? Data(contentsOf: URL(fileURLWithPath: path))
}
2. init(for:) - 为指定类获取 Bundle
// 获取包含指定类的 bundle
let bundle = Bundle(for: MyViewController.self)
// 从该 bundle 加载资源
if let imagePath = bundle.path(forResource: "logo", ofType: "png") {
let image = UIImage(contentsOfFile: imagePath)
}
参数:aClass: AnyClass - 任意类类型
返回:包含该类的 bundle,如果找不到则返回主 bundle
使用场景:
- 在框架或库中使用,确保从正确的 bundle 加载资源
- 创建可复用的 UI 组件库
- 单元测试中加载测试资源
示例:
// 在自定义框架中使用
class MyFrameworkClass {
static func loadIcon() -> UIImage? {
let bundle = Bundle(for: MyFrameworkClass.self)
return UIImage(named: "icon", in: bundle, compatibleWith: nil)
}
}
3. init?(path:) - 通过路径创建 Bundle
// 从指定路径创建 bundle
if let bundle = Bundle(path: "/Applications/MyApp.app/Contents/PlugIns/MyPlugin.bundle") {
print("Plugin bundle loaded")
// 从插件加载资源
bundle.loadNibNamed("PluginView", owner: self, options: nil)
}
参数:path: String - bundle 的文件系统路径
返回:Bundle? - 成功返回 bundle,失败返回 nil
使用场景:
- 加载插件系统的 bundle
- 动态加载外部框架
- 访问非标准位置的 bundle
4. init?(url:) - 通过 URL 创建 Bundle
// 使用 URL 创建 bundle
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let pluginURL = documentsURL.appendingPathComponent("MyPlugin.bundle")
if let bundle = Bundle(url: pluginURL) {
print("Bundle created from URL: \(bundle.bundleURL)")
}
参数:url: URL - bundle 的 URL
返回:Bundle? - 成功返回 bundle,失败返回 nil
使用场景:
- 使用现代 URL API 加载 bundle
- 与文件管理器 API 配合使用
5. allBundles - 所有已加载的 Bundle
// 获取应用程序中所有已加载的 bundles
let allBundles = Bundle.allBundles
for bundle in allBundles {
print("Bundle: \(bundle.bundleIdentifier ?? "Unknown")")
print("Path: \(bundle.bundlePath)")
}
类型:[Bundle](类属性)
返回:当前进程中所有已加载的 bundle 数组
使用场景:
- 调试和日志记录
- 检查已加载的所有模块
- 资源冲突检测
6. allFrameworks - 所有已加载的框架 Bundle
// 获取所有框架 bundles
let frameworks = Bundle.allFrameworks
for framework in frameworks {
print("Framework: \(framework.bundleIdentifier ?? "Unknown")")
}
类型:[Bundle](类属性)
返回:所有已加载的框架 bundle 数组
使用场景:
- 检查依赖的框架
- 框架版本检测
核心属性
1. bundleURL - Bundle 的 URL
let url = Bundle.main.bundleURL
print("Bundle URL: \(url)")
// 输出:file:///var/.../YourApp.app/
类型:URL
说明:bundle 目录的完整 URL
用途:
- 获取 bundle 的完整路径
- 构建相对路径
- 文件操作
2. bundlePath - Bundle 的路径字符串
let path = Bundle.main.bundlePath
print("Bundle Path: \(path)")
// 输出:/var/.../YourApp.app
类型:String
说明:bundle 目录的文件系统路径
用途:与需要字符串路径的旧 API 配合使用
3. bundleIdentifier - Bundle 标识符
if let identifier = Bundle.main.bundleIdentifier {
print("Bundle ID: \(identifier)")
// 输出:com.company.appname
}
类型:String?
说明:bundle 的唯一标识符(Info.plist 中的 CFBundleIdentifier)
用途:
- 识别应用或框架
- 用户偏好设置的 key
- App Groups 配置
4. resourceURL - 资源目录 URL
if let resourceURL = Bundle.main.resourceURL {
print("Resources at: \(resourceURL)")
}
类型:URL?
说明:包含资源文件的目录 URL
用途:
- 访问所有资源文件
- 批量处理资源
5. resourcePath - 资源目录路径
if let resourcePath = Bundle.main.resourcePath {
print("Resources path: \(resourcePath)")
}
类型:String?
说明:资源目录的文件系统路径
6. executableURL - 可执行文件 URL
if let executableURL = Bundle.main.executableURL {
print("Executable: \(executableURL.lastPathComponent)")
}
类型:URL?
说明:bundle 的主可执行文件 URL
用途:
- 获取应用程序二进制文件位置
- 代码签名验证
7. executablePath - 可执行文件路径
if let executablePath = Bundle.main.executablePath {
print("Executable path: \(executablePath)")
}
类型:String?
8. privateFrameworksURL / privateFrameworksPath
// 私有框架目录
if let privateFrameworksURL = Bundle.main.privateFrameworksURL {
print("Private frameworks: \(privateFrameworksURL)")
}
类型:URL? / String?
说明:bundle 内私有框架的位置
9. sharedFrameworksURL / sharedFrameworksPath
// 共享框架目录(macOS)
if let sharedURL = Bundle.main.sharedFrameworksURL {
print("Shared frameworks: \(sharedURL)")
}
类型:URL? / String?
说明:共享框架目录(主要用于 macOS)
10. sharedSupportURL / sharedSupportPath
// 共享支持文件目录(macOS)
if let sharedSupportURL = Bundle.main.sharedSupportURL {
print("Shared support: \(sharedSupportURL)")
}
类型:URL? / String?
说明:共享支持文件目录(macOS)
11. builtInPlugInsURL / builtInPlugInsPath
// 内置插件目录
if let pluginsURL = Bundle.main.builtInPlugInsURL {
print("Built-in plugins: \(pluginsURL)")
}
类型:URL? / String?
说明:bundle 内置插件目录
12. appStoreReceiptURL
// App Store 收据位置
if let receiptURL = Bundle.main.appStoreReceiptURL {
print("Receipt at: \(receiptURL)")
// 检查收据是否存在
if FileManager.default.fileExists(atPath: receiptURL.path) {
print("Receipt exists")
}
}
类型:URL?
说明:App Store 购买收据的 URL
用途:
- 验证应用购买
- 收据验证
13. infoDictionary - Info.plist 字典
// 获取 Info.plist 中的所有信息
if let infoDictionary = Bundle.main.infoDictionary {
// 应用名称
if let appName = infoDictionary["CFBundleName"] as? String {
print("App Name: \(appName)")
}
// 版本号
if let version = infoDictionary["CFBundleShortVersionString"] as? String {
print("Version: \(version)")
}
// 构建号
if let build = infoDictionary["CFBundleVersion"] as? String {
print("Build: \(build)")
}
// 自定义键
if let apiKey = infoDictionary["API_KEY"] as? String {
print("API Key: \(apiKey)")
}
}
类型:[String: Any]?
说明:Info.plist 的字典表示
常用键:
CFBundleName- 应用名称CFBundleIdentifier- Bundle IDCFBundleShortVersionString- 版本号CFBundleVersion- 构建号CFBundleDisplayName- 显示名称- 自定义配置项
14. localizedInfoDictionary - 本地化的 Info.plist
// 获取当前语言的本地化信息
if let localizedInfo = Bundle.main.localizedInfoDictionary {
if let displayName = localizedInfo["CFBundleDisplayName"] as? String {
print("Localized Name: \(displayName)")
}
}
类型:[String: Any]?
说明:当前本地化语言的 Info.plist 信息
15. preferredLocalizations - 首选本地化
// 应用支持的首选本地化列表
let preferred = Bundle.main.preferredLocalizations
print("Preferred localizations: \(preferred)")
// 输出:["zh-Hans", "en"]
类型:[String]
说明:按优先级排序的本地化语言列表
16. localizations - 所有本地化
// 所有可用的本地化
let allLocalizations = Bundle.main.localizations
print("Available localizations: \(allLocalizations)")
// 输出:["en", "zh-Hans", "zh-Hant", "ja", "ko"]
类型:[String]
说明:bundle 包含的所有本地化语言
17. developmentLocalization - 开发语言
// 开发时使用的主要语言
if let devLocalization = Bundle.main.developmentLocalization {
print("Development language: \(devLocalization)")
// 输出:Base 或 en
}
类型:String?
说明:bundle 的开发语言(通常是 Base 或 en)
路径相关 API
1. path(forResource:ofType:) - 获取资源路径 ⭐ 常用
// 基本用法
if let jsonPath = Bundle.main.path(forResource: "config", ofType: "json") {
print("Config file at: \(jsonPath)")
let data = try? Data(contentsOf: URL(fileURLWithPath: jsonPath))
}
// 不指定扩展名(文件名包含扩展名)
if let path = Bundle.main.path(forResource: "config.json", ofType: nil) {
print("Found: \(path)")
}
// 文件不存在时返回 nil
if Bundle.main.path(forResource: "nonexistent", ofType: "txt") == nil {
print("File not found")
}
参数:
name: String?- 资源文件名(不含扩展名)ext: String?- 文件扩展名
返回:String? - 文件的完整路径,找不到返回 nil
使用场景:
- 加载 JSON 配置文件
- 读取文本文件
- 访问数据文件
2. path(forResource:ofType:inDirectory:) - 在子目录中查找
// 在指定子目录中查找资源
if let imagePath = Bundle.main.path(
forResource: "logo",
ofType: "png",
inDirectory: "Images/Branding"
) {
let image = UIImage(contentsOfFile: imagePath)
}
// 查找嵌套目录
if let soundPath = Bundle.main.path(
forResource: "click",
ofType: "mp3",
inDirectory: "Sounds/UI"
) {
// 播放音效
}
参数:
name: String?- 资源文件名ext: String?- 文件扩展名subpath: String?- 子目录路径
返回:String?
3. url(forResource:withExtension:) - 获取资源 URL ⭐ 推荐
// 使用 URL(现代推荐方式)
if let configURL = Bundle.main.url(forResource: "config", withExtension: "json") {
let data = try? Data(contentsOf: configURL)
let decoder = JSONDecoder()
// 解析 JSON...
}
// 加载图片
if let imageURL = Bundle.main.url(forResource: "hero", withExtension: "jpg") {
let image = UIImage(contentsOf: imageURL)
}
参数:
name: String?- 资源文件名ext: String?- 文件扩展名
返回:URL? - 文件的 URL
优势:URL 比字符串路径更现代、更安全
4. url(forResource:withExtension:subdirectory:) - 在子目录中查找 URL
// 在子目录中查找
if let videoURL = Bundle.main.url(
forResource: "intro",
withExtension: "mp4",
subdirectory: "Videos/Tutorials"
) {
// 播放视频
let player = AVPlayer(url: videoURL)
}
参数:
name: String?- 资源文件名ext: String?- 文件扩展名subpath: String?- 子目录路径
5. paths(forResourcesOfType:inDirectory:) - 批量获取同类型文件
// 获取所有 JSON 文件
let jsonPaths = Bundle.main.paths(forResourcesOfType: "json", inDirectory: nil)
for path in jsonPaths {
print("JSON file: \(URL(fileURLWithPath: path).lastPathComponent)")
}
// 获取特定目录下的所有图片
let imagePaths = Bundle.main.paths(forResourcesOfType: "png", inDirectory: "Images")
print("Found \(imagePaths.count) PNG images")
参数:
ext: String?- 文件扩展名subpath: String?- 子目录路径(nil 表示整个 bundle)
返回:[String] - 所有匹配文件的路径数组
使用场景:
- 批量加载资源
- 遍历所有配置文件
- 资源清单生成
6. urls(forResourcesWithExtension:subdirectory:) - 批量获取 URL
// 获取所有音频文件的 URL
if let audioURLs = Bundle.main.urls(forResourcesWithExtension: "mp3", subdirectory: "Audio") {
for url in audioURLs {
print("Audio: \(url.lastPathComponent)")
}
}
// 加载所有本地化字符串文件
if let stringsURLs = Bundle.main.urls(forResourcesWithExtension: "strings", subdirectory: nil) {
print("Found \(stringsURLs.count) .strings files")
}
参数:
ext: String?- 文件扩展名subpath: String?- 子目录路径
返回:[URL]? - URL 数组
7. url(forResource:withExtension:subdirectory:localization:) - 特定本地化资源
// 获取特定语言的资源
if let zhStringsURL = Bundle.main.url(
forResource: "Localizable",
withExtension: "strings",
subdirectory: nil,
localization: "zh-Hans"
) {
print("Chinese strings: \(zhStringsURL)")
}
// 获取英文版图片
if let enImageURL = Bundle.main.url(
forResource: "welcome",
withExtension: "png",
subdirectory: "Images",
localization: "en"
) {
let image = UIImage(contentsOf: enImageURL)
}
参数:
name: String?- 资源名ext: String?- 扩展名subpath: String?- 子目录localizationName: String?- 本地化名称(如 “zh-Hans”, “en”)
资源加载 API
1. loadNibNamed(_:owner:options:) - 加载 XIB 文件
// 加载 XIB 文件
if let views = Bundle.main.loadNibNamed("CustomView", owner: self, options: nil) {
if let customView = views.first as? CustomView {
self.view.addSubview(customView)
}
}
// 在自定义类中加载
class MyCustomView: UIView {
static func loadFromNib() -> MyCustomView? {
let bundle = Bundle(for: MyCustomView.self)
let nib = bundle.loadNibNamed("MyCustomView", owner: nil, options: nil)
return nib?.first as? MyCustomView
}
}
参数:
name: String- XIB 文件名(不含 .xib 扩展名)owner: Any?- File’s Owner 对象options: [UINib.OptionsKey: Any]?- 加载选项
返回:[Any]? - XIB 中的顶级对象数组
2. load() / loadAndReturnError(_:) - 加载 Bundle 代码
// 加载 bundle 的可执行代码(用于插件)
if let pluginBundle = Bundle(path: "/path/to/plugin.bundle") {
do {
try pluginBundle.loadAndReturnError()
print("Plugin loaded successfully")
// 获取插件的主类
if let pluginClass = pluginBundle.principalClass as? PluginProtocol.Type {
let plugin = pluginClass.init()
plugin.execute()
}
} catch {
print("Failed to load plugin: \(error)")
}
}
说明:动态加载 bundle 的可执行代码
使用场景:
- 插件系统
- 动态加载框架
- 运行时扩展
3. unload() - 卸载 Bundle
// 卸载之前加载的 bundle
let success = pluginBundle.unload()
if success {
print("Bundle unloaded")
}
返回:Bool - 是否成功卸载
注意:主 bundle 不能被卸载
4. preflightAndReturnError(_:) - 预检查 Bundle
// 在加载前检查 bundle 是否有效
if let bundle = Bundle(path: "/path/to/bundle") {
do {
try bundle.preflightAndReturnError()
print("Bundle is valid and can be loaded")
} catch {
print("Bundle preflight failed: \(error)")
}
}
说明:检查 bundle 是否可以被加载,但不实际加载
本地化相关 API
1. localizedString(forKey:value:table:) - 获取本地化字符串 ⭐ 常用
// 基本用法
let greeting = Bundle.main.localizedString(
forKey: "greeting",
value: "Hello",
table: nil
)
print(greeting) // 根据系统语言输出 "你好" 或 "Hello"
// 使用特定的 strings 文件
let errorMsg = Bundle.main.localizedString(
forKey: "network_error",
value: "Network Error",
table: "Errors" // 使用 Errors.strings
)
// 带默认值
let message = Bundle.main.localizedString(
forKey: "unknown_key",
value: "Default Message", // key 不存在时使用此值
table: nil
)
参数:
key: String- 本地化键value: String?- 默认值(key 不存在时返回)tableName: String?- .strings 文件名(nil 使用 Localizable.strings)
返回:String - 本地化后的字符串
2. NSLocalizedString 宏 - 简化本地化 ⭐ 最常用
// 等同于 Bundle.main.localizedString(forKey:value:table:)
let title = NSLocalizedString("app_title", comment: "应用标题")
// 带默认值
let message = NSLocalizedString("welcome_message",
value: "Welcome!",
comment: "欢迎消息")
// 使用特定表
let error = NSLocalizedString("error_title",
tableName: "Errors",
bundle: .main,
comment: "错误标题")
使用场景:
- UI 文本本地化
- 错误消息
- 提示信息
3. 本地化字符串文件示例
// Localizable.strings (English)
"greeting" = "Hello";
"app_title" = "My App";
"welcome_message" = "Welcome to our app!";
// Localizable.strings (简体中文)
"greeting" = "你好";
"app_title" = "我的应用";
"welcome_message" = "欢迎使用我们的应用!";
// Errors.strings (English)
"network_error" = "Network connection failed";
"invalid_input" = "Invalid input";
// Errors.strings (简体中文)
"network_error" = "网络连接失败";
"invalid_input" = "输入无效";
Bundle 信息
获取版本信息
extension Bundle {
/// 应用版本号(如 1.0.0)
var appVersion: String? {
return infoDictionary?["CFBundleShortVersionString"] as? String
}
/// 构建号(如 123)
var buildNumber: String? {
return infoDictionary?["CFBundleVersion"] as? String
}
/// 完整版本字符串(如 "1.0.0 (123)")
var fullVersion: String {
let version = appVersion ?? "Unknown"
let build = buildNumber ?? "Unknown"
return "\(version) (\(build))"
}
/// 应用名称
var appName: String? {
return infoDictionary?["CFBundleName"] as? String
}
/// 显示名称(用户看到的名称)
var displayName: String? {
return infoDictionary?["CFBundleDisplayName"] as? String
}
}
// 使用
print("App: \(Bundle.main.displayName ?? "Unknown")")
print("Version: \(Bundle.main.fullVersion)")
获取自定义配置
// Info.plist 中添加自定义键值
// <key>API_BASE_URL</key>
// <string>https://api.example.com</string>
extension Bundle {
var apiBaseURL: String? {
return infoDictionary?["API_BASE_URL"] as? String
}
var apiKey: String? {
return infoDictionary?["API_KEY"] as? String
}
var isDebugMode: Bool {
#if DEBUG
return true
#else
return infoDictionary?["DEBUG_MODE"] as? Bool ?? false
#endif
}
}
// 使用
if let apiURL = Bundle.main.apiBaseURL {
print("API URL: \(apiURL)")
}
类和可执行文件
1. principalClass - 主类
// 获取 bundle 的主类(Info.plist 中的 NSPrincipalClass)
if let principalClass = Bundle.main.principalClass {
print("Principal class: \(principalClass)")
}
// 在插件中使用
if let pluginBundle = Bundle(path: "/path/to/plugin.bundle"),
let pluginClass = pluginBundle.principalClass as? PluginProtocol.Type {
let plugin = pluginClass.init()
plugin.run()
}
2. classNamed(_:) - 通过名称获取类
// 通过字符串名称获取类
if let viewControllerClass = Bundle.main.classNamed("MyApp.HomeViewController") as? UIViewController.Type {
let vc = viewControllerClass.init()
navigationController?.pushViewController(vc, animated: true)
}
// 从框架加载类
let frameworkBundle = Bundle(for: SomeFrameworkClass.self)
if let customClass = frameworkBundle.classNamed("MyFramework.CustomView") {
print("Class loaded: \(customClass)")
}
**
Giscus 未启用:请在
src/site.config.ts中配置 repoId 与 categoryId。