Capturing data from Alamofire - ios

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?

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:
Update your getBook method to be asynchronous and return the result with a completion block/callback
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
}
}

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.

Related

Closure returning data before async work is done

UPDATED WITH PROPOSED SOLUTION AND ADDITIONAL QUESTION
I'm officially stuck and also in callback hell. I have a call to Firebase retrieving all articles in the FireStore. Inside each article object is a an Image filename that translates into a storage reference location that needs to be passed to a function to get the absolute URL back. I'd store the URL in the data, but it could change. The problem is the ArticleListener function is prematurely returning the closure (returnArray) without all the data and I can't figure out what I'm missing. This was working fine before I added the self.getURL code, but now it's returning the array back empty and then doing all the work.
If anyone has some bonus tips here on chaining the methods together without resorting to PromiseKit or GCD that would be great, but open to all suggestions to get this to work as is
and/or refactoring for more efficiency / readability!
Proposed Solution with GCD and updated example
This is calling the Author init after the Article is being created. I am trying to transform the dataDict dictionary so it get's used during the Author init for key ["author"]. I think I'm close, but not 100% sure if my GCD enter/leave calls are happening in the right order
public func SetupArticleListener(completion: #escaping ([Article]) -> Void) {
var returnArray = [Article]()
let db = FIRdb.articles.reference()
let listener = db.addSnapshotListener() { (querySnapshot, error) in
returnArray = [] // nil this out every time
if let error = error {
print("Error in setting up snapshot listener - \(error)")
} else {
let fireStoreDispatchGrp = DispatchGroup() /// 1
querySnapshot?.documents.forEach {
var dataDict = $0.data() //mutable copy of the dictionary data
let id = $0.documentID
//NEW EXAMPLE WITH ADDITIONAL TASK HERE
if let author = $0.data()["author"] as? DocumentReference {
author.getDocument() {(authorSnapshot, error) in
fireStoreDispatchGrp.enter() //1
if let error = error {
print("Error getting Author from snapshot inside Article getDocumentFunction - leaving dispatch group and returning early")
fireStoreDispatchGrp.leave()
return
}
if let newAuthor = authorSnapshot.flatMap(Author.init) {
print("Able to build new author \(newAuthor)")
dataDict["author"] = newAuthor
dataDict["authorId"] = authorSnapshot?.documentID
print("Data Dict successfully mutated \(dataDict)")
}
fireStoreDispatchGrp.leave() //2
}
}
///END OF NEW EXAMPLE
if let imageURL = $0.data()["image"] as? String {
let reference = FIRStorage.articles.referenceForFile(filename: imageURL)
fireStoreDispatchGrp.enter() /// 2
self.getURL(reference: reference){ result in
switch result {
case .success(let url) :
dataDict["image"] = url.absoluteString
case .failure(let error):
print("Error getting URL for author: \n Error: \(error) \n forReference: \(reference) \n forArticleID: \(id)")
}
if let newArticle = Article(id: id, dictionary: dataDict) {
returnArray.append(newArticle)
}
fireStoreDispatchGrp.leave() ///3
}
}
}
//Completion block
print("Exiting dispatchGroup all data should be setup correctly")
fireStoreDispatchGrp.notify(queue: .main) { ///4
completion(returnArray)
}
}
}
updateListeners(for: listener)
}
Original Code
Calling Setup Code
self.manager.SetupArticleListener() { [weak self] articles in
print("🌈🌈🌈🌈🌈🌈🌈In closure function to update articles🌈🌈🌈🌈🌈🌈🌈")
self?.articles = articles
}
Article Listener
public func SetupArticleListener(completion: #escaping ([Article]) -> Void) {
var returnArray = [Article]()
let db = FIRdb.articles.reference()
let listener = db.addSnapshotListener() { (querySnapshot, error) in
returnArray = [] // nil this out every time
if let error = error {
printLog("Error retrieving documents while adding snapshotlistener, Error: \(error.localizedDescription)")
} else {
querySnapshot?.documents.forEach {
var dataDict = $0.data() //mutable copy of the dictionary data
let id = $0.documentID
if let imageURL = $0.data()["image"] as? String {
let reference = FIRStorage.articles.referenceForFile(filename: imageURL)
self.getURL(reference: reference){ result in
switch result {
case .success(let url) :
print("Success in getting url from reference \(url)")
dataDict["image"] = url.absoluteString
print("Dictionary XFORM")
case .failure(let error):
print("Error retrieving URL from reference \(error)")
}
if let newArticle = Article(id: id, dictionary: dataDict) {
printLog("Success in creating Article with xformed url")
returnArray.append(newArticle)
}
}
}
}
print("🌈🌈🌈🌈🌈🌈🌈 sending back completion array \(returnArray)🌈🌈🌈🌈🌈🌈🌈")
completion(returnArray)
}
}
updateListeners(for: listener)
}
GetURL
private func getURL(reference: StorageReference, _ result: #escaping (Result<URL, Error>) -> Void) {
reference.downloadURL() { (url, error) in
if let url = url {
result(.success(url))
} else {
if let error = error {
print("error")
result(.failure(error))
}
}
}
}
You need dispatch group as the for loop contains multiple asynchronous calls
public func SetupArticleListener(completion: #escaping ([Article]) -> Void) {
var returnArray = [Article]()
let db = FIRdb.articles.reference()
let listener = db.addSnapshotListener() { (querySnapshot, error) in
returnArray = [] // nil this out every time
if let error = error {
printLog("Error retrieving documents while adding snapshotlistener, Error: \(error.localizedDescription)")
} else {
let g = DispatchGroup() /// 1
querySnapshot?.documents.forEach {
var dataDict = $0.data() //mutable copy of the dictionary data
let id = $0.documentID
if let imageURL = $0.data()["image"] as? String {
let reference = FIRStorage.articles.referenceForFile(filename: imageURL)
g.enter() /// 2
self.getURL(reference: reference){ result in
switch result {
case .success(let url) :
print("Success in getting url from reference \(url)")
dataDict["image"] = url.absoluteString
print("Dictionary XFORM")
case .failure(let error):
print("Error retrieving URL from reference \(error)")
}
if let newArticle = Article(id: id, dictionary: dataDict) {
printLog("Success in creating Article with xformed url")
returnArray.append(newArticle)
}
g.leave() /// 3
}
}
}
g.notify(queue:.main) { /// 4
print("🌈🌈🌈🌈🌈🌈🌈 sending back completion array \(returnArray)🌈🌈🌈🌈🌈🌈🌈")
completion(returnArray)
}
}
}
updateListeners(for: listener)
}

