Swift componentized resource management

Summary after learning the componentized resource file management scheme

origin

Before componentization, all code and resource files were in the main project, and a large number of image resources would inevitably cause naming conflicts. In general, we need a strict naming convention, such as adding prefixes according to the division of modules, to avoid naming conflicts.

The company adopts a componentized solution, but the resource files are not split and placed in the main project. It is very painful to maintain components and projects. We split the main project resource file into the corresponding Pod, and let the component manage it by itself. In this way, the resource files are split into each Pod, which makes the division of responsibilities more reasonable, reduces external dependencies, and facilitates independent operation.

Features provided by CocoaPods

CocoaPods provides use_frameworks, static_framework, resource, resource_bundlesthe choice of these functions, and the final storage path of resource files after some of these functions are used in combination, resulting in various processing of image files, font files, localization files, and other files by users. The way is unknown.

Resource file storage path in combined scenarios

use_frameworks!Different combinations of , static_framework, resource_bundles, and resources, will result in different final storage paths for resource files. The following is a summary of the paths in various scenarios:

# use_frameworks! 动态库
# static_framework 静态库 podspec文件中不声明,默认为false
# resource_budles 生成独立的bundle
# resources 不生成独立的bundle,放置在.app根目录
# ---------------------------------------------------------------------------
use_frameworks! 
	static_framework = false
		resource_bundles: .app/Frameworks/pod_name.framework/pod_name.bundle/.
		resources: .app/Frameworks/pod_name.framework/.
	static_framework = true
		resource_bundles: .app/pod_name.bundle/.
		resources: .app/.

# use_frameworks!
	static_framework = false
		resource_bundles: .app/pod_name.bundle/.
		resources: .app/.
	static_framework = true
		resource_bundles: .app/pod_name.bundle/.
		resources: .app/.
复制代码

(.app/. represents the main bundle)

You can refer to this article for the image value method and difference under different paths: [CocoaPods resource management and Asset Catalog optimization]( dreampiggy.com/2018/11/26/… Catalog optimization/)

It should be noted that when the .xcassetstype finally stored in the main bundle, it will .xcassetsconflict with the file of the main project, and the compilation will report an error:

error: Multiple commands produce ‘/Users/gonghonglou/Library/Developer/Xcode/DerivedData/HoloResource-giqfnwiluvssbzbyvuhpkrjoewvd/Build/Products/Debug-iphonesimulator/HoloResource_Example.app/Assets.car’: \1) Target ‘HoloResource_Example’ (project ‘HoloResource’) has compile command with input ‘/Users/gonghonglou/holo/HoloResource/Example/HoloResource/Images.xcassets’ \2) That command depends on command in Target ‘HoloResource_Example’ (project ‘HoloResource’): script phase “[CP] Copy Pods Resources”

解决方法,在 Podfile 里添加:

install! 'cocoapods', :disable_input_output_paths => true
复制代码

Tip: resource+不使用use_frameworks! +assets时会编译失败,因为工程只会生成一份Assets.car文件

Showing All Messages

2022-03-26 20:42:51.046 ibtoold[25007:9169669] Launching AssetCatalogSimulatorAgent using native architecture.

/* com.apple.actool.errors */

: error: There are multiple stickers icon set or app icon set instances named "AppIcon".

Off-topic: After Xcode 10, a new compilation system was introduced, which made it impossible to compile into the project in time if there were code changes in the Pod. The solution is also the above method.

参考:Build System Release Notes for Xcode 10

Resource Handling Practices

ImageUIImage

public extension Resource where Base: UIImage {

    /// 加载图片资源
    /// - Parameters:
    ///   - name: 图片名
    ///   - bundleName: s.resource_bundles中设置的名字 一般为组件名
    /// - Returns: description
    static func loadImage(name: String, bundleName: String) -> UIImage? {
        /// 静态库+独立bundle
        var image = loadImage1(name: name, in: bundleName)
        if image == nil {
            /// 动态库+独立bundle
            image = loadImage2(name: name, in: bundleName)
        }
        if image == nil {
            /// 动态库+非独立bundle
            image = loadImage3(name: name, in: bundleName)
        }
        if image == nil {
            // 根目录 .app/
            image = loadImage4(name: name, in: bundleName)
        }
        return image
    }
    
    /// 加载  .app/pod_name.bundle/.
    /// - Parameters:
    ///   - name: 图片名
    ///   - bundleName: 组件名
    /// - Returns: description
    fileprivate static func loadImage1(name: String, in bundleName: String) -> UIImage? {
        let pathComponent = "/\(bundleName).bundle"
        return commonLoadImage(name: name, in: pathComponent)
    }

