Optional CocoaPod dependencies using Subspecs


Several of our Kite Print SDK iOS customers have asked if the somewhat large (as in file size) PayPal SDK dependency could be optional when using our library as they had no need for it. It turns out this isn’t an overly tricky thing to do if you know what to search for. The magic CocoaPod term you’re looking for (and the one that initially eluded me) is “subspec”.

Podfile usage

CocoaPod subspec usage is best illustrated with a couple of example Podfile dependencies:

pod "Kite-Print-SDK", "~> 1.0"

The above will install the lightweight version of the Kite SDK that does not include optional PayPal & ApplePay dependencies - If you wanted those too:

pod "Kite-Print-SDK", "~> 1.0"
pod "Kite-Print-SDK/PayPal", "~> 1.0"
pod "Kite-Print-SDK/ApplePay", "~> 1.0"

Notice the explicit reference to the PayPal & ApplePay subspecs.

Podspec overview

Now lets take a look at the Print SDK Podspec to see how these CocoaPod subspecs feature to make the above possible:

# Kite-Print-SDK.podspec
Pod::Spec.new do |spec|
  spec.name		= 'Kite-Print-SDK'
  spec.version  = '1.0.2'
  spec.license  =  'MIT'
  spec.homepage = 'https://github.com/OceanLabs/iOS-Print-SDK'
  spec.authors  = {'Deon Botha' => 'deon@oceanlabs.co'}
  spec.summary  = '...'
  spec.source   = {
    :git => 'https://github.com/OceanLabs/iOS-Print-SDK.git', 
    :tag => '1.0.2'}
  spec.source_files = 
    ['PSPrintSDK/OL*.{h,m}', 'PSPrintSDK/CardIO*.h']
  spec.resources = 
    ['PSPrintSDK/KitePrintSDK.xcassets', '*.lproj']
  spec.dependency	'SDWebImage'
  spec.dependency	'SVProgressHUD'
  spec.dependency	'AFNetworking', '2.5.0'
  spec.dependency	'UICKeyChainStore', '~> 1.0.4' 
  spec.requires_arc	= true
  spec.platform		= :ios, '7.0'
  spec.social_media_url	= 'https://twitter.com/dbotha'
  spec.default_subspec = 'Lite'

  spec.subspec 'Lite' do |lite|
  # subspec for users who don't want the third party PayPal 
  # & Stripe bloat

  spec.subspec 'PayPal' do |paypal|
    paypal.xcconfig	=  
        { 'OTHER_CFLAGS' => '$(inherited) -DKITE_OFFER_PAYPAL' }
    paypal.dependency	'PayPal-iOS-SDK', '~> 2.4.2'

  spec.subspec 'ApplePay' do |apple|
    apple.xcconfig =   
        { 'OTHER_CFLAGS' => '$(inherited) -DKITE_OFFER_APPLE_PAY' }
    apple.dependency	  'Stripe', '2.2.0'
    apple.dependency	  'Stripe/ApplePay'

Most of spec is boilerplate and should be somewhat familiar if you’ve ever written one before. The interesting bits are the three spec.subspec blocks & the spec.default_subspec = 'Lite' line.

The Subspecs

There are three subspecs included in the above Podspec:

  • Lite - This is an empty subspec that we’ll later see forms the default subspec. If it were the sole dependency reference in your Podfile (i.e. pod "Kite-Print-SDK/Lite") the PayPal & ApplePay dependencies would be excluded and the Kite SDK footprint would be that much smaller.
  • PayPal - This subspec includes the PayPal iOS SDK as a dependency. It also sets a preprocessor macro that is used in the Kite SDK source code to conditionally enable PayPal payment functionality within the checkout process.
  • ApplePay - This subspec includes Stripe iOS SDK + Apple Pay functionality as a dependency. It also sets a preprocessor macro that is used in the Kite SDK source code to conditionally enable ApplePay payment functionality within the checkout process.

All subspecs implicitly inherit from the root specification that encloses them. Preprocessor macros are used to optionally enable functionality within the Kite Print SDK.

Default subspec

If you don’t explicitly set the default_subspec then all subspec blocks (and the main/root specification) will be included and the full library with PayPal & ApplePay dependencies will be made available by default.

I explicitly override the default behaviour to target my empty Lite subspec. This means the following two Podfile dependency lines are equivalent:

pod "Kite-Print-SDK", "~> 1.0"
pod "Kite-Print-SDK/Lite", "~> 1.0"

It should be noted though that this goes against CocoaPod best practice:

A Pod should make available the full library by default. Users can fine tune their dependencies, and exclude unneeded subspecs, once their requirements are known. Therefore, this attribute is rarely needed. It is intended to be used to select a default if there are ‘sub-specifications’ which provide alternative incompatible implementations, or to exclude modules rarely needed (especially if they trigger dependencies on other libraries).

In our case it just felt like a more sensible default based on some of the feedback I’d been getting from our SDK users.

And that’s how you can use CocoaPod subspecs to reduce the footprint of your own libraries in certain configurations.