How to wait until alamofire image request is done

Hello I have been trying alot and looking around on SO, but I cant find a good solution to my problem.
The problem is that i do 2 requests within one function and the first one finishes, then it does updateUI function on the main thread instead of waiting for the second download to finish.
I know that i am doing something wrong but in my mind the two requests work on different threads and that is why updateUI only will trigger after first download is complete.
Is it possible to use dispatchqueue? I dont know how completionhandlers work either sadly..
I couldnt see what i have to use from their github page either, im quite new at Swift.
Please do not set this as duplicate. would really appreciate it
func getMovieData(){
self.posterLoading.startAnimating()
//Set up URL
let testCall: String = "https://api.themoviedb.org/3/discover/movie?api_key=935f5ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=12"
Alamofire.request(testCall).responseJSON { response in
//print(response.request) // original URL request
//print(response.response) // HTTP URL response
//print(response.data) // server data
//print(response.result) // result of response serialization
if let json = response.result.value as? Dictionary<String,AnyObject> {
if let movies = json["results"] as? [AnyObject]{
for movie in movies{
let movieObject: Movie = Movie()
let title = movie["title"] as! String
let releaseDate = movie["release_date"] as! String
let posterPath = movie["poster_path"] as! String
let overView = movie["overview"] as! String
let movieId = movie["id"] as! Int
let genre_ids = movie["genre_ids"] as! [AnyObject]
movieObject.title = title
movieObject.movieRelease = releaseDate
movieObject.posterPath = posterPath
movieObject.overView = overView
movieObject.movieId = movieId
for genre in genre_ids{//Genre ids, fix this
movieObject.movieGenre.append(genre as! Int)
}
Alamofire.request("http://image.tmdb.org/t/p/w1920" + posterPath).responseImage {
response in
debugPrint(response)
//print(response.request)
//print(response.response)
//debugPrint(response.result)
if var image = response.result.value {
image = UIImage(data: response.data!)!
movieObject.poster = image
}
}
self.movieArray.append(movieObject)
print("movie added")
}//End of for each movie
DispatchQueue.main.async(){
print("is ready for UI")
self.updateUI()
}
}
}
}//End of Json request
}//End of getmoviedata
func updateUI(){
uiMovieTitle.text = movieArray[movieIndex].title
uiMoviePoster.image = movieArray[movieIndex].poster
}
Just make your getMovieData func with a completion block.
func getMovieData(finished: () -> Void) {
Alamofire.request(testCall).responseJSON { response in
// call me on success or failure
finished()
}
}
and then you can call your update UI in the completion block of the func, where you calling it the second time getMovieData()
Your function should look like this.
func getMovieData(completionHandler: #escaping (_ returnedData: Dictionary<String,AnyObject>)-> Void ) {
Alamofire.request(testCall).response { response in
if let JSON = response.result.value {
completionHandler(JSON)
}
}
}
And your function call look like
getMovieData(completionHandler: {(returnedData)-> Void in
//Do whatever you want with your returnedData JSON data.
//when you finish working on data you can update UI
updateUI()
})
You don't have to do your data operations in your function call btw.You can do your stuff in your function and call the completionHandler() end of it. Then you can update your ui only at function call.

