Linting your podspec after introducing Swift

March 30th, 2016
#cocoapods

After converting ConvenientFileManager from an Objective-C to Swift project I ran into the following error when linting the pod spec (pod spec lint):

-> ConvenientFileManager (2.0.0)
    - ERROR | [iOS] public_header_files: The pattern matches non-header files (ConvenientFileManager/Cache/NSFileManager+CFMCache.swift, ConvenientFileManager/Documents/NSFileManager+CFMDocuments.swift, ConvenientFileManager/Persistence/NSFileManager+CFMPersistence.swift).
    - ERROR | [iOS] xcodebuild: Returned an unsuccessful exit code. You can use `--verbose` for more information.
    - NOTE  | xcodebuild:  :1:9: note: in file included from :1:
    - ERROR | xcodebuild:  Target Support Files/ConvenientFileManager/ConvenientFileManager-umbrella.h:3:9: error: 'NSFileManager+CFMCache.swift' file not found
    - NOTE  | xcodebuild:  :0: error: could not build Objective-C module 'ConvenientFileManager'
🤷‍♂️

Taking the advice from the above error message, I re-ran the linting in verbose mode (--verbose), which threw up some more information:

<module-includes>:1:9: note: in file included from <module-includes>:1:
#import "Headers/ConvenientFileManager-umbrella.h"
        ^
/var/folders/2v/51_f5pnn7y5dq0yz453yb4q40000gq/T/CocoaPods/Lint/Pods/Target Support Files/ConvenientFileManager/ConvenientFileManager-umbrella.h:3:9: error: 'NSFileManager+CFMCache.swift' file not found
#import "NSFileManager+CFMCache.swift"
        ^
<unknown>:0: error: could not build Objective-C module 'ConvenientFileManager'

After a few hours of fruitless tweaking of various project settings, I stumbled upon this issue in the Swift MongoDB project. As it turned out, this error was related to what I had in the podspec rather than in the project.

During the conversion to Swift I updated the project's podspec to remove all references to .h and .m files by replacing them with .swift, this resulted in two changes:

s.source_files  = "ConvenientFileManager/**/*.{h,m}"
s.public_header_files = "ConvenientFileManager/**/*.{h}"

to

s.source_files  = "ConvenientFileManager/**/*.swift"
s.public_header_files = "ConvenientFileManager/**/*.swift"

In my haste to publish this new pod version, I didn't stop to think what source_files and public_header_files were responsible for and this lack of understanding lay at the heart of the pod linting error.

As you can see with the original podspec code snippet above, ConvenientFileManager is exposing through the public_header_files configuration option which headers are accessible when this pod is used. While not the case with ConvenientFileManager in some pods there are files that are meant for internal use only and this is where we come up against one of Objective-C's sharp edges: access control. Objective-C has a crude form of file access control - a header is considered public if it is externally exposed and internal if it is not. There is no way to indicate in the code if a file is public or internal, public_header_files is a hack to try and overcome this by allowing the pod creator to define a regex to control which headers are exposed.

Swift combines the header (.h) and implementation (.m) files into one file (.swift) and has a more sophisticated suite of access control options. These improved access control options allow us to declare in code which classes should be externally accessible through the open, public, internal, fileprivate and private keywords. Any internal, fileprivate and private classes/properties/methods are stripped from the symbols list when accessed externally. This lack of headers combined with automatic symbol stripping via in-code access controls means that we don't need to use a regex to determine what we want and don't want to expose - the use case for the public_header_files configuration option doesn't exist for Swift projects.

It took a bit of digging and gaining a deeper understanding of some of the differences between Swift and Objective-C, but I eventually managed to get pod linting passing and the latest version of ConvenientFileManager published 😅.

For reference this is the current podspec file for ConvenientFileManager:

Pod::Spec.new do |s|

  s.name         = "ConvenientFileManager"
  s.version      = "2.0.0"
  s.summary      = "A suite of categories to ease using NSFileManager for common tasks."

  s.homepage     = "http://www.williamboles.me"
  s.license      = { :type => 'MIT', 
                       :file => 'LICENSE.md' }
  s.author       = "William Boles"

  s.platform     = :ios, "9.0"

  s.source       = { :git => "https://github.com/wibosco/ConvenientFileManager.git", 
                       :branch => "master", 
                       :tag => s.version }

  s.source_files  = "ConvenientFileManager/**/*.swift"
    
  s.requires_arc = true
  
end

What do you think? Let me know by getting in touch on Twitter - @wibosco