Using System Headers in Swift

Note: I’m not entirely happy with the formatting of the code blocks below. You can read the original post on Ind.ie if you like. You cannot leave comments there though.

Step 1: Set up the framework

After I had setup my framework, I wanted to start using getifaddrs from ifaddrs.h. Normally, in C you would just type #include <ifaddrs.h>. In Swift, you could do the same in your bridging header when your target is an application, but not when it’s a framework. So when I tried to add this include to my umbrella header, I got the following error message:

Terminal output:/<path_to_project>/NetUtils/NetUtils/NetUtils.h:13:10: error: include of non-modular header inside framework module 'NetUtils' #include <ifaddrs.h>
^

Step 2: Using the framework as a CocoaPod

Both solutions have one problem, though: when you want to use the framework as a CocoaPod, they don’t work. The first doesn’t work because the required build settings are not used by CocoaPods, and the second because the required shell script build phase is not used by CocoaPods. The reason for this is that CocoaPods creates its own project and target for the pod, and it doesn’t know that it needs to apply those custom build settings.

Adding the Module Map Files

I want my framework to be usable on iOS, iPhone Simulator, and OS X. Since they all have a different path to the ifaddrs.h file, I created three module maps. This is the one for the iPhone Simulator, the other two are similar:

Module map:module ifaddrs [system] [extern_c] {
header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/ifaddrs.h"
export *
}

Configuring the CocoaPod

CocoaPods to the rescue! As I wrote before, CocoaPods doesn’t know that these special build settings have to be used. Fortunately, there is a way to tell it that it has to, by adding the xcconfig key to the pod spec. In my case, in the (JSON) pod spec, I added these lines:

Podspec fragment:"xcconfig": {
"SWIFT_INCLUDE_PATHS[sdk=iphoneos*]": "$(SRCROOT)/NetUtils/ifaddrs/iphoneos",
"SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]": "$(SRCROOT)/NetUtils/ifaddrs/iphonesimulator",
"SWIFT_INCLUDE_PATHS[sdk=macosx*]": "$(SRCROOT)/NetUtils/ifaddrs/macosx"
},
"preserve_paths": [ "ifaddrs/*" ],

Not Every Computer Is The Same

There is one more thing left to do. You might have noticed that the module.modulemap files contain the path of Xcode itself (/Applications/Xcode.app). For my computer that is (currently) fine, because that’s what I’m using. Someone else may use different Xcode versions side by side or use a completely different location for Xcode. In order to allow for that, I added a small shell script that replaces the default Xcode path with the configured path on your computer. This is done by calling xcode-select -p, so if you have changed it using xcode-select or even if you have changed it only by setting the DEVELOPER_DIR environment variable, it will use the correct path.

Bash:#!/bin/sh defaultXcodePath="/Applications/Xcode.app/Contents/Developer"
realXcodePath="`xcode-select -p`"
fatal() {
echo "[fatal] $1" 1>&2
exit 1
}
absPath() {
case "$1" in
/*)
printf "%s\n" "$1"
;;
*)
printf "%s\n" "$PWD/$1"
;;
esac;
}
scriptDir="`dirname $0`"
scriptName="`basename $0`"
absScriptDir="`cd $scriptDir; pwd`"
main() {
for f in `find ${absScriptDir} -name module.modulemap`; do
cat ${f} | sed "s,${defaultXcodePath},${realXcodePath},g" > ${f}.new || fatal "Failed to update modulemap ${f}"
mv ${f}.new ${f} || fatal "Failed to replace modulemap ${f}"
done
}
main $*
Podspec fragment:"prepare_command": "ifaddrs/injectXcodePath.sh"

Complete Pod Spec

That’s it, everything should work now. For your convenience, here is the complete pod spec of version 0.4 of NetUtils:

CocoaPods podspec:{
"name": "NetUtils",
"version": "0.4",
"summary": "Swift library that simplifies getting information about your network interfaces and their properties, both for iOS and OS X.",
"homepage": "https://github.com/svdo/swift-netutils",
"license": {
"type": "The Unlicense <http://unlicense.org>"
},
"source": {
"git": "https://github.com/svdo/swift-netutils.git",
"tag": "0.4"
},
"authors": "Stefan van den Oord",
"platforms": {
"ios": "8.0",
"osx": "10.9"
},
"source_files": "NetUtils/**/*.swift",
"requires_arc": true,
"xcconfig": {
"SWIFT_INCLUDE_PATHS[sdk=iphoneos*]": "$(SRCROOT)/NetUtils/ifaddrs/iphoneos",
"SWIFT_INCLUDE_PATHS[sdk=iphonesimulator*]": "$(SRCROOT)/NetUtils/ifaddrs/iphonesimulator",
"SWIFT_INCLUDE_PATHS[sdk=macosx*]": "$(SRCROOT)/NetUtils/ifaddrs/macosx"
},
"preserve_paths": [ "ifaddrs/*" ],
"prepare_command": "ifaddrs/injectXcodePath.sh"
}

Feedback

As always, feedback is much appreciated. Thanks!

I love creating beautiful software and have a passion for making music and listening to music.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store