Alamofire request gives memory warning

I am using a Master Detail Application. Master Screen is a Dashboard and on selecting an item, moves to the detailed screen where I trigger an Alamofire request in the backend
Below is the snippet
class APIManager: NSObject {
class var sharedManager: APIManager {
return _sharedManager
}
private var requests = [Request]()
// Cancel any ongoing download
func cancelRequests() {
if requests.count > 0 {
for request in requests {
request.cancel()
}
}
}
func getData(completion: (dataSet: [Data]?, error: NSError?) -> Void) {
let request = Alamofire.request(.GET, "http://request")
.response { (request, response, data, error) in
dispatch_async(dispatch_get_main_queue(), {
if(error == nil) {
if let response = data, data = (try? NSJSONSerialization.JSONObjectWithData(response, options: [])) as? [NSDictionary] {
var dataSet = [Data]()
for (_, dictionary) in data.enumerate() {
let lat = dictionary["Latitude"]
let lng = dictionary["Longitude"]
let id = dictionary["ID"] as! Int
let data = Data(lat: lat!, long: lng!, id: shuttleID)
dataSet.append(data)
}
completion(dataSet: dataSet, error: nil)
}
} else { completion(dataSet: nil, error: error) }
})
}
requests.append(request)
}
}
I have a singleton API manager class and from the detail view controller I call getData() function. Everything works fine.
But, when I push and pop repeatedly, I see rapid increase in the memory and after 10-15 attempts, I get memory warning. However in the AppDelegate I am managing it to show an Alert message and adding a delay timer for 8 seconds. But however after 20-25 attempts app crashes due to memory warning.
In viewWillDisappear(), I cancel any ongoing requests also. But I couldn't able to stop memory warning issue. I commented the part where I call the request, I see no issues, even memory consumption is less.
I welcome ideas.
The problem is you are never removing the requests that you append to the member variable 'requests'.
You will need to ensure to remove the request when you either cancel it or when the request completes successfully.
Do the following modifications-
func cancelRequests() {
if requests.count > 0 {
for request in requests {
request.cancel()
}
}
requests.removeAll() //Delete all canseled requests
}
also
func getData(completion: (dataSet: [Data]?, error: NSError?) -> Void) {
let request = Alamofire.request(.GET, "http://request")
.response { (request, response, data, error) in
dispatch_async(dispatch_get_main_queue(), {
if(error == nil) {
if let response = data, data = (try? NSJSONSerialization.JSONObjectWithData(response, options: [])) as? [NSDictionary] {
var dataSet = [Data]()
for (_, dictionary) in data.enumerate() {
let lat = dictionary["Latitude"]
let lng = dictionary["Longitude"]
let id = dictionary["ID"] as! Int
let data = Data(lat: lat!, long: lng!, id: shuttleID)
dataSet.append(data)
}
requests.removeObject(request)
completion(dataSet: dataSet, error: nil)
}
} else {
requests.removeObject(request)
completion(dataSet: nil, error: error) }
})
}
requests.append(request)
}
Add this Handy extension on Array to remove item to your code:
// Swift 2 Array Extension
extension Array where Element: Equatable {
mutating func removeObject(object: Element) {
if let index = self.indexOf(object) {
self.removeAtIndex(index)
}
}
mutating func removeObjectsInArray(array: [Element]) {
for object in array {
self.removeObject(object)
}
}
}
On analysis, I found that the memory warning was not due to the Alamofire request. It was due to MKMapView. Loading a MKMapView, zooming in and zooming out consumes more memory. So, in viewWillDisappear I did the fix.
override func viewWillDisappear(animated:Bool){
super.viewWillDisappear(animated)
self.applyMapViewMemoryFix()
}
func applyMapViewMemoryFix(){
switch (self.mapView.mapType) {
case MKMapType.Hybrid:
self.mapView.mapType = MKMapType.Standard
break;
case MKMapType.Standard:
self.mapView.mapType = MKMapType.Hybrid
break;
default:
break;
}
self.mapView.showsUserLocation = false
self.mapView.delegate = nil
self.mapView.removeFromSuperview()
self.mapView = nil
}
Courtesy - Stop iOS 7 MKMapView from leaking memory

