Deadlock inside NSURLSession delegate queue - ios

I'm experiencing a deadlock inside one of the operations in the NSUrlSession delegate queue when using Alamofire.
it happens when i'm doing at least one download and one upload simultaneously (all requests are done through the default Alamofire manager). Is there any problem doing so from multiple threads? (either in NSUrlSession or Alamofire)
it seems to be stuck on __psynch_mutexwait in one of the operations in the NSURLSession delegate queue, and it completely shuts down the app's ability to make network requests through Alamofire (because the delegate won't be called ever).
as I said the download and upload called simultaneously on 2 different queues (one of them is usually called on the main thread)
upload example :
Alamofire.upload(.POST, uploadURL,
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: x.dataUsingEncoding(NSUTF8StringEncoding)!, name: "X")
multipartFormData.appendBodyPart(data: fileData, name: "file", fileName: "Y", mimeType: "application/octet-stream")
}
},
encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.response { (request, response, data, error) -> Void in
if let error = error {
callback("Failure", "\(error)")
} else {
callback("SUCCESS", nil)
}
}
case .Failure(let encodingError):
callback(nil, "Failed due to \(encodingError)")
}
}
)
download example :
Alamofire.download(.GET, downloadUrl, parameters: ["a": "a", "b": "b"], destination:
{
tempURL, response in
return path
}).response {
(request, response, _, error) in
let data = NSData(contentsOfURL: path)
doSomeStuffWithDownloadedData(data)
// make another request after download completed
Alamofire.request(.GET, requestUrl, parameters: ["c":"c", "d":"d"]).response {
request, response, data, error in
if let e = error {
log.error("request failed, \(e)")
}
}
}
stack trace

After commenting most of my code I isolated the code causing the problem and it does not related at all to alamofire or NSURLSession.
I have in my own code a call to objc_sync_enter on an array (of objects), it always has a matching objc_sync_exit call on the same array. after changing this call to be on self instead of this array, the deadlock inside NSBlockOperation is gone. It may be related to the fact that an array is not an object but a struct. So if you experience very strange deadlock in your code, I suggest that before you try anything else, make sure you don't have calls of objc_sync_enter on structs.

Related

iOS Swift + Alamofire upload photos with exif data

