【问题标题】:How can i construct multiple HTTP requests in Swift?如何在 Swift 中构造多个 HTTP 请求?
【发布时间】:2016-07-24 14:25:31
【问题描述】:

我对 Swift 和编程完全陌生。我非常热衷于学习所有正确的方法。因此,任何额外的提示或评论都将受到赞赏。

我正在向 api 发出 HTTP 请求,效果很好。问题是每个请求限制为 100 个结果。我可以设置一个可选的偏移量和一个限制。如果我给出 101 的限制,我会收到服务器错误消息:“错误请求:为限制指定的值无效。允许的最大值为 100。”总数是 101,所以我至少需要做两个请求。只有在收到所有请求的总数据后,我才想填充我的 tableview。这就是我所拥有的:

class Book {

var id: Int
var title: String
let description: String
var coverImage: String
var isbn: String
var publisherID: Int
var publisherName: String
var authorID: Int
var authorFirstName: String
var authorLastName: String

class func getDataFromJson(completionHandler: ([Book]) -> ()) {

    var books = [Book]()

    let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())

    let request = NSURLRequest(URL: NSURL(string: "http://example.website.nl/books/highlighted")!)

    let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in

        if let data = data {

            do {
                let json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)

                if let booksFromResult = json["books"] as? [[String: AnyObject]] {
                    for book in booksFromResult {
                        let bookID = book["id"] as! Int
                        let bookTitle = book["title"] as! String
                        let bookDescription = book["description"] as! String
                        let bookCoverImage = book["cover_url"] as! String
                        let bookISBN = book["isbn"] as! String
                        if let bookPublisher = book["publisher"] as? [String: AnyObject] {
                            let bookPublisherID = bookPublisher["id"] as! Int
                            let bookPublisherName = bookPublisher["name"] as! String
                            if let bookAuthor = book["author"] as? [String: AnyObject] {
                                let bookAuthorID = bookAuthor["id"] as! Int
                                let bookAuthorFirstname = bookAuthor["first_name"] as! String
                                let bookAuthorLastName = bookAuthor["last_name"] as! String
                                books.append(Book(id: bookID, title: bookTitle, description: bookDescription, coverImage: bookCoverImage, isbn: bookISBN, publisherID: bookPublisherID, publisherName: bookPublisherName, authorID: bookAuthorID, authorFirstName: bookAuthorFirstname, authorLastName: bookAuthorLastName))
                            }
                        }

                    }
                    print(books.count)

                }
                dispatch_async(dispatch_get_main_queue(),{
                    completionHandler(books)
                })
            } catch {
                print("error serializing JSON: \(error)")
            }

        }
    }
    task.resume()
}




init(id: Int, title: String, description: String, coverImage: String, isbn: String, publisherID: Int, publisherName: String, authorID: Int, authorFirstName: String, authorLastName: String) {

    self.id = id
    self.title = title
    self.description = description
    self.coverImage = coverImage
    self.isbn = isbn
    self.publisherID = publisherID
    self.publisherName = publisherName
    self.authorID = authorID
    self.authorFirstName = authorFirstName
    self.authorLastName = authorLastName
  }
}

我已经尝试解决这个问题超过 24 小时。我真的在这里和网上搜索过一个例子。我在这里找到的小东西帮不了我。

我的想法是如何做到这一点:

  1. 发出第一个请求 -> 将数据存储在某处
  2. 发出第二个请求 -> 将数据添加到存储的数据中
  3. 发出最后一个请求 -> 将数据添加到存储的数据中
  4. 发送数据以填充表格视图。

我是否应该使用 url 数组并进行迭代 通过它而不是将数据附加到某个地方?

我希望有人可以帮助我。我真的很感激。

提前致谢。

