Capturing data from Alamofire

2019-01-29 04:52发布

I'm having trouble retrieving data from my Alamofire request asynchronously.

class BookGetter {

    static let instance = BookGetter()

    func getBook(bookId: String) -> Book {

        let rootUrl = "https://www.someusefulbookapi.com/bookid=?"
        let url = rootUrl + bookId
        var title = ""

        Alamofire.request(.GET, url).response { response in

            let jsonDict = JSON(data: response.2!)

            title = String(jsonDict["items"][0]["volumeInfo"]["title"])
        }
        let book = Book(title: title)
        print(book.title)
        return book
    }
}

The output of print(book.title) is "", and I understand this is because the print statement is running before the request returns.

How do I get the book instance to be returned only when it is instantiated with the data from the request?

2条回答
神经病院院长
2楼-- · 2019-01-29 05:25

The problem you have is that you are calling an asynchronous method and expecting to return the result synchronously. When your code is executed, the getBook function completes and returns before even the GET request has complete.

Basically, you have two options:

  1. Update your getBook method to be asynchronous and return the result with a completion block/callback
  2. Wait for the asynchronous call to complete, blocking the current thread (this is OK as long as it is not the main thread you are blocking), and return the result synchronously.

1. Update your method to be asynchronous

To do this, you must return the result on a block/callback function.

class BookGetter {

    static let instance = BookGetter()

    func getBook(bookId: String, complete: (book: Book?, error: NSError?) -> Void) {

        let rootUrl = "https://www.someusefulbookapi.com/bookid=?"
        let url = rootUrl + bookId
        var title = ""

        Alamofire.request(.GET, url).response { request, response, data, error in

            // TODO: You should check for network errors here
            // and notify the calling function and end-user properly.
            if error != nil {
                complete(book: nil, error: error as? NSError)
                return
            }

            let jsonDict = JSON(data: response.2!)

            title = String(jsonDict["items"][0]["volumeInfo"]["title"])
            let book = Book(title: title)
            print(book.title)
            complete(book: book, error: nil)
        }
    }
}

As mentioned in the above code, ideally you should handle errors in the callback response (including exceptions while parsing the JSON). Once handled, you can update the callback parameters to (book: Book?, error: NSError?) -> Void or similar, and check for book or error to be set on the caller function.

To call the function, you need to pass a block to handle the response:

BookGetter.instance.getBook("bookID") { (book, error) in
   if error != nil {
      // Show UIAlertView with error message (localizedDescription)
      return
   }
   // Update User Interface with the book details
}

2. Wait for the asynchronous call to complete

As mentioned above, this is a good idea only if you were running this code on a background thread. It is OK to block background threads, but it is never OK to block the main thread on a graphic application, as it will freeze the user interface. If you do not know what blocking means, please use the option #1.

class BookGetter {

    static let instance = BookGetter()

    func getBook(bookId: String) -> Book {

        let rootUrl = "https://www.someusefulbookapi.com/bookid=?"
        let url = rootUrl + bookId
        var title = ""

        let semaphore = dispatch_semaphore_create(0)    

        Alamofire.request(.GET, url).response { response in

            let jsonDict = JSON(data: response.2!)

            title = String(jsonDict["items"][0]["volumeInfo"]["title"])
            dispatch_semaphore_signal(semaphore)
        }

        //Wait for the request to complete
        while dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW) != 0 {
            NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate(timeIntervalSinceNow: 10))
        }

        let book = Book(title: title)
        print(book.title)
        return book
    }
}
查看更多
Deceive 欺骗
3楼-- · 2019-01-29 05:31

You can use closures and return a completionHandler with your book like in the following way:

func getBook(bookId: String, completionHandler: (book: Book?) -> ()) {

    let rootUrl = "https://www.someusefulbookapi.com/bookid=?"
    let url = rootUrl + bookId

    Alamofire.request(.GET, url).response { response in completionHandler(
       book: 
       {
           // In this block you create the Book object or returns nil in case of error
           if response == nil {
               return nil
           }

           let jsonDict = JSON(data: response.2!)
           let title = String(jsonDict["items"][0]["volumeInfo"]["title"])
           let book = Book(title: title)

           return book
       }())
    }
}

And then you can call it like in the following way:

getBook("idOfYourBook") { book in
     if let book = book {
        println(book.title)
     }
}

I hope this help you.

查看更多
登录 后发表回答