I am using Alamofire to upload multiple files at the same time to Open Asset using their REST API and I am able to get this to work, however, most of the EXIF data is being stripped out. Unfortunately, the EXIF data is a must as we need the ability mine out the GPS tags and a few other things through various web clients.
After doing some research, I found the issue is because I'm using UIImageJPEGRepresentation to convert the photos to NSData (which is what Alamofire expects or a fileURL, which I don't think would work for me?).
I am also using the BSImagePicker library to allow the user to take/select multiple photos, which returns a an array of PHAssets which then get converted to NSData. Here is my function to do this (where collectedImages is a global dictionary):
func compressPhotos(assets: [PHAsset]) -> Void {
for asset in assets {
let filename = self.getOriginalFilename(asset)
let assetImage = self.getAssetPhoto(asset)
let compressedImage = UIImageJPEGRepresentation(assetImage, 0.5)! // bye bye metadata :(
collectedImages[filename] = compressedImage
print("compressed image: \(filename)")
}
}
I think I could retain the EXIF data if I could use the full path from the PHAsset to the image locally on the phone, but Alamofire does not appear to support that. I'm hoping I'm wrong about that. Here is my uploader:
func uploadPhotos(projectId: String, categoryId: String, data: [String: NSData], completionHandler: (AnyObject?, NSError?) -> ()) {
var jsonBody = [AnyObject]() //lazy
Alamofire.upload(
.POST,
self.url + "/Files",
multipartFormData: { multipartFormData in
for (filename, img) in data {
jsonBody.append(["project_id": projectId, "category_id": categoryId, "original_filename": filename])
multipartFormData.appendBodyPart(data: img, name: "file", fileName: filename, mimeType: "image/jpeg")
print("img size: \(img.length)")
}
let jsonData = jsonToNSData(jsonBody)
print("_jsonBody: \(jsonBody)")
multipartFormData.appendBodyPart(data: jsonData!, name: "_jsonBody")
print("multipart: \(multipartFormData)")
},
encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.responseJSON { response in
debugPrint(response)
switch response.result {
case .Success(let value):
completionHandler(value as? NSArray, nil)
case .Failure(let error):
completionHandler(nil, error)
}
}
case .Failure(let encodingError):
print(encodingError)
}
}
)
}
So my question is, how can I upload multiple photos (while passing in other parameters too) while maintaining all the EXIF data? Alamofire is an awesome library and I would like to use it here, but I'm not married to it for the upload process if I can't keep the EXIF data.
I think you can first get the file URL from the PHAsset then use that file URL in the call to multipartFormData.appendBodyPart(...). Something like this:
Get URL from PHAsset:
[asset requestContentEditingInputWithOptions:editOptions
completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
NSURL *imageURL = contentEditingInput.fullSizeImageURL;
}];
Use file URL in AlamoFire API:
multipartFormData.appendBodyPart(fileURL: imageURL, name: "image")
I am not sure why I was having issues with the fullSizeImageURL, but I it did lead me to the right path as I was able to get this to work by getting the image as NSData from the file path like this:
asset.requestContentEditingInputWithOptions(PHContentEditingInputRequestOptions()) { (input, _) in
let fileURL = input!.fullSizeImageURL?.filePathURL
let data = NSData(contentsOfFile: fileURL!.path!)!
And then I just passed that in the Alamofire.request() as the data argument. This maintained all the original photo metadata.

Ambiguous reference to member upload alamofire

I am receiving this error when trying to upload a picture to my web server. I just copied and pasted the alamofire sample from github and I receive an error right away. My code is as follows:
let data = UIImageJPEGRepresentation(picOutlet.image, 0.5)
Alamofire.upload(.POST, "phpurlhere", file: photo)
.progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
print(totalBytesWritten)
// This closure is NOT called on the main queue for performance
// reasons. To update your ui, dispatch to the main queue.
dispatch_async(dispatch_get_main_queue()) {
print("Total bytes written on main queue: \(totalBytesWritten)")
}
}
.validate()
.responseJSON { response in
debugPrint(response)
}
UPDATE: I added JPEG representation to pass to alamofire function but still getting same error.
The issue is that the upload function:
Alamofire.upload(.POST, "phpurlhere", file: photo)
is expecting an object of type NSURL for the file: parameter. You are giving it a UIImage.
If your goal is to upload picOutlet.image using the upload function, try the following:
let data = UIImageJPEGRepresentation(picOutlet.image, 0.5)
Alamofire.upload(.POST, "phpurlhere", data: data)
.progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
print(totalBytesWritten)
// This closure is NOT called on the main queue for performance
// reasons. To update your ui, dispatch to the main queue.
dispatch_async(dispatch_get_main_queue()) {
print("Total bytes written on main queue: \(totalBytesWritten)")
}
}
.validate()
.responseJSON { response in
debugPrint(response)
}
try this
Alamofire.upload(photoURL, to: "phpurlhere", method: .post, headers:nil)
.uploadProgress { progress in // main queue by default
progressView.progress = Float(progress.fractionCompleted)
}
.downloadProgress { progress in // main queue by default
print("Download Progress: \(progress.fractionCompleted)")
}
.responseJSON { response in
debugPrint(response)
}

Using Alamofire and multipart/form-data

I'm unable to approach the API that has been offered to me in the proper way for it to give me the response I'm looking for. I've been using Swift and Alamofire for a while but this is the first time I've to upload images using multipart/form-data. I'm able to upload images using Postman but I'm unable to get the same message send out by my application using the Alamofire framework.
My Swift code:
func postFulfilWish(wish_id: Int, picture : UIImage, completionHandler: ((AnyObject?, ErrorType?) -> Void)) {
var urlPostFulfilWish = Constant.apiUrl;
urlPostFulfilWish += "/wishes/";
urlPostFulfilWish += String(wish_id);
urlPostFulfilWish += "/fulfill/images" ;
let image : NSData = UIImagePNGRepresentation(UIImage(named: "location.png")!)!
Alamofire.upload(.POST, urlPostFulfilWish, headers: Constant.headers, multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: image, name: "file")
},
encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.responseJSON { response in
//This is where the code ends up now
//So it's able to encode my message into multipart/form-data but it's not doing it in the correct way for the API to handle it
debugPrint(response)
}
case .Failure(let encodingError):
print(encodingError)
}
}
)
}
In case it is not already answered, recently I had the same problem using Alamofire to upload an Image using form-data.
I was able to upload the image using Postman exactly as it's shown in this post, but no able to do it using Alamofire in my app.
You need to check two things, first of all, the name of the file that the server is expecting, and second the method used to append the body part in the multipartFormData closure.
This two methods were not working in my case -
multipartFormData.appendBodyPart(data: imageData, name: "file")
this one neither
multipartFormData.appendBodyPart(data: imageData, name: "file", fileName: name)
But on this one worked splendid -
multipartFormData.appendBodyPart(data: imageData, name: "file", fileName: "file.jpeg", mimeType: "image/jpeg")
The problem basically is that the server can't find the file with the expected name.
I hope this help someone to save time wondering why it's not working.
You are doing debugPrint(response). You presumably should do another switch response.result { ... } and see if you got .Success or .Failure as the result of the request, and if success, you'd look at the response object contents (or if failure, look at the failure error). You need to look at that result to diagnose whether it was successful or not.
Alamofire.upload(.POST, urlPostFulfilWish, headers: Constant.headers, multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(data: image, name: "file")
}) { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.responseJSON { response in
switch response.result {
case .Success(let value):
print(value)
case .Failure(let error):
print(error)
}
}
case .Failure(let encodingError):
print(encodingError)
}
}
I recently got a 404 from the server when posting a multipart request along with parameters in the body. I was using a UIImagePickerController (the delegate for which returns a UIImage) and I then sent up the PNG representation of it.
This only occurred for files that were JPEG on disk. Strangely this issue seems to only affect multipart requests that also had parameters in the body. It worked fine when the API endpoint didn't require anything else.
My guess is that there is something weird going on along the line of JPEG -> UIImage -> PNG representation that results in some sort of problem which oddly only seems to manifest itself in multipart requests that also have parameters in the body. Might be some special characters in there that makes the server not recognise the request and just return a 404.
I ended up fixing it by sending up the UIImageJPEGRepresentation of the selected image instead of UIImagePNGRepresentation, and no such errors.
I believe the question is outdated already but for as long as there is no answer accepted try the following:
multipartFormData.appendBodyPart(data: imageData, name: "name", fileName: "filename", mimeType: mimeType)

