Linting your podspec after introducing Swift
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