iOS - Why reloadData tableView data on first application load?

I am working on a simple Flickr app that gets some data from their API and displays it on a tableview instance. Here's a piece of the code for the TableViewController subclass.
var photos = [FlickrPhotoModel]()
override func viewDidLoad() {
super.viewDidLoad()
getFlickrPhotos()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
private func getFlickrPhotos() {
DataProvider.fetchFlickrPhotos { (error: NSError?, data: [FlickrPhotoModel]?) in
//data is received
dispatch_async(dispatch_get_main_queue(), {
if error == nil {
self.photos = data!
self.tableView.reloadData()
}
})
}
}
The application does not seem to load the data if the { tableView.reloadData() } line is removed. Does anyone know why this would happen since I call getFlickrPhotos() within viewDidLoad(). I believe I am also dispatching from the background thread in the appropriate place. Please let me know what I am doing incorrectly.
EDIT -- Data Provider code
class func fetchFlickrPhotos(onCompletion: FlickrResponse) {
let url: NSURL = NSURL(string: "https://api.flickr.com/services/rest/?method=flickr.photos.getRecent&api_key=\(Keys.apikey)&per_page=25&format=json&nojsoncallback=1")!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
if error != nil {
print("Error occured trying to fetch photos")
onCompletion(error, nil)
return
}
do {
let jsonResults = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
let photosContainer = jsonResults!["photos"] as? NSDictionary
let photoArray = photosContainer!["photo"] as? [NSDictionary]
let flickrPhoto: [FlickrPhotoModel] = photoArray!.map{
photo in
let id = photo["id"] as? String ?? ""
let farm = photo["farm"] as? Int ?? 0
let secret = photo["secret"] as? String ?? ""
let server = photo["server"] as? String ?? ""
var title = photo["title"] as? String ?? "No title available"
if title == "" {
title = "No title available"
}
let model = FlickrPhotoModel(id: id, farm: farm, server: server, secret: secret, title: title)
return model
}
//the request was successful and flickrPhoto contains the data
onCompletion(nil, flickrPhoto)
} catch let conversionError as NSError {
print("Error parsing json results")
onCompletion(conversionError, nil)
}
}
task.resume()
}
I'm not familiar with that API, but it looks like the fetchFlickrPhotos method is called asynchronously on a background thread. That means that the rest of the application will not wait for it to finish before moving on. viewDidLoad will call the method, but then move on without waiting for it to finish.
The completion handler that you provide is called after the photos are done downloading which, depending on the number and size of the photos, could be seconds later. So reloadData is necessary to refresh the table view after the photos are actually done downloading.

iOS background download

