CocoaPods - Subspecs Organization
Organize complex libraries into modular subspecs for better maintainability and optional features.
What Are Subspecs?
Subspecs allow you to split a pod into logical modules that can be installed independently or as a group.
Benefits
-
Modularity: Separate core functionality from optional features
-
Selective Installation: Users install only what they need
-
Reduced Dependencies: Optional features don't force unnecessary dependencies
-
Better Organization: Clear separation of concerns
Basic Subspec Pattern
Pod::Spec.new do |spec| spec.name = 'MyLibrary' spec.version = '1.0.0'
Main spec has no source files - all in subspecs
spec.default_subspecs = 'Core'
Core subspec - installed by default
spec.subspec 'Core' do |core| core.source_files = 'Source/Core/**/*.swift' core.frameworks = 'Foundation' end
Optional feature subspec
spec.subspec 'Networking' do |networking| networking.source_files = 'Source/Networking/**/*.swift' networking.dependency 'MyLibrary/Core' # Depends on Core networking.dependency 'Alamofire', '~> 5.0' end
Another optional feature
spec.subspec 'UI' do |ui| ui.source_files = 'Source/UI/**/*.swift' ui.dependency 'MyLibrary/Core' ui.ios.frameworks = 'UIKit' ui.osx.frameworks = 'AppKit' end end
Dependency Patterns
Subspec Dependencies
Pod::Spec.new do |spec| spec.name = 'MySDK'
Foundation layer
spec.subspec 'Core' do |core| core.source_files = 'Source/Core/**/*.swift' end
Networking depends on Core
spec.subspec 'Networking' do |net| net.source_files = 'Source/Networking/**/*.swift' net.dependency 'MySDK/Core' net.dependency 'Alamofire', '~> 5.0' end
Analytics depends on Core and Networking
spec.subspec 'Analytics' do |analytics| analytics.source_files = 'Source/Analytics/**/*.swift' analytics.dependency 'MySDK/Core' analytics.dependency 'MySDK/Networking' end end
External Dependencies Per Subspec
spec.subspec 'SQLite' do |sqlite| sqlite.source_files = 'Source/SQLite/**/*.swift' sqlite.dependency 'MyLibrary/Core' sqlite.dependency 'SQLite.swift', '~> 0.14' sqlite.libraries = 'sqlite3' end
spec.subspec 'Realm' do |realm| realm.source_files = 'Source/Realm/**/*.swift' realm.dependency 'MyLibrary/Core' realm.dependency 'RealmSwift', '~> 10.0' end
Default Subspecs
Single Default Subspec
Pod::Spec.new do |spec| spec.name = 'MyLibrary'
When users do: pod 'MyLibrary'
Only Core is installed
spec.default_subspecs = 'Core'
spec.subspec 'Core' do |core| core.source_files = 'Source/Core/**/*.swift' end
spec.subspec 'Extensions' do |ext| ext.source_files = 'Source/Extensions/**/*.swift' ext.dependency 'MyLibrary/Core' end end
Multiple Default Subspecs
Pod::Spec.new do |spec| spec.name = 'MySDK'
When users do: pod 'MySDK'
Both Core and Networking are installed
spec.default_subspecs = 'Core', 'Networking'
spec.subspec 'Core' do |core| core.source_files = 'Source/Core/**/*.swift' end
spec.subspec 'Networking' do |net| net.source_files = 'Source/Networking/**/*.swift' net.dependency 'MySDK/Core' end
spec.subspec 'Analytics' do |analytics| analytics.source_files = 'Source/Analytics/**/*.swift' analytics.dependency 'MySDK/Core' # Optional - not installed by default end end
Platform-Specific Subspecs
Pod::Spec.new do |spec| spec.name = 'CrossPlatformLib'
Shared core
spec.subspec 'Core' do |core| core.source_files = 'Source/Core/**/*.swift' core.frameworks = 'Foundation' end
iOS-only subspec
spec.subspec 'iOS' do |ios| ios.source_files = 'Source/iOS/**/*.swift' ios.dependency 'CrossPlatformLib/Core' ios.ios.deployment_target = '13.0' ios.ios.frameworks = 'UIKit' end
macOS-only subspec
spec.subspec 'macOS' do |macos| macos.source_files = 'Source/macOS/**/*.swift' macos.dependency 'CrossPlatformLib/Core' macos.osx.deployment_target = '10.15' macos.osx.frameworks = 'AppKit' end end
Resource Bundles in Subspecs
spec.subspec 'UI' do |ui| ui.source_files = 'Source/UI/**/*.swift'
Each subspec can have its own resource bundle
ui.resource_bundles = { 'MyLibrary_UI' => [ 'Resources/UI//*.{png,jpg,xcassets}', 'Resources/UI//*.{storyboard,xib}' ] }
ui.dependency 'MyLibrary/Core' end
spec.subspec 'Themes' do |themes| themes.source_files = 'Source/Themes/**/*.swift'
themes.resource_bundles = { 'MyLibrary_Themes' => ['Resources/Themes/**/*'] }
themes.dependency 'MyLibrary/UI' end
Common Subspec Patterns
Core + Optional Features
Pod::Spec.new do |spec| spec.name = 'MyFramework' spec.default_subspecs = 'Core'
Required core functionality
spec.subspec 'Core' do |core| core.source_files = 'Source/Core/**/*.swift' end
Optional: JSON serialization
spec.subspec 'JSON' do |json| json.source_files = 'Source/JSON/**/*.swift' json.dependency 'MyFramework/Core' json.dependency 'SwiftyJSON', '~> 5.0' end
Optional: XML support
spec.subspec 'XML' do |xml| xml.source_files = 'Source/XML/**/*.swift' xml.dependency 'MyFramework/Core' end
Optional: Networking
spec.subspec 'Networking' do |net| net.source_files = 'Source/Networking/**/*.swift' net.dependency 'MyFramework/Core' net.dependency 'Alamofire', '~> 5.0' end end
Layered Architecture
Pod::Spec.new do |spec| spec.name = 'MySDK'
Layer 1: Foundation
spec.subspec 'Foundation' do |foundation| foundation.source_files = 'Source/Foundation/**/*.swift' end
Layer 2: Data (depends on Foundation)
spec.subspec 'Data' do |data| data.source_files = 'Source/Data/**/*.swift' data.dependency 'MySDK/Foundation' end
Layer 3: Domain (depends on Data)
spec.subspec 'Domain' do |domain| domain.source_files = 'Source/Domain/**/*.swift' domain.dependency 'MySDK/Data' end
Layer 4: Presentation (depends on Domain)
spec.subspec 'Presentation' do |presentation| presentation.source_files = 'Source/Presentation/**/*.swift' presentation.dependency 'MySDK/Domain' presentation.ios.frameworks = 'UIKit' end end
Protocol + Implementations
Pod::Spec.new do |spec| spec.name = 'MyStorage'
Protocol definitions
spec.subspec 'Core' do |core| core.source_files = 'Source/Core/**/*.swift' end
UserDefaults implementation
spec.subspec 'UserDefaults' do |ud| ud.source_files = 'Source/UserDefaults/**/*.swift' ud.dependency 'MyStorage/Core' end
Keychain implementation
spec.subspec 'Keychain' do |keychain| keychain.source_files = 'Source/Keychain/**/*.swift' keychain.dependency 'MyStorage/Core' keychain.dependency 'KeychainAccess', '~> 4.0' end
SQLite implementation
spec.subspec 'SQLite' do |sqlite| sqlite.source_files = 'Source/SQLite/**/*.swift' sqlite.dependency 'MyStorage/Core' sqlite.libraries = 'sqlite3' end end
User Installation Patterns
Installing Default Subspecs
Installs default subspecs only
pod 'MyLibrary'
Installing Specific Subspecs
Install only Core
pod 'MyLibrary/Core'
Install Core and Networking
pod 'MyLibrary/Core' pod 'MyLibrary/Networking'
Or more concisely
pod 'MyLibrary', :subspecs => ['Core', 'Networking']
Installing All Subspecs
Install everything (not recommended - bloats dependency tree)
No built-in way - user must list each subspec
Nested Subspecs
spec.subspec 'Networking' do |net|
Nested subspec: Networking/REST
net.subspec 'REST' do |rest| rest.source_files = 'Source/Networking/REST/**/*.swift' rest.dependency 'MyLibrary/Core' end
Nested subspec: Networking/GraphQL
net.subspec 'GraphQL' do |graphql| graphql.source_files = 'Source/Networking/GraphQL/**/*.swift' graphql.dependency 'MyLibrary/Core' graphql.dependency 'Apollo', '~> 1.0' end end
Users install with:
pod 'MyLibrary/Networking/REST'
pod 'MyLibrary/Networking/GraphQL'
Best Practices
Directory Structure
MyLibrary/ ├── MyLibrary.podspec ├── Source/ │ ├── Core/ # Core subspec │ ├── Networking/ # Networking subspec │ ├── UI/ # UI subspec │ └── Analytics/ # Analytics subspec ├── Resources/ │ ├── Core/ │ ├── UI/ │ └── Analytics/ └── Tests/ ├── CoreTests/ ├── NetworkingTests/ └── UITests/
Naming Conventions
Use clear, descriptive names
spec.subspec 'Networking' # Good spec.subspec 'Net' # Too abbreviated
Group related functionality
spec.subspec 'UI' spec.subspec 'UIComponents' spec.subspec 'UIExtensions'
Platform suffixes when needed
spec.subspec 'iOS' spec.subspec 'macOS'
Dependency Guidelines
Keep dependency chains shallow
spec.subspec 'A' do |a| a.dependency 'MyLib/Core' # 1 level - Good end
spec.subspec 'B' do |b| b.dependency 'MyLib/A' # 2 levels - OK end
spec.subspec 'C' do |c| c.dependency 'MyLib/B' # 3 levels - Consider flattening end
Anti-Patterns
Don't
❌ Create too many small subspecs
Over-granular
spec.subspec 'StringExtensions' spec.subspec 'ArrayExtensions' spec.subspec 'DictionaryExtensions'
Better: Group as 'Extensions'
❌ Circular dependencies
spec.subspec 'A' do |a| a.dependency 'MyLib/B' end
spec.subspec 'B' do |b| b.dependency 'MyLib/A' # CIRCULAR - Will fail end
❌ Duplicate source files
spec.subspec 'Core' do |core| core.source_files = 'Source/**/*.swift' # Includes everything end
spec.subspec 'Utils' do |utils| utils.source_files = 'Source/Utils/**/*.swift' # DUPLICATE end
Do
✅ Group related functionality
spec.subspec 'Extensions' do |ext| ext.source_files = 'Source/Extensions/**/*.swift' end
✅ Use clear dependency hierarchy
spec.subspec 'A' do |a| a.dependency 'MyLib/Core' end
spec.subspec 'B' do |b| b.dependency 'MyLib/Core' # Both depend on Core - Good end
✅ Keep source files separate
spec.subspec 'Core' do |core| core.source_files = 'Source/Core/**/*.swift' end
spec.subspec 'Utils' do |utils| utils.source_files = 'Source/Utils/**/*.swift' end
Testing Subspecs
Lint specific subspec
pod lib lint --include-podspecs=*.podspec
Test specific subspec in example project
cd Example pod install
Then build/run in Xcode
Related Skills
-
cocoapods-podspec-fundamentals
-
cocoapods-test-specs
-
cocoapods-publishing-workflow