Cross platform code sharing in Swift

I have an app created from Xcode 9 / New Project / Cross Platform Game App, targets: macOS and iOS.

I wrote the following simple function to load text from a Data Asset.

func dataAssetAsString(_ name: String) -> String? {
     if let asset = NSDataAsset(name: NSDataAsset.Name(name)) {
          return String(data: asset.data, encoding: .utf8)
     } return nil
}


I'm puzzled by the following things:

1. This function only works if I import either UIKit, or AppKit.

2. But somehow importing MetalKit instead of UIKIt or AppKit makes it work. This really puzzles me. This should have nothing to do with MetalKit.


Most answers I found for code sharing between iOS and macOS suggest some kind of conditional import at the beginning of the file / shimming.


I went through the slides of WWDC 2014 Session 233: Sharing code between iOS and OS X, and it explicitly says we should not do shimming in Swift.



On the other hand, I don't understand what we should do instead of shimming. The mentioned very complicated process of creating and compiling shared frameworks for a 5 line function cannot be the right direction.


1. In 2018 (Swift 4, Xcode 9, iOS 11, macOS 10.13), what would be the recommended way to add such a trivial cross platform code to an app?

2. What is the magic of MetalKit which makes it work without shimming? Does it shim internally?

Accepted Answer

>> What is the magic of MetalKit which makes it work


Most Apple-supplied frameworks are umbrella frameworks, which import multiple individual frameworks. MetalKit, AppKit and UIKit ar all umbrella frameworks, and presumably they all import something that contains the NSDataAsset declaration.


Instead of using a conditional import, I suggest you create 2 new separate .swift files for iOS and macOS, and set one to be part of your iOS target, the other of your macOS target. Put the platform-appropriate import at the start of each file.


You have a couple of choices about what to do next. You could put your global dataAssetAsString in both files. Or, slightly better, you can put a typealias in each file:


     typealias DataAsset = NSDataAsset


and use the DataAsset type name in the rest of your code. (This is basically what Sprite Kit does for types like SKColor.)


Typically you'll have several issues like this to resolve in a cross-platform project. For example, in a SpriteKit game I have typealiases for UIColor/NSColor, UIFont/NSFont and various gesture recognizers. I also have compatibility code for UIBezierPath/NSBezier path (where it's not just a naming issue — the Mac version uses degrees where the iOS version uses radians, plus other API differences).


Or, if NSDataAsset is your only issue, you could just cheat and import MetalKit.

Thanks a lot, this is a very clean solution! I created a TypeAliases_iOS.swift and TypeAliases_macOS.swift and I will keep these things there.

Cross platform code sharing in Swift
 
 
Q