如何在Swift中构建多个HTTP请求?

时间:2016-07-24 14:25:31

标签: ios swift httprequest

我对Swift和编程完全不熟悉。我非常热衷于学习所有正确的方法。所以任何额外的提示或评论总是受到赞赏。

我正在向api发出HTTP请求,并且工作正常。问题是每个请求限制为100个结果。 Theres是一个可选的偏移和我可以设置的限制。如果我给出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. 发送数据以填充tableview。
  5. 我应该使用url数组并迭代  通过它,而不是将数据附加到某个地方?

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

    提前致谢。

1 个答案:

答案 0 :(得分: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
    }
}