Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
2.2k views
in Technique[技术] by (71.8m points)

ios - Capturing data from Alamofire

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...