Is there a failed block for Alamofire?

I'm using Alamofire with Swift kind of this way:
Alamofire.request(.GET, urlString)
.authenticate(usingCredential: credential)
.response {
(request, responseJSON, data, error) in [..and so on]
Now I wonder how I can execute some code in case the server is e.g. completely down. Something like a failed block in ObjC.
I know that I can call something like this to get an error code:
if let response = responseJSON {
var statusCode = response.statusCode
println("-->statusCode: \(statusCode)")
}
But in the case that I can't reach the server, the .response closure won't execute, so there is no error message.
How is this handled?
If you can't reach the server, you will receive a NSURLErrorDomain error via the error variable in the closure.

How to have a completion handler/block after Alamofire Post request?

I have a method which handles a Apple Push Notification Service remote notification. When this method is executed, I want it to call my server and do a HTTP POST request using the Alamofire library. I want to execute another method that will handle the response of the POST request.
The problem for me is that I am using an existing API to fetch a profile from the server in this POST request. So I need to use this existing API and figure out when this profile fetch is specifically triggered from the remote notification.
Since Alamofire requests are done in a background queue, how would I go about doing an execution of a method after receiving the profile back from the server?
What would be a good option to solving this issue?
Thank you!
Since Alamofire requests are done in a background queue, how would I go about doing an execution of a method after receiving the profile back from the server?
Response handling is built in to Alamofire. You can do something like this (adapted from the docs):
Alamofire.request(.POST, "http://httpbin.org/get", parameters: ["foo": "bar"])
.response { (request, response, data, error) in
println(request)
println(response)
println(error)
}
Note the .response method call, which adds a completion handler to the request object; the completion handler is invoked by Alamofire when the request completes (or fails).
It wasn't clear from your question formulation what problem you were trying to solve. But you've clarified your intent in the question comments above.
As I understand the problem now, you're got some code that updates a profile on the server and handles the server's response. The code is called in two contexts, one initiated by a manual request from the user, another initiated by a push notification. In the first case, you don't want to generate an alert after you process the response from the server, but in the second case you do.
You do indeed have a closure that you can use to handle the different behavior even though the difference happens in the asynchronous part of the process. Here's a sketch (not actual working code) of how that might look:
func updateProfile(parameters: [String:String], showAlert: Bool) {
Alamofire.request(.POST, "http://myserver.com/profile", parameters: parameters)
.response { (request, response, data, error) in
if (error == nil) {
processProfileResponse(response)
if showAlert {
showProfileWasUpdatedAlert()
}
}
}
}
Note the showAlert parameter passed in to the updateProfile method. If you pass in true, it calls the showProfileWasUpdatedAlert method to show your alert after receiving the server's response. Note that this boolean value is "captured" by the closure that handles the Alamofire response because the closure was defined inside the updateProfile function.
This, IMHO, is a better approach than declaring an app global inside your AppDelegate.
Here you go
func AlamofireRequest(method: Alamofire.Method, URLString: URLStringConvertible, parameters: [String : AnyObject]?, encoding: ParameterEncoding, headers: [String : String]?) -> Alamofire.Result<String>? {
var finishFlag = 0
var AlamofireResult: Alamofire.Result<String>? = nil
Alamofire.request(method, URLString, parameters: parameters, encoding: encoding, headers: headers)
.responseString { (_, _, result) -> Void in
if result.isSuccess {
finishFlag = 1
AlamofireResult = result
}
else {
finishFlag = -1
}
}
while finishFlag == 0 {
NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture())
}
return AlamofireResult
}

Resources