Tuesday, December 8, 2015

Building and using a framework in Swift

In the past, I've looked into the theory and some of the complications of building libraries and frameworks on OS X, much of it written up on this blog. I got interested today yesterday in the problem of building and using a pure Swift framework. I tried some of what the top hits on Google said to do, but it wasn't very helpful (because it's all iOS, and because it didn't seem to work), however, eventually I noodled out something that I think does work. I have yet to do more than skim the official Apple documentation.

According to this

Most public frameworks should be installed at the local level in /Library/Frameworks

That's our goal here. Any app that launches and then needs to find a framework should find it by searching "the usual suspects". According to the docs, the best place is /Library/Frameworks, but here I will use ~/Library/Frameworks.

Such a framework would be a dynamic framework, not static, or baked into the app. If you were to write an installer for such an app, you should take care to install its frameworks in the right place. R is a great example of how to do it right.



So in Xcode:

OS X > New Project > Framework & Library > Cocoa Framework > Swift




I named it SpeakerFramework.framework. Add to the framework a new Swift file

speaker.swift:



Having the init and speak functions both public (as well as the class itself) is required for external visibility (ref).

Build the framework.

Under Products, find SpeakerFramework.framework.



Control-click and show in Finder



then drag it to the Desktop.

For what we will do in a minute, we need the Finder to show ~/Library in an Open File dialog. With your home folder selected in Finder, do CMD-J (or View > Show View Options) and select Show Library Folder. (If you are just on the Desktop, then Show View Options shows something different).



Now for the app. In Xcode:

New Project > OS X > Application > Cocoa App > Swift




I named it MyApp.

In AppDelegate.swift add

import SpeakerFramework


This import statement gives an error and the project will not build (No such module 'SpeakerFramework').



To fix this, open ~/Library/Frameworks and drag the framework we just copied to the Desktop into that folder.



Alternatively, just do this in Terminal:

cp -r SpeakerFramework.framework ~/Library/Frameworks


Now, back in Xcode, select the project in the Project Navigator. In the tab view, select General and scroll to the bottom where it says Linked Frameworks and Libraries.



Click the + symbol below that line (not shown in the screenshot, because I only took it after I added the framework to this project).



Click Add Other... Then navigate to the framework in ~/Library/Frameworks and select it.

The MyApp project shows the framework in the General tab



Go back to the AppDelegate. The warning should be gone.



MyApp will build now. So let's use it. Edit the AppDelegate to call the speak method:



In the Debug window, we can see the expected output.



It would be nice to make a bigger statement.

I'll just outline the steps briefly. Delete the default window in MainMenu.xib. Add a new Cocoa class, subclassing NSWindowController. Have Xcode make the xib file too. Drag a label onto that window. Make it really, really big.



Hook it up to the new class (MainWindowController) as an IBOutlet, with this code for the AppDelegate:



and for MainWindowController:



Pretty impressive:



If we select Products > MyApp.app, then show in the Finder, and drag it to the Desktop, and delete everything else, except the framework in ~/Library/Frameworks, it still works. If we then do

> mv ~/Library/Frameworks/SpeakerFramework.framework/ old
> ls ~/Library/Frameworks
>




So it looks like everything is working as expected.

I thought I would snoop on the Loader as I did long ago:

> export DYLD_PRINT_LIBRARIES=1
> open -a MyApp.app

But it gives nothing. I am not sure why, yet.