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

A Swift Journey

A Swift Journey

The story (so far) of how the mobile team at 3Squared moved to 100% Swift development for all new iOS projects. Topics covered include:

• What different approaches were taken to learning Swift.

• How the right projects were identified for 100% Swift development from the start.

• A tour of the reusable code components developed by the team which are now used in all new apps.

• The plan for the future - how to get off Objective-C for good!

Luke Stringer

February 06, 2017
Tweet

More Decks by Luke Stringer

Other Decks in Programming

Transcript

  1. A Swift Journey
    Luke Stringer

    View Slide

  2. Hi, I'm Luke !
    @lukestringer90

    View Slide

  3. I'm the Head of
    Mobile 3Squared
    • Software for Rail and Construction
    industries
    • Based in Sheffield
    • Build Web apps
    • C# .NET
    • Build native iOS and Android apps
    • Swift, Objective-C & Java
    • I lead team of 7 mobile developers

    View Slide

  4. Our "Swift" Journey
    for 2 reasons...

    View Slide

  5. 1. We moved from
    Objective-C to Swift

    View Slide

  6. 2. We did it Quickly

    View Slide

  7. Our 2016 Goals
    1. Train the mobile team in Swift
    2. Move from Obj-C to Swift for new projects
    3. Develop reusable Swift code
    4. Modernise & standardise our tools and practises

    View Slide

  8. 1. Training

    View Slide

  9. Looking for Training
    We needed training materials that could be used for:
    • New / Inexperiences developers - placement students
    • Experienced non-iOS developers - Android developers
    • Experienced iOS developers

    View Slide

  10. Styles of Learning
    • Follow guided tutorials
    • Build a simple project in Swift
    • Request data from GitHub API
    • Parse & persist
    • Display on screen
    • Build a wrapper and other reusable code

    View Slide

  11. Just Do It!
    Learn whilst Building
    Something Real

    View Slide

  12. Learning & Discussion
    as a Team was Crucial

    View Slide

  13. 2. New Projects in
    Swift

    View Slide

  14. First Project
    100% Swift from the Start!

    View Slide

  15. First Project
    • Chose a small / medium complexity project
    • Evaluated Swift's readiness for production
    • Discussed code design and style as a team
    • Identified gaps in our knowledge & our tools

    View Slide

  16. Results
    • Project delivered on time
    • No major problems - Swift is production ready
    • Beginnings of an agreed code style
    • Built helpers for using Core Data & Storyboards

    View Slide

  17. The Next Projects
    Following Code Style, using
    Helpers

    View Slide

  18. Next Projets
    • 2 new projects 100% Swift from start
    • More ambitions in scope and longer in duration
    • More of the team involved
    • Followed code style
    • Used helper code from first project

    View Slide

  19. Trialling Reusable Code
    • At this stage reusable code was unversioned
    • Just dropped straight into an Xcode project
    • Provided freedom to adapt and change
    • However this led to code divergence
    • At a suitable point team decided to standardise the
    approaches

    View Slide

  20. Results
    • Both projects delivered on time
    • Code style adapted
    • Filled more gaps - Networking, Threading, Error Handling
    • Helper code adapted and now versioned using cocoapods

    View Slide

  21. 3. Reusable Code

    View Slide

  22. The "THR"
    Micro Frameworks
    (Inspired by other works)

    View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. View Slide

  27. View Slide

  28. View Slide

  29. THRResult
    • Models success and failure
    • Extends the familiar Result type to work with do-catch error
    handling
    • Powerful, flexible and expressive
    • Credit to
    • https://github.com/antitypical/Result
    • Olivier Halligon "Asynchronous error handling"

    View Slide

  30. enum Result {
    case success(T)
    case failure(Error)
    }

    View Slide

  31. enum Result {
    case success(T)
    case failure(Error)
    }
    // Using a switch:
    switch result {
    case .success(let value):
    // process success value
    case .failure(let error):
    // handle failure error
    }

    View Slide

  32. enum Result {
    case success(T)
    case failure(Error)
    init(_ throwingExpr: () throws -> T) {
    do {
    let value = try throwingExpr()
    self = .success(value)
    } catch {
    self = .failure(error)
    }
    }
    func resolve() throws -> T {
    switch self {
    case .success(let value): return value
    case .failure(let error): throw error
    }
    }
    }

    View Slide

  33. let successResult = Result { return value }
    let failureResult = Result { throw ImportError.invalidData }

    View Slide

  34. let successResult = Result { return value }
    let failureResult = Result { throw ImportError.invalidData }
    // Using do-catch:
    do {
    let value = try result.resolve()
    // Process value
    }
    catch {
    // Handle error
    }

    View Slide

  35. let successResult = Result { return value }
    let failureResult = Result { throw ImportError.invalidData }
    // Using if let
    if let value = try? result.resolve() {
    // Process value
    }
    // or
    let value = try! result.resolve()

    View Slide

  36. THROperations
    • Preferred method for threading
    • Helpful subclasses of Operation
    • Expressive syntax for combining operations
    • Uses THRResult to report success and failure
    • Credit to
    • https://github.com/ProcedureKit/ProcedureKit

    View Slide

  37. protocol ProducesResult: class {
    associatedtype Output
    var output: Result { get set }
    }

    View Slide

  38. extension ProducesResult where Self: Operation {
    func addResultBlock(block: @escaping (Result) -> Void) {
    if let existing = completionBlock {
    completionBlock = {
    existing()
    block(self.output)
    }
    }
    else {
    completionBlock = {
    block(self.output)
    }
    }
    }
    }

    View Slide

  39. Example
    Let's build an operation to extract a top level domain (TLD)
    from a URL string
    typealias TopLevelDomain = String
    typealias URLString = String

    View Slide

  40. class ExtractTopLevelDomainOperation: Operation, ProducesResult {
    // ProducesResult
    var output: Result = Result {
    throw ResultError.noResult
    }
    // TopLevelDomainOperation
    let urlString: URLString
    init(urlString: URLString) {
    self.urlString = urlString
    }
    override func main() {
    super.main()
    extractTopLevelDomain(from: urlString)
    }
    // ...
    }

    View Slide

  41. // ...continued
    func extractTopLevelDomain(from urlString: URLString) {
    guard urlString.characters.count > 0 else {
    output = Result { throw TopLevelDomainError.invalidURL }
    return
    }
    let parts = urlString.components(separatedBy: ".")
    guard parts.count > 1, let topLevelDomain = parts.last else {
    output = Result { throw TopLevelDomainError.none }
    return
    }
    output = Result { return topLevelDomain }
    }

    View Slide

  42. let operation = ExtractTopLevelDomainOperation(urlString: "apple.com")
    operation.addResultBlock { result in
    switch result {
    case .success(let value):
    // process TLD
    case .failure(let error):
    switch error {
    case TopLevelDomainOperation.none:
    // handle no TLD
    case TopLevelDomainOperation.invalidURL:
    // handle invalid URL
    }
    }
    }
    operation.enqueue()

    View Slide

  43. let operation = ExtractTopLevelDomainOperation(urlString: "apple.com")
    operation.addResultBlock { result in
    do {
    let topLevelDomain = try result.resolve()
    // process TLD
    }
    catch {
    switch error {
    case TopLevelDomainOperation.none:
    // handle no TLD
    case TopLevelDomainOperation.invalidURL:
    // handle invalid URL
    }
    }
    }
    operation.enqueue()

    View Slide

  44. let operation = ExtractTopLevelDomainOperation(urlString: "apple.com")
    operation.addResultBlock { result in
    if let topLevelDomain = try? result.resolve() {
    // process TLD
    }
    }
    operation.enqueue()

    View Slide

  45. Combining Operations
    • Often the output from one operation is the input to another
    • We want to pass the Result onwards
    • Combining many simple operations can produce complex
    logic

    View Slide

  46. protocol ProducesResult: class {
    associatedtype Output
    var output: Result { get set }
    }
    protocol ConsumesResult: class {
    associatedtype Input
    var input: Result { get set }
    }

    View Slide

  47. class ExtractTopLevelDomainOperation: BaseOperation,
    ProducesResult,
    ConsumesResult {
    // ProducesResult
    var output: Result = Result {
    throw ResultError.noResult
    }
    // ConsumesResult
    var input: Result = Result {
    throw ResultError.noResult
    }
    // ...
    }

    View Slide

  48. // ...continued
    override func main() {
    super.main()
    do {
    let urlString = try input.resolve()
    extractTopLevelDomain(from: urlString)
    }
    catch {
    output = Result { throw error }
    }
    }

    View Slide

  49. // Output: URLString
    let getURLString = RequestURLStringOperation()
    // Input: URLString, Output: TopLevelDomain
    let extractTLD = ExtractTopLevelDomainOperation()
    // Input: TopLevelDomain, Output: Bool
    let validate = ValidateTopLevelDomainOperation()
    validate.addResultBlock { result in
    if let isValid = try? result.resolve(), isValid == true {
    // TLD is valid
    }
    }
    getURLString
    .passesResult(to: extractTLD)
    .passesResult(to: validate)
    .enqueue()

    View Slide

  50. View Slide

  51. 4. Modernise &
    Standardise

    View Slide

  52. Modernise & Standardise
    • Handle errors and unhappy paths (THRResult)
    • Perform work asynchronously (THROperations)
    • Build complex logic from smaller units (THROperations)
    • Request data over a network, then parse into core data
    (THRNetwork & THRCoreData)
    • Use storyboard segues with type safety (THRUtilities)
    • Parse dates to and from a web API (THRDate)

    View Slide

  53. Did We Meet Our
    Goals?

    View Slide

  54. Our 2016 Goals
    1. Train the mobile team in Swift
    2. Move from Obj-C to Swift for new projects
    3. Develop reusable Swift code
    4. Modernise & standardise our tools and practises

    View Slide

  55. Our 2016 Goals
    1. Train the mobile team in Swift ✅
    2. Move from Obj-C to Swift for new projects
    3. Develop reusable Swift code
    4. Modernise & standardise our tools and practises

    View Slide

  56. Our 2016 Goals
    1. Train the mobile team in Swift ✅
    2. Move from Obj-C to Swift for new projects ✅
    3. Develop reusable Swift code
    4. Modernise & standardise our tools and practises

    View Slide

  57. Our 2016 Goals
    1. Train the mobile team in Swift ✅
    2. Move from Obj-C to Swift for new projects ✅
    3. Develop reusable Swift code ✅
    4. Modernise & standardise our tools and practises

    View Slide

  58. Our 2016 Goals
    1. Train the mobile team in Swift ✅
    2. Move from Obj-C to Swift for new projects ✅
    3. Develop reusable Swift code ✅
    4. Modernise & standardise our tools and practises ✅

    View Slide

  59. Recommendations

    View Slide

  60. 1. Learn as a Team

    View Slide

  61. 2. Gain Support from
    Business
    Management

    View Slide

  62. 3. Make Space for
    Experimentation

    View Slide

  63. 4. Expect to make
    Mistakes

    View Slide

  64. Our Plan for The
    Future

    View Slide

  65. How do we stop
    writing Objective-C in
    existing apps?

    View Slide

  66. Every line of
    Objective-C written
    now is Technical Debt

    View Slide

  67. Swift alongside Objective-C
    • Understand the limitations of Obj-C/Swift interoperability
    • No structs
    • No associated values with enums
    • No protocol extensions
    • Identify new features that can be written in Swift without too
    much difficulty

    View Slide

  68. Swift alongside Objective-C
    • Replace existing standalone Obj-C classes, methods etc
    with Swift implementation
    • NSPredicate generation
    • Simple / isolated UIViewControllers
    • Replace existing Obj-C structures with THR frameworks
    • e.g. rewrite data synchronisation subsystem

    View Slide

  69. A Swift Journey
    Luke Stringer

    View Slide

  70. Any Questions? !

    View Slide

  71. References
    • http://alisoftware.github.io/swift/async/error/2016/02/06/
    async-errors/
    • https://github.com/antitypical/Result
    • https://github.com/ProcedureKit/ProcedureKit

    View Slide