ドキュメントみたいな API Client を求めて

ドキュメントみたいな API Client を求めて

横浜へなちょこiOS #20

0a86666ca90016b2a28289dda921b0d7?s=128

Toshihiro Morimoto

June 20, 2015
Tweet

Transcript

  1. υΩϡϝϯτΈ͍ͨͳ API Client ΛٻΊͯ 2015/06/20 ԣ඿΁ͳͪΐ͜iOS #20 @dealforest Toshihiro Morimoto

  2. ࣗݾ঺հ

  3. Crying DFTDebugScreenshot cocoapods-browser Tuna

  4. https://github.com/dealforest/Tuna

  5. by AlcatrazTour

  6. ܦҢ

  7. ܦҢ

  8. ·ͩಀ͛ΕΔ…

  9. ܦҢ

  10. Φϫλ/(^o^)\

  11. ͱ͍͏Θ͚ͰLT͢Δ͜ͱʹͳΓ·ͨ͠

  12. ஫ҙ ͜ΕΑΓઌ͸ࢲݟͰຬͪᷓΕ͍ͯΔͷͰɹ ͦΕΛ౿·্͑ͨͰฉ͍ͯ௖͚Δͱ޾͍Ͱ ͢

  13. API Client ࢙ 1. UIKitظ (NSURLConnection, NSURLSession ౳) 2. ϥΠϒϥϦظ

    (AFNetwork, Alamofire ౳) 3. UIKit + αظ (؆୯ͳ Wrapper Λࣗ෼Ͱॻ͘)
  14. ࠷௿ݶͳχʔζ • ௨৴ϨΠϠʔΛू໿Խ͍ͨ͠ • Object Mapping ͸΄͍͠ • υΩϡϝϯτΛॻ͔ͣʹɺ͍͔ʹυΩϡ ϝϯτͬΆ͘͢Δ͔

  15. ͍͔ʹυΩϡϝϯτͬΆ͘͢Δ͔

  16. Why document?

  17. Կ౓΋ API Client Λ࢖͖ͬͯ·͕ͨ͠ ਺िؒ΋ͨͭͱ API ࢓༷͕ هԱͷ൴ํʹ๨٫ͯ͠͠·͍·͢

  18. ͞Θ͍ͬͯͳ͍ͱಛʹૣ͍Ͱ͢ΑͶ

  19. ͦ͏ͳͬͨ৔߹ɺͲΕ͚ͩஸೡʹ ίʔυΛॻ͍ͨͱͯ͠΋ API ͷ ࢓༷·Ͱ͸Θ͔Βͳ͍Ͱ͠ΐ͏

  20. ͳͷͰ࣮ࡍʹϦϑΝϨϯεΛΈͨΓ API Λୟ͍ͨΓ ςετίʔυͰ֬ೝͨ͠Γ ͳͲͳͲ

  21. ͦΜͳ࣌ͱ͋ΔൃදΛΈͯ ײಈ͠·ͨ͠

  22. https://speakerdeck.com/ishkawa/introducing-apikit

  23. ίʔυ͕ͦͷ·· υΩϡϝϯτͬΆ͘ಡΊΔͷͰ͢

  24. Ωʔʹͳͬͯ͘Δͷ͕ “Swift is a type safe language”

  25. ͦͯ͠ ϥΠϒϥϦظ ;ͨͨͼʂʂʂ

  26. APIKit + Himotoki

  27. APIKit https://github.com/ishkawa/APIKit

  28. APIKit Λ API ͷ υΩϡϝϯτͱͯ͠࢖͏

  29. https://speakerdeck.com/ishkawa/introducing-apikit

  30. GitHub serach ͷ API ͷ৔߹

  31. ར༻͢Δํ

  32. // https://api.github.com/search/repositories let request = GitHub.Endpoint.SearchRepositories( query: "APIKit", sort: .Stars,

    order: .Asceding ) GitHub.sendRequest(request) { response in switch response { case .Success(let box): self.repositories = box.unbox case .Fail println(box.value) } }
  33. // https://api.github.com/search/repositories let request = GitHub.Endpoint.SearchRepositories( query: "APIKit", sort: .Stars,

    order: .Asceding ) GitHub.sendRequest(request) { response in switch response { case .Success(let box): self.repositories = box.unbox case .Fail println(box.value) } } DBTF CPYWBMVF 4VDDFTT 3FTQPOTF ϦΫΤετ ੒ޭ 'BJMVSF /4&SSPS ϦΫΤετ ࣦഊ
  34. ࣮૷

  35. class GitHub: API { override class var baseURL: NSURL {

    return NSURL(string: "https://api.github.com")! } class Endpoint { // https://developer.github.com/v3/search/#search-repositories class SearchRepositories: APIKit.Request { enum Sort: String { case Stars = "stars" case Forks = "forks" case Updated = "updated" } enum Order: String { case Ascending = "asc" case Descending = "desc" } typealias Response = [Repository] ... class func responseFromObject(object: AnyObject) -> Response? { var repositories = [Repository]() if let dictionaries = object["items"] as? [NSDictionary] { for dictionary in dictionaries { if let repository = Repository(dictionary: dictionary) { repositories.append(repository) } } } return repositories } } } }
  36. class GitHub: API { override class var baseURL: NSURL {

    return NSURL(string: "https://api.github.com")! } class Endpoint { // https://developer.github.com/v3/search/#search-repositories class SearchRepositories: APIKit.Request { enum Sort: String { case Stars = "stars" case Forks = "forks" case Updated = "updated" } enum Order: String { case Ascending = "asc" case Descending = "desc" } typealias Response = [Repository] ... class func responseFromObject(object: AnyObject) -> Response? { var repositories = [Repository]() if let dictionaries = object["items"] as? [NSDictionary] { for dictionary in dictionaries { if let repository = Repository(dictionary: dictionary) { repositories.append(repository) } } } return repositories } } } }
  37. class Endpoint { class SearchRepositories: APIKit.Request { enum Sort: String

    { case Stars = "stars" case Forks = "forks" case Updated = "updated" } enum Order: String { case Ascending = "asc" case Descending = "desc" } typealias Response = [Repository] let query: String let sort: Sort let order: Order var URLRequest: NSURLRequest? { return GitHub.URLRequest( method: .GET, path: "/search/repositories", parameters: ["q": query, "sort": sort.rawValue, "order": order.rawValue] ) } init(query: String, sort: Sort = .Stars, order: Order = .Ascending) { self.query = query self.sort = sort self.order = order } func responseFromObject(object: AnyObject) -> Response? { var repositories = [Repository]() if let dictionaries = object["items"] as? [NSDictionary] { for dictionary in dictionaries { if let repository = Repository(dictionary: dictionary) { repositories.append(repository) } } } return repositories } } }
  38. class Endpoint { class SearchRepositories: APIKit.Request { enum Sort: String

    { case Stars = "stars" case Forks = "forks" case Updated = "updated" } enum Order: String { case Ascending = "asc" case Descending = "desc" } typealias Response = [Repository] let query: String let sort: Sort let order: Order var URLRequest: NSURLRequest? { return GitHub.URLRequest( method: .GET, path: "/search/repositories", parameters: ["q": query, "sort": sort.rawValue, "order": order.rawValue] ) } init(query: String, sort: Sort = .Stars, order: Order = .Ascending) { self.query = query self.sort = sort self.order = order } func responseFromObject(object: AnyObject) -> Response? { var repositories = [Repository]() if let dictionaries = object["items"] as? [NSDictionary] { for dictionary in dictionaries { if let repository = Repository(dictionary: dictionary) { repositories.append(repository) } } } return repositories } } } initializer Object Mapping
  39. class Endpoint { class SearchRepositories: APIKit.Request { enum Sort: String

    { case Stars = "stars" case Forks = "forks" case Updated = "updated" } enum Order: String { case Ascending = "asc" case Descending = "desc" } typealias Response = [Repository] let query: String let sort: Sort let order: Order var URLRequest: NSURLRequest? { return GitHub.URLRequest( method: .GET, path: "/search/repositories", parameters: ["q": query, "sort": sort.rawValue, "order": order.rawValue] ) } init(query: String, sort: Sort = .Stars, order: Order = .Ascending) { self.query = query self.sort = sort self.order = order } func responseFromObject(object: AnyObject) -> Response? { var repositories = [Repository]() if let dictionaries = object["items"] as? [NSDictionary] { for dictionary in dictionaries { if let repository = Repository(dictionary: dictionary) { repositories.append(repository) } } } return repositories } } } ΤϯυϙΠϯτͷ৘ใ
  40. ·ΔͰυΩϡϝϯτΛ ॻ͖͔ࣸͨ͠ͷΑ͏ͳఆٛ

  41. ΤϯυϙΠϯτͷఆٛΛ ͜ͷΫϥεʹू໿ԽͰ͖Δ

  42. ͨͩ਺͕૿͑ͯ͘Δͱ initializer, object mapper ͕ अຐʹͳͬͯ͘Δ

  43. class Endpoint { class SearchRepositories: APIKit.Request { enum Sort: String

    { case Stars = "stars" case Forks = "forks" case Updated = "updated" } enum Order: String { case Ascending = "asc" case Descending = "desc" } typealias Response = [Repository] let query: String let sort: Sort let order: Order var URLRequest: NSURLRequest? { return GitHub.URLRequest( method: .GET, path: "/search/repositories", parameters: ["q": query, "sort": sort.rawValue, "order": order.rawValue] ) } init(query: String, sort: Sort = .Stars, order: Order = .Ascending) { self.query = query self.sort = sort self.order = order } func responseFromObject(object: AnyObject) -> Response? { var repositories = [Repository]() if let dictionaries = object["items"] as? [NSDictionary] { for dictionary in dictionaries { if let repository = Repository(dictionary: dictionary) { repositories.append(repository) } } } return repositories } } } initializer Object Mapping
  44. class Endpoint { class SearchRepositories: APIKit.Request { enum Sort: String

    { case Stars = "stars" case Forks = "forks" case Updated = "updated" } enum Order: String { case Ascending = "asc" case Descending = "desc" } typealias Response = [Repository] let query: String let sort: Sort let order: Order var URLRequest: NSURLRequest? { return GitHub.URLRequest( method: .GET, path: "/search/repositories", parameters: ["q": query, "sort": sort.rawValue, "order": order.rawValue] ) } init(query: String, sort: Sort = .Stars, order: Order = .Ascending) { self.query = query self.sort = sort self.order = order } class func responseFromObject(object: AnyObject) -> Response? { return object["items"].flatMap(decode) ?? [] } } } Using Himotoki
  45. Awesome!!!!

  46. Himotoki ͷ͓͔͛Ͱ ΑΓυΩϡϝϯτͱͯ͠ Έ΍͘͢ͳΓ·͢Ͷ

  47. Himotoki https://github.com/ikesyo/Himotoki

  48. Himotoki Λ API Ϩεϙϯεͷ υΩϡϝϯτͱͯ͠࢖͏

  49. http://www.slideshare.net/syoikeda/himotoki-a-typesafe-json-decoding-library

  50. Usage struct User: Decodable { let id: Int let login:

    String let avatar_url: String static func decode(e: Extractor) -> User? { let create = { User($0) } return build( e <| "id", e <| "login", e <| "avatar_url" ).map(create) } }
  51. Operators <| // T <|? // T? <|| // [T]

    <||? // [T]? <|-| // [String: T] <|-|? // [String: T]?
  52. tips • SnakeCase → CamelCase • ܕม׵ • ܕม׵ (Enum)

    • σʔλ֊૚͕ਂ͍৔߹
  53. SnakeCase → CamelCase struct User: Decodable { let id: Int

    let login: String let avatar_url: String static func decode(e: Extractor) -> User? { let create = { User($0) } return build( e <| "id", e <| "login", e <| "avatar_url" ).map(create) } }
  54. SnakeCase → CamelCase struct User: Decodable { let id: Int

    let login: String let avatarURL: String static func decode(e: Extractor) -> User? { let create = { User($0) } return build( e <| "id", e <| "login", e <| "avatar_url" ).map(create) } }
  55. ܕ͑͞Ұக͍ͯ͠Ε͹ ໊લ͸ͳΜͰ΋͍͍

  56. ܕม׵ struct User: Decodable { let id: Int let login:

    String let avatarURL: String static func decode(e: Extractor) -> User? { let create = { User($0) } return build( e <| "id", e <| "login", e <| "avatar_url" ).map(create) } }
  57. ܕม׵ struct User: Decodable { let id: Int let login:

    String let avatarURL: NSURL static func decode(e: Extractor) -> User? { let create = { User($0) } return build( e <| "id", e <| "login", (e <| "avatar_url").flatMap { NSURL(string: $0) } ).map(create) } }
  58. ܕม׵ (Enum) struct User: Decodable { enum Type: String {

    case User = "User" case Organization = "Organization" } let id: Int let login: String let type: Type let avatarURL: NSURL static func decode(e: Extractor) -> User? { let create = { User($0) } return build( e <| "id", e <| "login", (e <| "type").flatMap { Type(rawValue: $0) }, e <| "avatar_url").flatMap { NSURL(string: $0) } ).map(create) }
  59. σʔλ֊૚͕ਂ͍৔߹ repositories = [ { "id": 3081286, "name": "Tetris", "owner":

    { "login": "dtrupenn", "id": 872147, "avatar_url": "https://secure.gravatar.com/...", "type": "User" }, ... } ]
  60. σʔλ֊૚͕ਂ͍৔߹ struct Repository: Decodable { let id: Int let name:

    String let owner: User static func decode(e: Extractor) -> Repository? { let create = { Repository($0) } return build( e <| "id", e <| "name", e <| "owner" ).map(create) } } struct User: Decodable { let id: Int let login: String let avatarURL: NSURL static func decode(e: Extractor) -> User? { let create = { User($0) } return build( e <| "id", e <| "login", (e <| "avatar_url").flatMap { NSURL(string: $0) } ).map(create) } }
  61. σʔλ֊૚͕ਂ͍৔߹ struct Repository: Decodable { let id: Int let name:

    String let ownerId: Int static func decode(e: Extractor) -> Repository? { let create = { Repository($0) } return build( e <| "id", e <| “name", e <| [“owner”, “id”], ).map(create) } }
  62. ͨ·ʹมͳఆٛΛ͢Δͱ ܕਪ࿦͕ղܾͰ͖ͣʹ ϑΝϯ͕ᄬΓΛ্͛ͨ͋ͱtimeout తͳ ΤϥʔͰίϯύΠϧʹࣦഊ͢Δ

  63. ͭ·Γ͸ϑΝϯ͕ᄬΓΛ͋͛͸͡ΊΔͱ ࣗ෼͕มͳఆٛΛ͍ͯ͠ΔՄೳੑ͕ߴ͍ͷͰ ଈࠁݟͳ͓ͨ͠ํ͕͍͍Ͱ͢

  64. ͓͢͢Ί • Object Mapping ͷఆ͚ٛͩΛ୯ҰϑΝΠϧʹͯ͠ఆٛ͠ ͓͚ͯ͹ݟ΍͘͢ͳΔ • extension Ͱ logic

    Λఆٛ͢Ε͹σʔλͱৼΔ෣͍͕Θ ͔ΕͯΘ͔Γ΍͍͢ • ctrl + 6 ͨ͠Βͪΐͬͱײಈ͢Δ • ؆୯ͳ΋ͷͰ͍͍͔Βςετ͸ॻ͍ͱ͍ͨํ͕͍͍ • Կ͔ͷมߋͰ decode ʹࣦഊͨ͠ࡍʹݕ஌Ͱ͖ΔͨΊ • stub Λߋ৽͢ΔεΫϦϓτͱ͔͋Ε͹ϝϯς͸͔ͲΔ
  65. ·ͱΊ APIKit + Himotoki Λ࢖͑͹ݟࣄʹϦϑΝϨ ϯεͬΆ͘ͳΓ·͢Ͷ ͔ͬ͠ΓϨΠϠʔ΋෼͚ΕΔͷͰอक͠΍ ͍͢ίʔυʹͳΓ·͢

  66. ·ͱΊ શͯΛ͜ΕͰ΍Ε͹͍͍ͱ͍͏Θ͚Ͱͳ͘ ࣗࣾαʔϏεͷAPIͷ৔߹ͳͲϦϑΝϨϯε ·Ͱ༻ҙͯ͠ΔέʔεͳͲ΄΅ͳ͍ͷͰɺ গ͕࣌ؒ͋͘͠ͱ๨͕ͪʹͳͬͯ͠·͏ͷ Ͱͦ͏͍͏࣌ʹศརͰ͢Ͷ ͜͏͍͏Ξϓϩʔν΋͋Γ͔ͳͱࢥ͏࣍ୈ Ͱ͢

  67. ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