Mike JS Choi
Mike JS Choi

Reputation: 1151

Swift Protocol Oriented Networking code crashing Xcode

Playing around in playground with protocol oriented networking code and on the last line where I bring everything together, Xcode crashes by saying

LLDB RPC server has crashed... and so on

There must be something terrifically wrong in my code and I'm sure it's on the last couple of lines but I can't figure it out.

import UIKit
import PlaygroundSupport

struct Repo {
    let id: Int
    let name: String
}

extension Repo {
    init?(dict: JSONDict) {
        guard let id = dict["id"] as? Int, let name = dict["name"] as? String else { return nil }
        self.id = id
        self.name = name
    }
}

typealias JSONDict = [String:Any]

protocol Resource {
    associatedtype Content
    var url: URL { get }
    func parse(data: Data) -> Content?
}

extension Resource {
    func parseJSON(data: Data) -> Any? {
        let json = try? JSONSerialization.jsonObject(with: data)
        return json
    }
}

struct RepoService: Resource {
    let url = URL(string: "https://api.github.com/users/mkchoi212/repos")!

    func parse(data: Data) -> [Repo]? {
        guard let dictArray = self.parseJSON(data: data) as? [JSONDict] else { return nil }
        return dictArray.flatMap(Repo.init)
    }
}

final class WebService<Content> {
    func load<R: Resource>(resource: R, completion: @escaping (R.Content?) -> ()) {
        URLSession.shared.dataTask(with: resource.url) { data, _, _ in
            let res = data.flatMap(resource.parse)
            completion(res)
        }.resume()
    }
}

WebService().load(resource: RepoService) { res in
    for elem in res {
        print(elem)
    }
}

PlaygroundPage.current.needsIndefiniteExecution = true

Upvotes: 2

Views: 216

Answers (2)

user3441734
user3441734

Reputation: 17544

working example

import Foundation

import PlaygroundSupport

struct Repo {
    let id: Int
    let name: String
}

extension Repo {
    init?(dict: JSONDict) {
        guard let id = dict["id"] as? Int, let name = dict["name"] as? String else { return nil }
        self.id = id
        self.name = name
    }
}

typealias JSONDict = [String:Any]

protocol Resource {
    associatedtype Content
    var url: URL { get }
    func parse(data: Data) -> Content?
}

extension Resource {
    func parseJSON(data: Data) -> Any? {
        let json = try? JSONSerialization.jsonObject(with: data)
        return json
    }
}

struct RepoService: Resource {
    let url = URL(string: "https://api.github.com/users/mkchoi212/repos")!

    func parse(data: Data) -> [Repo]? {
        guard let dictArray = self.parseJSON(data: data) as? [JSONDict] else { return nil }
        return dictArray.flatMap(Repo.init)
    }
}

final class WebService {
    func load<R: Resource>(resource: R, completion: @escaping (R.Content) -> ()) {
        URLSession.shared.dataTask(with: resource.url) { data, _, _ in
            if let res = data.flatMap(resource.parse) {
                completion(res)
            }
            }.resume()
    }
}

WebService().load(resource: RepoService()) { res in
    for elem in res {
        print(elem)
    }
}

PlaygroundPage.current.needsIndefiniteExecution = true

gave me this result

Repo(id: 93473150, name: "bitbuf")
Repo(id: 28517340, name: "DIJKASTRAS")
Repo(id: 71310619, name: "git-strbuf")
Repo(id: 30558002, name: "Habitats-master")
Repo(id: 28517464, name: "Home-Automation-Project")
Repo(id: 33394787, name: "hw.")
Repo(id: 39911528, name: "JPSThumbnailAnnotation")
Repo(id: 37162894, name: "M3")
Repo(id: 39709018, name: "Maroon")
Repo(id: 41267183, name: "mkchoi212.github.io")
Repo(id: 28519262, name: "Money-Saving-Meals")
Repo(id: 39857111, name: "MRCircularProgressView")
Repo(id: 81773538, name: "papers-we-love")
Repo(id: 37074317, name: "socket.io-client-swift")
Repo(id: 28519322, name: "Wordsneak")

Upvotes: 0

Martin R
Martin R

Reputation: 539815

A crashing compiler is a bug and should be reported. Xcode 9 (beta) does not crash with your code but gives helpful error messages leading to a solution:

error: generic parameter 'Content' could not be inferred
WebService().load(resource: RepoService) { res in
             ^

The generic placeholder Content in class WebService<Content> is not used at all, remove it and make the class non-generic. Then:

error: generic parameter 'R' could not be inferred
WebService().load(resource: RepoService) { res in
             ^

You have to pass an instance of RepoService to the load method, and res inside the closure is an optional.

With these changes

final class WebService {
    func load<R: Resource>(resource: R, completion: @escaping (R.Content?) -> ()) {
        URLSession.shared.dataTask(with: resource.url) { data, _, _ in
            let res = data.flatMap(resource.parse)
            completion(res)
            }.resume()
    }
}

WebService().load(resource: RepoService()) { res in
    guard let res = res else { return }
    for elem in res {
        print(elem)
    }
}

your code compiles and runs as expected, in Xcode 8.3.3 and 9.

Upvotes: 4

Related Questions