Exploring iOS Componentization
This article introduces iOS componentization using CocoaPods, detailing the drawbacks of manual code copying, explaining the creation of local podspec libraries, configuring source files, resources, access levels, and integration steps, while also covering sub‑specs, resource bundles, and practical code examples for Swift projects.
Exploring iOS Componentization
Componentization of iOS projects has become a mature practice in the industry, yet many teams still copy source files manually between projects, leading to duplicated code, difficult maintenance, and poor debugging experience. This article presents a practical workflow for modularizing an iOS app using CocoaPods.
1. Background
Manual copying of FeatureModuleA and its dependency FeatureModuleB into new projects creates multiple independent copies. Updating the module requires editing every copy, which is error‑prone and hampers unified management.
2. Basic Principle
The chosen approach relies on cocoapods to create a local pod library that encapsulates the component. The component is stored in its own Git repository and integrated into consuming apps via a Podfile entry.
2.1 Creating a Component Library
Two common ways to generate the initial pod structure:
pod lib create – scaffolds a full template for a new component. lanfudong@MacBook-Pro ~ % pod lib create QRCodeReader (The command asks a few questions and creates a folder containing QRCodeReader.podspec , an Example project, and template source files.)
pod spec create – creates only an empty .podspec file for an existing module. lanfudong@MacBook-Pro ~ % pod spec create QRCodeReader
2.2 Editing the podspec
A typical .podspec looks like the following (key fields are highlighted):
Pod::Spec.new do |spec|
spec.name = "QRCodeReader"
spec.version = "0.1.0"
spec.summary = "A short description of QRCodeReader."
spec.description = <<-DESC
TODO: Add long description of the pod here.
DESC
spec.license = { :type => "MIT", :file => "LICENSE" }
spec.author = { "lanfudong" => "[email protected]" }
spec.source = { :git => "https://github.com/lanfudong/QRCodeReader.git", :tag => spec.version.to_s }
spec.ios.deployment_target = "10.0"
spec.source_files = "QRCodeReader/Classes/**/*"
spec.resource_bundle = { "QRCodeReader" => "Resources/**/*.{xcassets,json,plist}" }
spec.xcconfig = { 'DEFINES_MODULE' => 'YES' }
spec.frameworks = "AudioToolbox", "AVFoundation"
endThe spec defines the module name, version, summary, license, author, source location, deployment target, source files, resource bundle, build settings, and required frameworks.
3. Adjusting Access Levels
Swift defaults to internal visibility, which restricts symbols to the same module. After modularization, public APIs must be marked public or open so that consuming apps can import the component.
Access Levels open – highest level, usable and subclassable outside the module. public – usable outside the module but not subclassable. internal – default, visible only inside the module. fileprivate – visible within the same source file. private – visible only within the declaring scope.
4. Integrating the Component
Add the component to the app’s Podfile :
pod 'QRCodeReader', :path => '../QRCodeReader'Run pod install . Xcode will show a Development Pods group containing the component.
5. Managing Resource Files
Resources (images, JSON, plist, etc.) should be placed in a Resources folder inside the component and referenced via resource_bundle to avoid name collisions.
Loading an image from the component’s bundle requires specifying the bundle explicitly:
// App main bundle root path
let mainPath = Bundle.main.resourcePath
// QRCodeReader.bundle relative path
let pathComponent = "/Frameworks/QRCodeReader.framework/QRCodeReader.bundle"
let bundle = Bundle(path: mainPath + pathComponent)
let image = UIImage(named: imageName, in: bundle, compatibleWith: nil)A helper that works for both dynamic (framework) and static library integrations:
public func image(named: String, in bundleName: String) -> UIImage? {
if let img = _dynamicImage(named: named, in: bundleName) { return img }
if let img = _staticImage(named: named, in: bundleName) { return img }
return UIImage(named: named) // fallback
}
private func _dynamicImage(named: String, in bundleName: String) -> UIImage? {
let pathComponent = "/Frameworks/\(bundleName).framework/\(bundleName).bundle"
return _image(named: named, with: pathComponent)
}
private func _staticImage(named: String, in bundleName: String) -> UIImage? {
let pathComponent = "/\(bundleName).bundle"
return _image(named: named, with: pathComponent)
}
private func _image(named: String, with pathComponent: String) -> UIImage? {
guard let mainPath = Bundle.main.resourcePath else { return nil }
let bundle = Bundle(path: mainPath + pathComponent)
return UIImage(named: named, in: bundle, compatibleWith: nil)
}6. Sub‑components (subspecs)
When a library contains several small modules, they can be expressed as subspec s inside the same podspec. Example:
spec.subspec 'QRCodeReader' do |reader|
reader.source_files = 'QRCodeReader/**/*.{swift,m,c,h}'
end
spec.subspec 'ImageViewer' do |viewer|
viewer.source_files = 'ImageViewer/**/*.{swift,m,c,h}'
end
spec.subspec 'ImagePicker' do |picker|
picker.source_files = 'ImagePicker/**/*.{swift,m,c,h}'
endConsumers can integrate the whole library or select individual subspecs:
# Integrate all subspecs
pod 'FocusUtility', :path => '../FocusUtility'
# Integrate only QRCodeReader
pod 'FocusUtility/QRCodeReader', :path => '../FocusUtility'7. Conclusion
The guide demonstrates a minimal yet functional iOS componentization workflow: decoupling code, configuring a local CocoaPods library, handling access levels, managing resources, and optionally defining subspecs. While the example is simple, real‑world projects may require more sophisticated dependency management, versioning, and automation.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.