    /// 加载  .app/Frameworks/pod_name.framework/pod_name.bundle/.
    /// - Parameters:
    ///   - name: 图片名
    ///   - bundleName: 组件名
    /// - Returns: description
    fileprivate static func loadImage2(name: String, in bundleName: String) -> UIImage? {
        let pathComponent = "/Frameworks/\(bundleName).framework/\(bundleName).bundle"
        return commonLoadImage(name: name, in: pathComponent)
    }

    /// 加载.app/Frameworks/pod_name.framework/.
    /// - Parameters:
    ///   - name: 图片名
    ///   - bundleName: 组件名
    /// - Returns: description
    fileprivate static func loadImage3(name: String, in bundleName: String) -> UIImage? {
        let pathComponent = "/Frameworks/\(bundleName).framework"
        return commonLoadImage(name: name, in: pathComponent)
    }

    /// 加载.app/
    /// - Parameters:
    ///   - name: 图片名
    ///   - bundleName: 组件名
    /// - Returns: description
    fileprivate static func loadImage4(name: String, in bundleName: String) -> UIImage? {
        return UIImage(named: name)
    }

    fileprivate static func commonLoadImage(name: String, in pathComponent: String) -> UIImage? {
        guard let resourcePath: String = Bundle.main.resourcePath else { return nil }
        let bundlePath = resourcePath + pathComponent
        let bundle = Bundle(path: bundlePath)
        return UIImage(named: name, in: bundle, compatibleWith: nil)
    }

}
复制代码

Each component UIImage.resource.loadImage(name: "图片名", bundleName: "组件名")call can access the pictures in its own bundle.

The four paths mentioned here are prioritized:

1. The first access is the path of the static library + resource_bundles2, the second is the dynamic library path using use_frameworks!+ 3, the dynamic library path using + resource_bundlesis accessed again 4, and finally the main bundle pathuse_frameworks!resources

The most recommended and reasonable configuration method is of course the first one, providing static libraries and using to resource_bundlesstore resource files.

Path Bundle

Since pictures can be encapsulated in this way, then refer to this idea to encapsulate a bundle as a basic method. With a unified bundle calling method, access to other resource files is much simpler.

public extension Resource where Base: Bundle {

    /// 加载图片资源
    /// - Parameters:
    ///   - name: 图片名
    ///   - bundleName: s.resource_bundles中设置的名字 一般为组件名
    /// - Returns: description
    static func loadBundle(in bundleName: String) -> Bundle? {
        var image = loadBundle1(in: bundleName)
        if image == nil {
            image = loadBundle2(in: bundleName)
        }
        if image == nil {
            image = loadBundle3(in: bundleName)
        }
        if image == nil {
            image = loadBundle4(in: bundleName)
        }
        return image
    }

    /// 加载  .app/pod_name.bundle/.
    /// - Parameters:
    ///   - name: 图片名
    ///   - bundleName: 组件名
    /// - Returns: description
    fileprivate static func loadBundle1(in bundleName: String) -> Bundle? {
        let pathComponent = "/\(bundleName).bundle"
        return commonLoadBundle(in: pathComponent)
    }

    /// 加载  .app/Frameworks/pod_name.framework/pod_name.bundle/.
    /// - Parameters:
    ///   - name: 图片名
    ///   - bundleName: 组件名
    /// - Returns: description
    fileprivate static func loadBundle2(in bundleName: String) -> Bundle? {
        let pathComponent = "/Frameworks/\(bundleName).framework/\(bundleName).bundle"
        return commonLoadBundle(in: pathComponent)
    }

    /// 加载.app/Frameworks/pod_name.framework/.
    /// - Parameters:
    ///   - name: 图片名
    ///   - bundleName: 组件名
    /// - Returns: description
    fileprivate static func loadBundle3(in bundleName: String) -> Bundle? {
        let pathComponent = "/Frameworks/\(bundleName).framework"
        return commonLoadBundle(in: pathComponent)
    }

    /// 加载.app/
    /// - Parameters:
    ///   - name: 图片名
    ///   - bundleName: 组件名
    /// - Returns: description
    fileprivate static func loadBundle4(in bundleName: String) -> Bundle? {
        return Bundle.main
    }

    fileprivate static func commonLoadBundle(in pathComponent: String) -> Bundle? {
        guard let resourcePath = Bundle.main.resourcePath else { return nil }
        let bundlePath = resourcePath + pathComponent
        let bundle = Bundle(path: bundlePath)
        return bundle
    }

}
复制代码

Practice Demo link

grateful

Componentized resource file management solution

Guess you like

Origin juejin.im/post/7079400388352278564