Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Cross-Platform Swift

Cross-Platform Swift

A talk about writing Swift targeting multiple platforms.

9d2ea021919ff81e02d48530aae191bd?s=128

Boris Bügling

February 17, 2016
Tweet

Transcript

  1. CROSS-PLATFORM SWIFT MOBOS CONFERENCE, FEBRUARY 2016 BORIS BÜGLING - @NEONACHO

  2. COCOAPODS

  3. CONTENTFUL

  4. None
  5. AGENDA ▸ Which platforms can we target? ▸ How to

    share code between them ▸ Some practical examples
  6. WHICH PLATFORMS CAN WE TARGET?

  7. APPLE PLATFORMS ▸ OS X ▸ iOS ▸ watchOS ▸

    tvOS
  8. FRAMEWORKS SHARED BETWEEN APPLE PLATFORMS CFNetwork.framework CoreData.framework CoreFoundation.framework CoreGraphics.framework CoreLocation.framework

    CoreText.framework Foundation.framework ImageIO.framework Security.framework
  9. THERE'S MORE DEPENDING ON THE SUBSET WE TARGET Accelerate.framework AudioToolbox.framework

    AudioUnit.framework AVFoundation.framework AVKit.framework CloudKit.framework CoreBluetooth.framework
  10. THERE'S MORE DEPENDING ON THE SUBSET WE TARGET CoreImage.framework CoreMedia.framework

    CoreVideo.framework EventKit.framework GameController.framework GameKit.framework GLKit.framework MapKit.framework
  11. THERE'S MORE DEPENDING ON THE SUBSET WE TARGET MediaAccessibility.framework Metal.framework

    MobileCoreServices.framework SceneKit.framework SpriteKit.framework StoreKit.framework SystemConfiguration.framework
  12. UIKIT When only targeting iOS and tvOS

  13. NIBS ! If you don't feel like copy-pasting stuff between

    NIBs ! https://github.com/neonichu/bohne
  14. OPEN SOURCE SWIFT ▸ Linux

  15. OPEN SOURCE SWIFT ▸ FreeBSD (somewhat)

  16. OPEN SOURCE SWIFT ▸ Android (someday, maybe) https://github.com/SwiftAndroid/swift

  17. FRAMEWORKS SHARED BETWEEN all PLATFORMS Foundation.framework https://github.com/apple/swift-corelibs-foundation

  18. FOUNDATION IS INCOMPLETE AND SOMETIMES DIFFERENT FROM OS X #if

    os(Linux) let index = p.startIndex.distanceTo(p.startIndex.successor()) path = NSString(string: p).substringFromIndex(index) #else path = p.substringFromIndex(p.startIndex.successor()) #endif
  19. APPLE'S GOAL ▸ Be compatible with Swift 3.0 ▸ Scheduled

    to ship by the end of 2016
  20. EVEN SOME THINGS IN THE STANDARD LIBRARY MIGHT NOT BE

    AVAILABLE #if _runtime(_ObjC) // Excluded due to use of dynamic casting and Builtin.autorelease, neither // of which correctly work without the ObjC Runtime right now. // See rdar://problem/18801510 [...] public func getVaList(args: [CVarArgType]) -> CVaListPointer {
  21. EVEN LIBC IS PROBLEMATIC #if os(Linux) import Glibc #else import

    Darwin.C #endif
  22. let flags = GLOB_TILDE | GLOB_BRACE | GLOB_MARK if system_glob(cPattern,

    flags, nil, &gt) == 0 { #if os(Linux) let matchc = gt.gl_pathc #else let matchc = gt.gl_matchc #endif
  23. AND OTHER RANDOM FUN ./.build/debug/spectre-build /usr/bin/ld: .build/debug/Clock.a(ISO8601Parser.swift.o): undefined reference to

    symbol '_swift_FORCE_LOAD_$_swiftGlibc' /home/travis/.swiftenv/versions/swift-2.2-SNAPSHOT-2015-12-22-a/ usr/lib/swift/linux/libswiftGlibc.so: error adding symbols: DSO missing from command line clang: error: linker command failed with exit code 1 (use -v to see invocation)
  24. => IT'S COMPLICATED

  25. HOW TO SHARE CODE BETWEEN THEM

  26. ▸ Shared files ▸ Shared frameworks ▸ Shared packages

  27. SHARED FILES

  28. BUILD CONFIGURATIONS ▸ os(): OSX, iOS, watchOS, tvOS, Linux ▸

    arch(): x86_64, arm, arm64, i386
  29. #if os(OSX) print("Running on OS X") #elseif os(watchOS) print("Running on

    watchOS") #else print("Running on any platform but OS X and watchOS") #end
  30. SHARED FRAMEWORKS

  31. [XCODE 7 BETA] LINKING DUAL (IPHONEOS AND WATCHOS) FRAMEWORKS WITH

    SAME PRODUCT NAME CAUSES ARCHIVE TO FAIL http://openradar.appspot.com/22392501
  32. SHARED PACKAGES ▸ CocoaPods ▸ Carthage ▸ Swift Package Manager

  33. COCOAPODS "platforms" : { "osx" : "10.10", "ios": "8.0", "watchos":

    "2.0", "tvos": "9.0" }
  34. IF A POD DOES NOT SUPPORT A CERTAIN PLATFORM ▸

    Fork and add it ▸ Submit a PR ▸ For the adventurous ! https://github.com/orta/cocoapods-expert-difficulty
  35. COCOAPODS AVOIDS 22392501 ▸ Each duplicated target gets scoped ▸

    <.../Build/Debug-iphoneos/Pods-UserTarget/ *.framework>
  36. CONCHE $ conche build Downloading Dependencies -> PathKit 0.5.0 ->

    Commander 0.5.0 Building Dependencies -> PathKit -> Commander Building Conche Building Entry Points -> conche -> .conche/bin/conche
  37. CARTHAGE ▸ Essentially a nicer way to do shared frameworks

  38. SWIFT PACKAGE MANAGER

  39. WHAT IS SWIFTPM?

  40. import PackageDescription let package = Package( name: "Hello", dependencies: [

    .Package(url: "ssh://git@example.com/Greeter.git", versions: Version(1,0,0)..<Version(2,0,0)), ] )
  41. $ swift build Compiling Swift Module 'Clock' (2 sources) Linking

    Library: .build/debug/Clock.a
  42. WHAT DOES IT DO? ▸ Compiles and links Swift packages

    ▸ Resolves, fetches and builds their dependencies
  43. CURRENT STATE ▸ Currently builds static libraries or binaries ▸

    Supported platforms are OS X and Ubuntu Linux ▸ Only builds Swift code, no C/C++/Objective-C/...
  44. $ swift build --help OVERVIEW: Build sources into binary products

    USAGE: swift build [options] MODES: --configuration <value> Build with configuration (debug|release) [-c] --clean Delete all build intermediaries and products [-k] OPTIONS: --chdir <value> Change working directory before any other operation [-C] -v Increase verbosity of informational output
  45. None
  46. MAKING OUR OWN PACKAGE

  47. ! A small library for parsing and writing ISO8601 date

    strings.
  48. Sources/ !"" Clock #"" ISO8601Parser.swift !"" ISO8601Writer.swift 1 directory, 2

    files
  49. $ touch Package.swift $ swift build

  50. TESTS?

  51. SPECTRE describe("a person") { let person = Person(name: "Kyle") $0.it("has

    a name") { try expect(person.name) == "Kyle" } $0.it("returns the name as description") { try expect(person.description) == "Kyle" } }
  52. SPECTRE-BUILD $ swift build $ .build/debug/spectre-build -> a person ->

    has a name -> returns the name as description 2 passes and 0 failures
  53. import PackageDescription let package = Package( name: "Clock", testDependencies: [

    .Package(url: "https://github.com/neonichu/spectre-build.git", majorVersion: 0), ] )
  54. BUILD_DIR=./.build/debug .PHONY: clean lib test test: lib $(BUILD_DIR)/spectre-build clean: swift

    build --clean lib: swift build
  55. $ make test swift build ./.build/debug/spectre-build -> Converting dates to

    strings -> can convert NSDate to an ISO8601 GMT string -> Parsing of localtime dates -> can parse dates -> can parse dates with negative timezone offsets -> can parse timezone offsets without colons -> Parsing of UTC dates -> can parse dates -> can parse epoch -> can parse dates without seconds -> is resilient against Y2K bugs 8 passes and 0 failures
  56. SWIFT VERSIONS $ cat .swift-version swift-2.2-SNAPSHOT-2015-12-22-a ▸ Is read by

    either chswift or swiftenv
  57. TRAVIS CI os: - linux - osx language: generic sudo:

    required dist: trusty osx_image: xcode7.2 install: - curl -sL https://gist.github.com/kylef/ 5c0475ff02b7c7671d2a/raw/ 621ef9b29bbb852fdfd2e10ed147b321d792c1e4/swiftenv-install.sh | bash script: - . ~/.swiftenv/init
  58. GIT TAGGING ▸ Package.swift only supports tagged dependencies ▸ Don't

    forget to push your tags to GitHub
  59. COCOAPODS ▸ chocolat-cli converts Package.swift to a JSON Podspec

  60. public func parse_package(packagePath: String) throws -> PackageDescription.Package { // FIXME:

    We depend on `chswift` installation and use here let toolchainPath = PathKit.Path(POSIX.getenv("CHSWIFT_TOOLCHAIN") ?? "") libc.setenv("SPM_INSTALL_PATH", toolchainPath.parent().description, 1) print_if("Using libPath \(Resources.runtimeLibPath)", false) let package = (try Manifest(path: packagePath)).package print_if("Converting package \(package.name) at \(packagePath)", false) return package }
  61. INTEGRATE SYSTEM LIBRARIES ▸ Empty Package.swift ▸ module.modulemap: module curl

    [system] { header "/usr/include/curl/curl.h" link "curl" export * }
  62. let package = Package( name: "example", dependencies: [ .Package(url: "https://github.com/neonichu/curl",

    majorVersion: 1) ] )
  63. HOW DOES IT WORK?

  64. MODULES INSIDE SWIFTPACKAGEMANAGER ▸ OS abstractions: libc, POSIX, sys ▸

    Package: PackageDescription ▸ Manifest: dep ▸ Downloading code: swift-get ▸ Building code: swift-build
  65. DEPENDENCIES ▸ Can be local or remote Git repositories ▸

    Need to be tagged ▸ Will be fetched to ./Packages/ MyPackage-0.0.1
  66. ROUGH BUILD PROCESS ▸ PackageDescription generates TOML ▸ dep parses

    the TOML and can generate YAML ▸ Dependencies are fetched by swift-get ▸ YAML is used as input to llbuild ▸ swift-build calls out to llbuild
  67. LLBUILD

  68. $ cat .build/debug/Clock.o/llbuild.yaml client: name: swift-build tools: {} targets: "":

    [<Clock>] Clock: [<Clock>]
  69. commands: <Clock-swiftc>: tool: swift-compiler executable: "/usr/bin/swiftc" inputs: ["ISO8601Parser.swift","ISO8601Writer.swift"] outputs: ["<Clock-swiftc>","Clock.swiftmodule",

    "ISO8601Parser.swift.o","ISO8601Writer.swift.o"] module-name: "Clock" module-output-path: "Clock.swiftmodule" is-library: true sources: ["ISO8601Parser.swift","ISO8601Writer.swift"] objects: ["ISO8601Parser.swift.o","ISO8601Writer.swift.o"] import-paths: ["/Users/boris/Projects/Clock/.build/debug"] temps-path: "/Users/boris/Projects/Clock/.build/debug/Clock.o/Clock" other-args: ["-j8","-Onone","-g","-target","x86_64-apple-macosx10.10", "-enable-testing","-sdk", "/.../Developer/SDKs/MacOSX10.11.sdk","-I","/usr/local/include"]
  70. <Clock>: tool: shell inputs: ["<Clock-swiftc>","ISO8601Parser.swift.o","ISO8601Writer.swift.o"] outputs: ["<Clock>","Clock.a"] args: ["/bin/sh","-c","rm -f

    'Clock.a'; ar cr 'Clock.a' 'ISO8601Parser.swift.o' 'ISO8601Writer.swift.o'"] description: "Linking Library: .build/debug/Clock.a"
  71. ADDITIONAL PACKAGE.SWIFT SYNTAX

  72. TARGETS import PackageDescription let package = Package( name: "Example", targets:

    [ Target( name: "top", dependencies: [.Target(name: "bottom")]), Target( name: "bottom") ] )
  73. EXCLUSION let package = Package( name: "Example", exclude: ["tools", "docs",

    "Sources/libA/images"] )
  74. TEST DEPENDENCIES import PackageDescription let package = Package( name: "Hello",

    testDependencies: [ .Package(url: "ssh://git@example.com/Tester.git", versions: Version(1,0,0)..<Version(2,0,0)), ] )
  75. CUSTOMIZING BUILDS import PackageDescription var package = Package() #if os(Linux)

    let target = Target(name: "LinuxSources/foo") package.targets.append(target) #endif
  76. You should think of it as an alpha code base

    that hasn't had a release yet. Yes, it is useful for doing some things [...] — Daniel Dunbar
  77. SOME PRACTICAL EXAMPLES

  78. SPRITEKIT EXAMPLE Can run on OS X, iOS and tvOS

  79. CLOCK ~/Clock# make swift build ./.build/debug/spectre-build -> Parsing of localtime

    dates fatal error: init(forSecondsFromGMT:) is not yet implemented: file Foundation/NSTimeZone.swift, line 69 Illegal instruction InvocationError() Makefile:6: recipe for target 'test' failed make: *** [test] Error 1
  80. ELECTRONIC MOJI $ ./.build/debug/electronic-moji car ! " # $ %

    & ' ( ) * + , - .
  81. CONTENTFUL SDK

  82. CONCLUSION ▸ It's complicated :) ▸ Still a lot of

    code can be shared ▸ Swift 3.0 will help a lot
  83. REFERENCES ▸ https://swift.org ▸ https://github.com/apple/swift-package-manager ▸ https://github.com/apple/llbuild ▸ https://github.com/neonichu/chocolat ▸

    https://github.com/neonichu/chswift ▸ https://github.com/neonichu/freedom
  84. THANK YOU!

  85. @NeoNacho boris@contentful.com http://buegling.com/talks