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

Switch to Swift presented at mix-it2015

Switch to Swift presented at mix-it2015

20a907ee0ab0e4756e19727209d0ac64?s=128

Corinne Krych

April 17, 2015
Tweet

Transcript

  1. Switch to Swift?

  2. tweet: @corinnekrych code: github corinnekrych blog: corinnekrych.org chat: irc aerogear

    Corinne
  3. Switch to Swift? That is the question

  4. Early adoption path Xcode crash

  5. Why Swift? Apple goals with Swift: • modern • easy-to-read

    code • safer code • compatible with existing Objective-C
  6. Swift super powers… … for Switching to Swift

  7. Syntax: melting pot Tip 1 "Of course, it also greatly

    benefited from the experiences hard- won by many other languages (…) Objective-C, Rust, Haskell, Ruby, Python, C#, CLU, and far too many others to list." Groovy Scala Rust C# ObjC Haskell
  8. var shoppingList = ["catfish", "water"] shoppingList[1] = "bottle of water"

    var occupations = [ "Malcolm": "Captain", "Kaylee": "Mechanic", ] occupations["Jayne"] = "Public Relations" var emptyMap = [:] var emptyList = [] def shoppingList = ["catfish", "water"] shoppingList[1] = "bottle of water" def occupations = [ "Malcolm": "Captain", "Kaylee": "Mechanic", ] occupations["Jayne"] = "Public Relations" def emptyMap = [:] def emptyList = [] Resemblance to…
  9. var shoppingList = ["catfish", "water"] shoppingList[1] = "bottle of water"

    var occupations = [ "Malcolm": "Captain", "Kaylee": "Mechanic", ] occupations["Jayne"] = "Public Relations" var emptyMap = [:] var emptyList = [] def shoppingList = ["catfish", "water"] shoppingList[1] = "bottle of water" def occupations = [ "Malcolm": "Captain", "Kaylee": "Mechanic", ] occupations["Jayne"] = "Public Relations" def emptyMap = [:] def emptyList = [] Resemblance to…
  10. Resemblance to… func f<T>(x: T) -> T {...} func g(x:

    A) {} func h<T: A>(x: T) -> T {...} func k(x:Int = 0) {} def f[T](x: T): T = ... def g(x: A) {} def h[T <: A](x: T): T ... def k(x: Int =0)
  11. Resemblance to… func f<T>(x: T) -> T {...} func g(x:

    A) {} func h<T: A>(x: T) -> T {...} func k(x:Int = 0) {} def f[T](x: T): T = ... def g(x: A) {} def h[T <: A](x: T): T ... def k(x: Int =0)
  12. Resemblance to… var a = "some text" var b =

    "some text" if a == b { println("The strings are equal") } if a.hasPrefix("some") { println("Starts with some") } if a.hasSuffix("some") { println("Endss with some") } a = "some text" b = "some text" if a == b: print("The strings are equal") if a.startswith("some"): print("Starts with some") if a.endswith("some"): print("Ends with some")
  13. Resemblance to… var a = "some text" var b =

    "some text" if a == b { println("The strings are equal") } if a.hasPrefix("some") { println("Starts with some") } if a.hasSuffix("some") { println("Endss with some") } a = "some text" b = "some text" if a == b: print("The strings are equal") if a.startswith("some"): print("Starts with some") if a.endswith("some"): print("Ends with some")
  14. Resemblance to… var dict = Dictionary<String, String>() var dict2 =

    ["TYO": "Tokyo", "DUB": “Dublin"] for (key, value) in dict { … … } var dict = new Dictionary<string, string>(); var dict2 = new Dictionary<string, string> { { "TYO", "Tokyo" }, { "DUB", "Dublin" } }; foreach(var item in dict) { var key = item.Key; var value = item.Value; }
  15. Resemblance to… var dict = Dictionary<String, String>() var dict2 =

    ["TYO": "Tokyo", "DUB": “Dublin"] for (key, value) in dict { … … } var dict = new Dictionary<string, string>(); var dict2 = new Dictionary<string, string> { { "TYO", "Tokyo" }, { "DUB", "Dublin" } }; foreach(var item in dict) { var key = item.Key; var value = item.Value; }
  16. Resemblance to… let success = { (res: A) -> ()

    in println("succeeded") } let failure = { (res: A) -> () in println("FH init failed") } FH(success: success, failure: failure) void (^success)(A *) = ^(A * res) { NSLog(@"succeeded"); }; void (^failure)(id) = ^(A * res) { NSLog(@"FH init failed"); }; [FH initWithSuccess:success AndFailure:failure];
  17. Resemblance to… let success = { (res: A) -> ()

    in println("succeeded") } let failure = { (res: A) -> () in println("FH init failed") } FH(success: success, failure: failure) void (^success)(A *) = ^(A * res) { NSLog(@"succeeded"); }; void (^failure)(id) = ^(A * res) { NSLog(@"FH init failed"); }; [FH initWithSuccess:success AndFailure:failure];
  18. Resemblance to… let success = { (res: A) -> ()

    in println("succeeded") } let failure = { (res: A) -> () in println("FH init failed") } FH(success: success, failure: failure) void (^success)(A *) = ^(A * res) { NSLog(@"succeeded"); }; void (^failure)(id) = ^(A * res) { NSLog(@"FH init failed"); }; [FH initWithSuccess:success AndFailure:failure]; http://goshdarnblocksyntax.com/
  19. Interactive Playgrounds Tip 2 // Playground - noun: // a

    place where people can play
  20. swift Xcode 6.3-beta Xcode 6.0 Inside Playgrounds

  21. swift Xcode 6.3-beta Xcode 6.0 Inside Playgrounds

  22. swift Xcode 6.3-beta Inside Playgrounds

  23. Playground time

  24. To be or not to be… Tip 3

  25. Properties Computed properties do not store a value. Instead, they

    provide a getter and an optional setter to retrieve and set other properties and values indirectly. Tip 4
  26. /** An OAuth2Session implementation to store OAuth2 metadata using Keychain.

    */ public class TrustedPersistantOAuth2Session: OAuth2Session { private let keychain: KeychainWrap /** The access token. The information is read securely from Keychain. */ public var accessToken: String? { get { return self.keychain.read(self.accountId, tokenType: .AccessToken) } set(value) { if let unwrappedValue = value { let result = self.keychain.save(self.accountId, tokenType: .AccessToken, value: unwrappedValue) } } } Computed properties aerogear-ios-oauth2
  27. /** An OAuth2Session implementation to store OAuth2 metadata using Keychain.

    */ public class TrustedPersistantOAuth2Session: OAuth2Session { private let keychain: KeychainWrap /** The access token. The information is read securely from Keychain. */ public var accessToken: String? { get { return self.keychain.read(self.accountId, tokenType: .AccessToken) } set(value) { if let unwrappedValue = value { let result = self.keychain.save(self.accountId, tokenType: .AccessToken, value: unwrappedValue) } } } Computed properties aerogear-ios-oauth2
  28. /** An OAuth2Session implementation to store OAuth2 metadata using Keychain.

    */ public class TrustedPersistantOAuth2Session: OAuth2Session { private let keychain: KeychainWrap /** The access token. The information is read securely from Keychain. */ public var accessToken: String? { get { return self.keychain.read(self.accountId, tokenType: .AccessToken) } set(value) { if let unwrappedValue = value { let result = self.keychain.save(self.accountId, tokenType: .AccessToken, value: unwrappedValue) } } } Computed properties aerogear-ios-oauth2
  29. /** An OAuth2Session implementation to store OAuth2 metadata using Keychain.

    */ public class TrustedPersistantOAuth2Session: OAuth2Session { private let keychain: KeychainWrap /** The access token. The information is read securely from Keychain. */ public var accessToken: String? { get { return self.keychain.read(self.accountId, tokenType: .AccessToken) } set(value) { if let unwrappedValue = value { let result = self.keychain.save(self.accountId, tokenType: .AccessToken, value: unwrappedValue) } } } Computed properties aerogear-ios-oauth2
  30. Enum on steroid Tip 5

  31. var jsonString = "[{\"id\":1, \"name\": \"Eliott\"}," + "{\"id\":2, \"name\": \"Emilie\"}]"

    var data = jsonString.dataUsingEncoding(NSUTF8StringEncoding) let jsonObject: AnyObject! = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: nil) // Handling JSON in Swift with optional and type casting if let personsArray = jsonObject as? NSArray { if let firstPerson = personsArray[0] as? NSDictionary { if let name = firstPerson["name"] as? NSString { println("First person name is \(name)") } } } Problem to solve: JSON SwitftyJSON
  32. var jsonString = "[{\"id\":1, \"name\": \"Eliott\"}," + "{\"id\":2, \"name\": \"Emilie\"}]"

    var data = jsonString.dataUsingEncoding(NSUTF8StringEncoding) let jsonObject: AnyObject! = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: nil) // Handling JSON in Swift with optional and type casting if let personsArray = jsonObject as? NSArray { if let firstPerson = personsArray[0] as? NSDictionary { if let name = firstPerson["name"] as? NSString { println("First person name is \(name)") } } } Problem to solve: JSON SwitftyJSON // Using SwiftyJSON let json = JSON(data: data!) if let userName = json[0]["name"].string { println("First person name is \(userName)”) }
  33. public enum JSON { //private type number case ScalarNumber(NSNumber) //private

    type string case ScalarString(NSString) //private type sequence case Sequence([JSON]) //private type mapping case Mapping([String: JSON]) //private type null case Null(NSError?) . . . } Enum associated values SwitftyJSON
  34. public enum JSON { //private type number case ScalarNumber(NSNumber) //private

    type string case ScalarString(NSString) //private type sequence case Sequence([JSON]) //private type mapping case Mapping([String: JSON]) //private type null case Null(NSError?) . . . } Enum associated values SwitftyJSON public init(object: AnyObject) { switch object { case let number as NSNumber: self = .ScalarNumber(number) case let string as NSString: self = .ScalarString(string) case let null as NSNull: self = .Null(nil) case let array as [AnyObject]: self = .Sequence(array) case let dictionary as [String: AnyObject]: self = .Mapping(dictionary) default: self = .Null(NSError(…)) } }
  35. SwitftyJSON Subscript // Using SwiftyJSON let json = JSON(data: data!)

    if let userName = json[0]["name"].string { println("First person name is \(userName)") }
  36. public subscript(index: Int) -> JSON { get { switch self

    { case .Sequence(let array): if array.count > index { return array[index] } else { return .Null(NSError(…)) } default: return .Null(NSError(…)) } } } SwitftyJSON Subscript // Using SwiftyJSON let json = JSON(data: data!) if let userName = json[0]["name"].string { println("First person name is \(userName)") }
  37. public var string: String? { get { switch self {

    case .ScalarString(let string): return string default: return nil } } } public subscript(index: Int) -> JSON { get { switch self { case .Sequence(let array): if array.count > index { return array[index] } else { return .Null(NSError(…)) } default: return .Null(NSError(…)) } } } SwitftyJSON Subscript // Using SwiftyJSON let json = JSON(data: data!) if let userName = json[0]["name"].string { println("First person name is \(userName)") }
  38. Surrounded by clones Almost all types in Swift are value

    types, including arrays, dictionary, numbers, booleans, tuples, and enums. Classes are the exception rather than the rule. Tip 6
  39. Functions in Swift are first-class values, i.e. functions may be

    passed as arguments to other functions, and functions may return new functions. Tip 7 Functions, methods closures
  40. Methods @implementation Greetings … - (NSString *)helloFirstname:(NSString *)firstname lastname:(NSNumber *)lastname

    { return [[NSString alloc] initWithFormat:@"hello, %@ %@", firstname, lastname]; } [self helloFirstName:@"Isabel" lastname:@"Dupont"];
  41. Methods @implementation Greetings … - (NSString *)helloFirstname:(NSString *)firstname lastname:(NSNumber *)lastname

    { return [[NSString alloc] initWithFormat:@"hello, %@ %@", firstname, lastname]; } [self helloFirstName:@"Isabel" lastname:@"Dupont"]; class Greetings { func helloFirstname(firstname: String, lastname: String) -> String { return "Hello \(firstname) \(lastname)" } } let morning = Greetings() morning.helloFirstname("Isabel", lastname: "Dupont")
  42. func hello(firstname: String, lastname: String) -> String { return "hello,

    \(firstname) \(lastname)" } hello("Isabel", "Dupont") Functions
  43. func hello(firstname: String, lastname: String) -> String { return "hello,

    \(firstname) \(lastname)" } hello("Isabel", "Dupont") Functions func hello(firstname: String, #lastname: String) -> String { return "Hello \(firstname) \(lastname)." } hello("Isabel", lastname: "Dupont")
  44. func hello(firstname: String, lastname: String) -> String { return "hello,

    \(firstname) \(lastname)" } hello("Isabel", "Dupont") Functions func hello(firstname: String, #lastname: String) -> String { return "Hello \(firstname) \(lastname)." } hello("Isabel", lastname: "Dupont") func hello(firstname: String, lastname lastname: String) -> String { return "Hello \(firstname) \(lastname)." } hello("Isabel", lastname: "Dupont")
  45. Define your own matrix Operator overloading! “With great power comes

    great responsibility,” Tip 8
  46. when to use them in real life? Person { "firstname":

    "john", "lastname": "doe", "address": { "street": "Buch Street", "poBox": 123, "city": "Glasgow", "country": "UK" } } aerogear-ios-jsonsz
  47. when to use them in real life? Person { "firstname":

    "john", "lastname": "doe", "address": { "street": "Buch Street", "poBox": 123, "city": "Glasgow", "country": "UK" } } class Person { var firstname: String? var lastname: String var address: Address? } class Address { var street: String var poBox: Int var city: String var country: String } aerogear-ios-jsonsz
  48. when to use them in real life? Person { "firstname":

    "john", "lastname": "doe", "address": { "street": "Buch Street", "poBox": 123, "city": "Glasgow", "country": "UK" } } class Person { var firstname: String? var lastname: String var address: Address? } class Address { var street: String var poBox: Int var city: String var country: String } class Person: JSONSerializable { var firstname: String? var lastname: String var address: Address? required init() {} class func map(source: JsonSZ, object: Person) { object.firstname <= source["firstname"] object.lastname <= source["lastname"] object.address <= source["address"] } } aerogear-ios-jsonsz
  49. when to use them in real life? Person { "firstname":

    "john", "lastname": "doe", "address": { "street": "Buch Street", "poBox": 123, "city": "Glasgow", "country": "UK" } } class Person { var firstname: String? var lastname: String var address: Address? } class Address { var street: String var poBox: Int var city: String var country: String } class Person: JSONSerializable { var firstname: String? var lastname: String var address: Address? required init() {} class func map(source: JsonSZ, object: Person) { object.firstname <= source["firstname"] object.lastname <= source["lastname"] object.address <= source["address"] } } aerogear-ios-jsonsz // serialize from json var serializer = JsonSZ() let developer:Person = serializer.fromJSON(developerJSON, to: Person.self) XCTAssertTrue(developer.firstname == "john") XCTAssertTrue(developer.lastname == "doe")
  50. it’s all about type… public func <=(inout left: String?, right:

    JsonSZ) { if let value = right.value { field = value as? String } } aerogear-ios-jsonsz
  51. it’s all about type… public func <=(inout left: String?, right:

    JsonSZ) { if let value = right.value { field = value as? String } } aerogear-ios-jsonsz public func <=(inout left: Int?, right: JsonSZ) { if let value = right.value { field = value as? Int } } public func <=(inout left: Bool?, right: JsonSZ) { if let value = right.value { field = value as? Bool } }
  52. Go Generics Generics allow a programmer to tell their functions

    and classes: “I am going to give you a type later and I want you to enforce that type everywhere I specify.” Tip 8
  53. it’s all about type… public func <=<T>(inout left: T?, right:

    JsonSZ) { if let value: AnyObject = right.value { switch T.self { case is String.Type, is Bool.Type, is Int.Type, is Double.Type, is Float.Type: field = value as? T . . . default: field = nil return } } } aerogear-ios-jsonsz
  54. it’s all about type… public func <=<T>(inout left: T?, right:

    JsonSZ) { if let value: AnyObject = right.value { switch T.self { case is String.Type, is Bool.Type, is Int.Type, is Double.Type, is Float.Type: field = value as? T . . . default: field = nil return } } } public func <=<T: JSONSerializable>(inout left: T?, right: JsonSZ) { if let value = right.value as? [String: AnyObject] { field = JsonSZ().fromJSON(value, to: T.self) } } aerogear-ios-jsonsz
  55. Extension Tip 9 Extensions add new functionality to an existing

    class, structure, or enumeration type.
  56. extension String { public func urlEncode() -> String { let

    encodedURL = CFURLCreateStringByAddingPercentEscapes(nil, self as NSString, nil, "!@#$%&*'();:=+,/?[]", CFStringBuiltInEncodings.UTF8.rawValue) return encodedURL as String } } aerogear-ios-http Extend behaviour
  57. class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate { // all methods }

    To be stylish ;)
  58. class MyViewController: UIViewController { // class stuff here } //

    MARK: - UITableViewDataSource extension MyViewController: UITableViewDataSource { // table view data source methods } // MARK: - UIScrollViewDelegate extension MyViewController: UIScrollViewDelegate { // scroll view delegate methods } class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate { // all methods } To be stylish ;)
  59. What about testing? Statically Stubbed or Dynamically Mocked Tip 10

  60. Mocking yourself func testRequestAccessWithAuthzCodeFlow() { let expectation = expectationWithDescription("AccessRequestWithAuthzFlow"); let

    googleConfig = . . . var mock = OAuth2ModulePartialMock(config: googleConfig, session: MockOAuth2Session()) mock.requestAccess { (response: AnyObject?, error:NSError?) -> Void in XCTAssertTrue("ACCESS_TOKEN" == response as String, “response with access token") expectation.fulfill() } waitForExpectationsWithTimeout(10, handler: nil) } aerogear-ios-oauth2
  61. Mocking yourself func testRequestAccessWithAuthzCodeFlow() { let expectation = expectationWithDescription("AccessRequestWithAuthzFlow"); let

    googleConfig = . . . var mock = OAuth2ModulePartialMock(config: googleConfig, session: MockOAuth2Session()) mock.requestAccess { (response: AnyObject?, error:NSError?) -> Void in XCTAssertTrue("ACCESS_TOKEN" == response as String, “response with access token") expectation.fulfill() } waitForExpectationsWithTimeout(10, handler: nil) } aerogear-ios-oauth2
  62. Mocking yourself func testRequestAccessWithAuthzCodeFlow() { let expectation = expectationWithDescription("AccessRequestWithAuthzFlow"); let

    googleConfig = . . . var mock = OAuth2ModulePartialMock(config: googleConfig, session: MockOAuth2Session()) mock.requestAccess { (response: AnyObject?, error:NSError?) -> Void in XCTAssertTrue("ACCESS_TOKEN" == response as String, “response with access token") expectation.fulfill() } waitForExpectationsWithTimeout(10, handler: nil) } class OAuth2ModulePartialMock: OAuth2Module { override func refreshAccessToken( completionHandler:(AnyObject?, NSError?) -> ()) { completionHandler("NEW_ACCESS_TOKEN", nil) } override func requestAuthorizationCode( completionHandler: (AnyObject?, NSError?) -> ()) { completionHandler("ACCESS_TOKEN", nil) } } aerogear-ios-oauth2
  63. Mocking yourself func testRequestAccessWithAuthzCodeFlow() { let expectation = expectationWithDescription("AccessRequestWithAuthzFlow"); let

    googleConfig = . . . var mock = OAuth2ModulePartialMock(config: googleConfig, session: MockOAuth2Session()) mock.requestAccess { (response: AnyObject?, error:NSError?) -> Void in XCTAssertTrue("ACCESS_TOKEN" == response as String, “response with access token") expectation.fulfill() } waitForExpectationsWithTimeout(10, handler: nil) } public class MockOAuth2Session: OAuth2Session { public override var refreshToken: String? { get {return nil} set(data) {} } public override func tokenIsNotExpired() -> Bool { return false } } class OAuth2ModulePartialMock: OAuth2Module { override func refreshAccessToken( completionHandler:(AnyObject?, NSError?) -> ()) { completionHandler("NEW_ACCESS_TOKEN", nil) } override func requestAuthorizationCode( completionHandler: (AnyObject?, NSError?) -> ()) { completionHandler("ACCESS_TOKEN", nil) } } aerogear-ios-oauth2
  64. Mocking yourself func testRequestAccessWithAuthzCodeFlow() { let expectation = expectationWithDescription("AccessRequestWithAuthzFlow"); let

    googleConfig = . . . var mock = OAuth2ModulePartialMock(config: googleConfig, session: MockOAuth2Session()) mock.requestAccess { (response: AnyObject?, error:NSError?) -> Void in XCTAssertTrue("ACCESS_TOKEN" == response as String, “response with access token") expectation.fulfill() } waitForExpectationsWithTimeout(10, handler: nil) } public class MockOAuth2Session: OAuth2Session { public override var refreshToken: String? { get {return nil} set(data) {} } public override func tokenIsNotExpired() -> Bool { return false } } class OAuth2ModulePartialMock: OAuth2Module { override func refreshAccessToken( completionHandler:(AnyObject?, NSError?) -> ()) { completionHandler("NEW_ACCESS_TOKEN", nil) } override func requestAuthorizationCode( completionHandler: (AnyObject?, NSError?) -> ()) { completionHandler("ACCESS_TOKEN", nil) } } aerogear-ios-oauth2
  65. Mocking yourself func testRequestAccessWithAuthzCodeFlow() { let expectation = expectationWithDescription("AccessRequestWithAuthzFlow"); let

    googleConfig = . . . var mock = OAuth2ModulePartialMock(config: googleConfig, session: MockOAuth2Session()) mock.requestAccess { (response: AnyObject?, error:NSError?) -> Void in XCTAssertTrue("ACCESS_TOKEN" == response as String, “response with access token") expectation.fulfill() } waitForExpectationsWithTimeout(10, handler: nil) } public class MockOAuth2Session: OAuth2Session { public override var refreshToken: String? { get {return nil} set(data) {} } public override func tokenIsNotExpired() -> Bool { return false } } class OAuth2ModulePartialMock: OAuth2Module { override func refreshAccessToken( completionHandler:(AnyObject?, NSError?) -> ()) { completionHandler("NEW_ACCESS_TOKEN", nil) } override func requestAuthorizationCode( completionHandler: (AnyObject?, NSError?) -> ()) { completionHandler("ACCESS_TOKEN", nil) } } aerogear-ios-oauth2
  66. SwiftyJSON aerogear-ios-oauth2 aerogear-ios-http aerogear-ios-jsonsz Be smart, code open source Dollar.swift