I have an application that requires downloading large amount of data when the user logs in. I wanted to move the download portion of it to a background thread so the user can navigate the app without having to wait for the download to complete. I have tried the following methods but some of them still locks the app so user cant click on anything,
dispatch_async(dispatch_get_main_queue(), ^{
});
Have also tried
[self performSelectorInBackground:#selector(loadDataThatToBeFetchedInThread:)
withObject:objectArrayThatNeedToFetchData];
this one seems to just stop if I move between activity. Have tried moving it to the AppDelegate method but when I try to save to SQlite DB i get some error. Am i doing something wrong? Can some one please help.
Thanks in advance
Well, dispatch_get_main_queue() is going to give you the main thread, so that's probably not what you want.
Instead, you should obtain a background queue using:
dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ ... });
And then, it's customary to either send out some notification, or even call back to the main thread directly to (in the UI) report success:
dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Do the download...
// Download finishes...
dispatch_async(dispatch_get_main_queue(), ^{
// Call a UI-updating method, or similar
});
});
Look up NSURLSession and NSURLSessionDownloadTask. This is the latest and greatest from Apple.
Watch the Core Networking videos (What's New in Core Networking) from the 2015 WWDC videos and 2014 WWDC videos.
URL Session Programming Guide is also a good resource.
NSURLSession is asynchronous out of the box β€” which is what you're looking for.
As a bonus NSURLSessionDownloadTask makes it easy to continue the download when you app changes to background state (which is much different than a background thread). It also allows you to easily cancel and/or resume a download.
I'd recommend using NSOperation and NSOperationQueue to keep it nice and clean.
Read & Watch more:
NSOperation reference
NSOperationQueue reference
Advanced NSOperations by Dave DeLong, WWDC 2015
Here's a basic setup that you can customise to fit your needs
Disclaimer: although it seems like a lot, it makes up for a nicer API.
First, let's define an interface to handle our API endpoints:
// Endpoints.swift
let api_base = "https://myserver.com/"
let api_path = "api/"
protocol EndpointGenerator {
func URL() -> NSURL
}
extension EndpointGenerator {
func URL() -> NSURL {
return NSURL(string: api_base)!
}
}
// Represents a null endpoint. It will fail.
struct NullEndpoint: EndpointGenerator { }
enum Endpoint: String, EndpointGenerator {
case Login = "login"
case SignUp = "signup"
func URL() -> NSURL {
return NSURL(string: api_base + api_path + self.rawValue)!
}
}
Next, let's build our custom NSOperation:
// Operation.swift
public class Operation: NSOperation {
public typealias Completion = Operation -> ()
public typealias Error = NSError -> ()
var endpoint: EndpointGenerator {
return NullEndpoint()
}
var headerParams: [String:String]? {
return nil
}
var requestBody: [String:AnyObject]? {
return nil
}
var method: HTTPMethod {
return .GET
}
var networkTask: NSURLSessionTask?
var completion: Completion?
var error: Error?
public var parsedObject = [String:AnyObject]()
override public init() { }
public init(completion: Completion, error: Error) {
self.completion = completion
self.error = error
}
override public func start() {
NSURLSessionImplementaion.execute(self)
}
override public func cancel() {
networkTask?.cancel()
networkTask = nil
}
}
To be almost done, let's handle the actual queue:
// OperationQueue.swift
public class OperationQueue: NSOperationQueue {
public static let internalQueue = OperationQueue()
public static func addOperation(operation: NSOperation) {
internalQueue.addOperation(operation)
}
public static func addOperations(operations: NSOperation...) {
for operation in operations {
addOperation(operation)
}
}
public static func cancellAllOperations() {
internalQueue.cancelAllOperations()
}
}
Finally, the download part:
// NSURLSessionImplementation.swift
enum HTTPMethod: String {
case POST = "POST"
case GET = "GET"
case PATCH = "PATCH"
}
public let OSNetworkingErrorDomain = "com.swanros.errordomain"
class NSURLSessionImplementaion {
class func execute(operation: Operation) {
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let request = NSMutableURLRequest(URL: operation.endpoint.URL())
if let headerParams = operation.headerParams {
for element in headerParams {
request.setValue(element.1, forHTTPHeaderField: element.0)
}
}
if let body = operation.requestBody {
do {
request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(body, options: .PrettyPrinted)
} catch {
return
}
}
request.HTTPMethod = operation.method.rawValue
let task = session.dataTaskWithRequest(request) { data, response, error in
if let e = error {
operation.error?(e)
return
}
guard let d = data else {
operation.error?(errorWithDescription("No data"))
return
}
do {
let json = try NSJSONSerialization.JSONObjectWithData(d, options: .MutableLeaves) as? [String:AnyObject]
guard let j = json else {
operation.error?(errorWithDescription("Error parsing JSON."))
return
}
if let errorMessage = string(j, key: "error") {
operation.error?(errorWithDescription(errorMessage))
return
}
operation.parsedObject = j
operation.completion?(operation)
} catch let jsonError as NSError {
operation.error?(jsonError)
}
}
operation.networkTask = task
task.resume()
}
}
func errorWithDescription(desc: String) -> NSError {
return NSError(domain: OSNetworkingErrorDomain, code: 0, userInfo: [NSLocalizedDescriptionKey:desc])
}
How do you implement this? Say you want to hit the /login endpoint. Subclass Operation as follows:
// LogInOperation.swift
public class LogInOperation: Operation {
override var endpoint: EndpointGenerator {
// A nice way to represent endpoints: use enums and protocols!
return Endpoint.Login
}
// The headers for this particular request. Maybe you need a token here!
override var headerParams: [String:String]? {
return [
"Content-Type": "application/json",
"Application-Id": "bAAvLosWNeSTHrlYilysdeEYoJHUXs88"
]
}
// The HTTP request body!
override var requestBody: [String:AnyObject]? {
return [
"mail": mail,
"password": password
]
}
// .GET is default
override var method: HTTPMethod {
return .POST
}
private var mail: String
private var password: String
public init(mail m: String, password p: String, completion: Completion, error: Error) {
mail = m
password = p
super.init(completion: completion, error: error)
}
}
And you use it like this:
// ViewController.swift
let loginOperation = LogInOperation(
mail: "mail#example.com",
password: "123123",
completion: { op in
// parsedObject would be the user's info
print(op.parsedObject?)
}, error: { error in
print(error.localizedDescription)
}
)
OperationQueue.addOperation(loginOperation)

Resources