Blitz to get SMSyncServer Ready for Open-Source
(Update; 1/20/16; SMSyncServer is now on Bitbucket!)
Wow. That was a significant push! Perhaps the Agile people call this a Sprint? Whichever, I’m now pretty close to making an initial open-source release of the SMSyncServer. What’s the big deal? Why not just push my code up to a Git server and make the link public? Well, I’m glad you asked! There have been several issues.
1. Cleanly separating between the iOS testing app and the iOS SMSyncServer client
As I’d been developing, the boundary between the iOS testing code and the iOS SMSyncServer client was pretty well established, but I wanted something more firm. In particular, I wanted the iOS SMSyncServer client to be contained in a custom Xcode Framework—to make it simpler for people download the client code, and install it into their own project.
Getting the SMSyncServer code into the form of a custom Framework posed several arcane technical issues. I’ll mention a few. One issue was a dependency on the Google Sign In Framework (https://developers.google.com/identity/sign-in/ios/). The initial version of SMSyncServer makes use of Google Drive, and uses the Google Sign In Framework to allow users to sign in so their Google Drive can be accessed. I’ve been using Cocoapods to link Google Sign In into my Xcode project, so my initial thought was to have the SMSyncServer Framework make use of Cocoapods to link Google Sign In. That didn’t work so well for me. I arm wrestled with Cocoapods for sometime, and in amongst what I tried, I tried making my own custom Cocoapod for the SMSyncServer Framework. In the end, Cocoapods won, I lost, and I gave up directly linking Google Sign In into the SMSyncServer Framework, for these reasons:
A) The developer using the SMSyncServer Framework does have to make their own Google developer account and use their own GoogleService-Info.plist file for Google access.
B) And they will almost certainly need to make their own Google account sign-in UI for Google—I have provided just a primitive example in SMGoogleSignInController.swift in iOSTests (i.e., it’s not very stylish!).
C) Making my own Cocoapod seemed too cumbersome for what I was trying to accomplish. And it seems to make development appreciably more complicated.
D) The SMSyncServer Framework just needs a minimal amount of information from Google Sign In (this amounts to an IdToken, and an AuthCode, just two strings). Why should I make my framework lots more complicated just for this small amount of information?
I don’t have a general picture yet of the credentials needed by cloud storage systems other than Google Drive, so my assumptions above may need to change. Such is development, however. I tend to like to start simple and generalize on the basis of actual data instead of trying to make a general structure that anticipates something I have no knowledge about.
So, in the end I have just made a simple interface between the client app and the SMSyncServer Framework for communicating credentials (see the enum SMCloudStorageUser). And I’ve pushed linkage to the Google Sign In Framework into the domain of the client app.
2. CoreData: Multiple Models & Managed Object Contexts
I’ve previously used iOS’s CoreData in several apps but this time I had a few new requirements. One new requirement was that I was making use of a collection of CoreData managed objects inside of the SMSyncServer Framework. To be more specific, the SMSyncServer iOS Framework needs to keep track of meta data on the collection of files synced with the Node.js server. This meta data includes things like UUID’s per file, and the version number of each file. Additionally, in my testing iOS app, I have another set of managed objects—that are logically separate (a different CoreData model) from the SMSyncServer iOS Framework managed objects. So, I have two CoreData models, with one of these in the main bundle (the testing app), and one of these in a custom Framework.
I had to rewrite my own CoreData class (see SMCoreLib CoreData.m/.h). Up until this point, my CoreData class had made only a weak effort to make use of multiple CoreData models and their associated managed object contexts. One tricky point in this was, at run-time, locating the CoreData model within the SMSyncServer Framework. Take a look at the method managedObjectModelForBundleResource in the SMCoreLib CoreData.m for how I did this.
3. Swift and Objective-C Bridging Loveliness
In general, I’m quite happy with Swift. What I’m less happy about is bridging between Swift and Objective-C. I have a reasonable amount of “legacy” Objective-C code that works just fine, and I have no plans to throw it away. Hence the need to bridge between the two languages. Apple provides some support, but it has its limits. And it gets pretty arcane. One issue that I had to wrap my mind around was directly related to Frameworks. In the wee hours one morning recently, I realized that my SMSyncServer Framework Umbrella header was publically exposing a great many Objective-C classes, in an uncontrolled manner, that ought to have been internal to the SMSyncServer Framework. Note that this is an Objective-C issue because Swift has scoping keywords (internal, private, public) that give you some control over such matters. In order to allow Swift within the Framework access to the Objective-C, those Objective-C classes seem to have to have their .h’s in the Umbrella header (explicit bridging headers are disallowed in these Frameworks). HOWEVER, putting these Objective-C headers in the Umbrella header has the side effect of exposing this intendedly internal Objective-C to the user of the Framework. Yikes! Ugggh! Namespace pollution at its finest! At first I just threw up my hands and didn’t want to worry about it.
Then I came across this statement from an Apple representative “Debugging a Swift target requires that frameworks be properly modular, meaning all of the publicly-imported headers are either accounted for in the framework’s umbrella header, are imported from another modular framework, or are listed explicitly in a custom module.modulemap file (advanced users only).” (https://forums.developer.apple.com/thread/23554). And, another developer offered related insight: “Why not just create [a] separate Framework that is included inside of your Swift Framework that includes any Objective-C or C code. With the dynamic frameworks you can embed other frameworks. Works like a Charm and no need to fool around with module mappings” (http://spin.atomicobject.com/2015/02/23/c-libraries-swift/).
“Eureka” I said! Instead of having all this uncontrolled exposure of Objective-C, I can put the Objective-C (and a little bit of needed Swift code) into (yet) another library. So, a developer making use of the SMSyncServer iOS Framework can opt to not import that other, internal library, and hence not be exposed to its internal namespace of (mostly) Objective-C code.
4. SMCoreLib: My own “Common” library code
So, the “final” state of things is that now I have two Frameworks that make up the SMSyncServer Famework – i.e., the SMSyncServer Framework, providing the public interface to developers making use of the SyncServer, and an internal Framework—which I call SMCoreLib. SMCoreLib can be used by developers, but they don’t have to import it—giving them more control.
5. Ensuring no file redundancy while allowing pushing all files to Git repo
This has been a technical issue I’ve had on my mind for a while. On my development computer, I have a set of Xcode projects that make use of a set of “Common” code files, all just dragged from a hierarchy of folders into those Xcode projects. So far, internally, I’ve not been making much use of Frameworks (that’s about to change though).
For purposes of pushing code to a network Git repository (and Bitbucket more specifically), to enable other developers to download and build SMSyncServer, I need more than Xcode links to files. I needed to push folders of actual “Common” code (my SMCoreLib now) to Git. How to accomplish this without pushing all of my Common code? (I don’t want to push all my Common code because there’s too much there, plus I’m proprietary about some of it).
One simple way to do this would be to just make physical copies of files. That as we all know and love makes maintenance a bit of a nightmare. You update one file with a bug fix and have to popagate the fix to multiple duplicate files. Not ideal. My solution was as follows. First, as mentioned above, I created the SMCoreLib, and into that folder structure, I physically moved parts of my “Common” code. Yes, that’s going to break some of my existing Xcode projects, but getting them building again should “just” be a matter of linking to SMCoreLib and removing some broken file references in Xcode. Second, from the folders containing the code to be pushed to Git for the SyncServer, I’ve hard linked to the SMCoreLib. See figure below.
My plan is to only hardlink from outside projects into Common. That is, Common will have the original files. I’m using hardlinks just because Git seems unwilling to follow symbolic links when it uploads code. (On a technical side note, this the first time I’ve seen hardlinks in the form of directories in a Unix system).
6. In Closing
All in all, getting this project ready for open-source release was a worthwhile technical accomplishment, but took far more effort than I thought it would.