【问题讨论】:

  • 您说“问题是每个请求限制为 50 个。”但目前尚不清楚这意味着什么。什么限制了对 50 个项目的响应?服务器代码?那么你应该如何申请下一批 50 件商品呢?是否有明确的协议来找出请求的总项目数,然后请求 50 个结果的批次?
  • 嗨,邓肯,抱歉不清楚,他们称之为可选的偏移和限制。当我收到批次时,我看到限制为 50,并且还有一个值总计为 101。
  • @Duncan,你让我以另一种方式思考。我可能没看懂解释。限制是 100,但我需要拿回 101。如果我给出 101 的限制,我会收到服务器错误消息:“错误请求:为 limit 指定的值无效。允许的最大值为 100。”
  • 好的,您需要编辑您的问题以显示附加信息。听起来您需要询问服务器它们的总条目数,然后如果结果> 50,则编写代码以一次循环请求 50 个,然后每次收到前 50 个时发送一个新请求.
  • @Duncan,我编辑了这个问题,谢谢。我已经取回了总条目,即 101。我确实理解一个接一个地执行请求的逻辑。我不明白他们是怎么做的。谢谢。

标签: ios swift httprequest


【解决方案1】:

直接回答问题:

// Heavily based on the video I recommended. Watch it for a great explanation
struct Resource<A>{
    let url: NSURL
    let parse: (NSData) -> [A]?
}

extension Book {

    // You could figure a way to dynamically populate this based on limiting
    static let urls = [NSURL(string: "http://example.website.nl/books/highlighted")!,
                       NSURL(string: "http://example.website.nl/books/highlighted2")!]

    // Creates an array of Requests passing in each url for the url, but the same parse function
    static let requests = urls.map { Resource<Book>(url: $0, parse: Book.parse) }

    // Used by Webservice (from the Resource struct) to parse the data into a Book
    static let parse: (NSData?) -> [Book]? = { data in
        guard let data = data else { return nil }

        guard let json = try? NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) else {
            print("Error deserializing json.")
            return nil
        }
        var books: [Book]? = nil
        guard let jsonBooks = json["books"] as? [[String: AnyObject]] else { return nil }
        for jsonBook in jsonBooks {
            guard let book = Book(fromJson: jsonBook) else { continue } // skips nil books from failable initializer, depends on how you want to handle that
            books = books ?? [Book]() // if nil create a new array, if not use the old one
            books!.append(book)
        }
        return books
    }
}

class Webservice {

    // A stands for a generic type. You could add a type called Publisher later and use the same function
    // This adopted from the video I showed you so it's a little more in depth
    func loadAll<A>(resources: [Resource<A>], completion: [A] -> ()) {
        let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
        var currentRequest = 0 // used to keep track of asynchronous callback order
        var allA = [A]()
        for resource in resources {
            session.dataTaskWithURL(resource.url) { (data, _, _) in
                defer {
                    currentRequest += 1 // marks that we're done with one request

                    // This check ensures that we only call the completion handler
                    // after we're done with the last request
                    if currentRequest == resources.count {
                        completion(allA)
                    }
                }
                guard let data = data else { return }

                // this parse function comes from the resource struct passed in.
                // It converts the data we get back from one request into an array of books.
                guard let manyA = resource.parse(data) else { return }

                // This is the total running tally of books from all our requests.
                allA.appendContentsOf(manyA)
            }
        }
    }
}

class TableViewController: UITableViewController {

    var books = [Book]() {
        didSet { tableView.reloadData() }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Call site
        Webservice().loadAll(Book.requests) { [weak self] (books) in
            dispatch_async(dispatch_get_main_queue()) {
                self?.books.appendContentsOf(books)
            }
        }

    }

    //... all your normal methods for cells and stuff
}

额外

我基于 JSON 对象为您的 Book 类创建了一个可失败的初始化程序,这样您的类方法就不必进行大部分解析。您可能希望您的 Book 类型为 struct 以获得 memberwise initializerpass by value semantics)

利用guard let else 控制语句来避免pyramid of doom 可选展开。

defer statements 对于调用完成处理程序一次非常有用,无论您如何退出范围(避免重复代码)。

高度推荐this video showing a webservice api design。它有点高级,但它向您展示了一种从 Web 服务初始化模型对象的好方法。

class Book {

    var id: Int
    var title: String
    let description: String
    var coverImage: String
    var isbn: String
    var publisherID: Int
    var publisherName: String
    var authorID: Int
    var authorFirstName: String
    var authorLastName: String

    init(id: Int, title: String, description: String, coverImage: String, isbn: String, publisherID: Int, publisherName: String, authorID: Int, authorFirstName: String, authorLastName: String) {

        self.id = id
        self.title = title
        self.description = description
        self.coverImage = coverImage
        self.isbn = isbn
        self.publisherID = publisherID
        self.publisherName = publisherName
        self.authorID = authorID
        self.authorFirstName = authorFirstName
        self.authorLastName = authorLastName
    }

    typealias JSONDictionary = [String: AnyObject] // syntactic sugar, makes it clearer

    convenience init?(fromJson json: JSONDictionary) {
        let bookID = json["id"] as! Int
        let bookTitle = json["title"] as! String
        let bookDescription = json["description"] as! String
        let bookCoverImage = json["cover_url"] as! String
        let bookISBN = json["isbn"] as! String

        // I would use guard let else statements here to avoid the pyramid of doom but it's stylistic
        if let bookPublisher = json["publisher"] as? [String: AnyObject] {
            let bookPublisherID = bookPublisher["id"] as! Int
            let bookPublisherName = bookPublisher["name"] as! String
            if let bookAuthor = json["author"] as? [String: AnyObject] {
                let bookAuthorID = bookAuthor["id"] as! Int
                let bookAuthorFirstname = bookAuthor["first_name"] as! String
                let bookAuthorLastName = bookAuthor["last_name"] as! String
                self.init(id: bookID, title: bookTitle, description: bookDescription, coverImage: bookCoverImage, isbn: bookISBN, publisherID: bookPublisherID, publisherName: bookPublisherName, authorID: bookAuthorID, authorFirstName: bookAuthorFirstname, authorLastName: bookAuthorLastName)
                return

            }
        }
        return nil
    }

}

extension Book {

    class func getDataFromJson(completionHandler: ([Book]) -> ()) {

        var books = [Book]()

        let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())

        let request = NSURLRequest(URL: NSURL(string: "http://example.website.nl/books/highlighted")!)

        let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in

            defer { // no matter how you exit the scope this will be called
                dispatch_async(dispatch_get_main_queue()) {
                    completionHandler(books)
                }
            }

            guard let data = data else { return } // still will call the deferred completion handler

            guard let json = try? NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) else {
                print("Error deserializing json.")
                return // still will call the deferred completion handler
            }

            if let jsonBooks = json["books"] as? [[String: AnyObject]] {
                for jsonBook in jsonBooks {
                    guard let book = Book(fromJson: jsonBook) else { continue } // skips nil books from failable initializer, depends on how you want to handle that
                    books.append(book)
                }
                print(books.count)
            }
        }
        task.resume()
        // call the deferred completion handler after leaving scope
    }
}

【讨论】:

  • @Tommy,非常感谢。我确实打算重新排序东西并使用结构和枚举进行错误处理。肯定会使用guard let else。我会研究你的代码并尝试理解它的作用,谢谢。通过它,我不确定一件事。我需要三个具有不同 url 的请求。我必须更改第二个和第三个请求中的偏移量才能取回正确的批次。我似乎没有在您提供的代码中找到此选项。也许我理解得不够好?还是我的解释不够清楚?
  • 我没有意识到您必须更改每个请求的 url。我会在一分钟内提交一个答案。
  • 是的,抱歉,不清楚。谢谢你的帮助,你太棒了!
  • 你是一个灵感!非常感谢你!这将使我在试图了解发生的事情时忙个不停。感谢添加 cmets,这有很大帮助!我想对你的答案投赞成票,但似乎我缺乏声誉:-(。我快到了,我还需要 4 个。一旦我有了它们,我就会投票。
  • 哈哈没问题,如果您需要任何进一步的解释,请询问。我会警告你,这只是由编译器检查,我没有运行任何测试来确保每个部分在运行时工作(即 json,所以这是你的职责)。但是,如果您遇到任何奇怪的编译问题,请发表评论。
猜你喜欢
  • 1970-01-01
  • 2021-12-16
  • 1970-01-01
  • 1970-01-01
  • 2021-10-26
  • 2020-11-13
  • 2017-03-02
  • 1970-01-01
相关资源
最近更新 更多