Uploading multiple images with Alamofire causes huge memory usage - ios

I am working on an app that we need to upload multiple images to our server using Alamofire. The problem is converting UIImage to Data then appending it to Multipart uses so much memory and eventually causes app to crash.
Alamofire.upload(multipartFormData: { (multipart) in
for (key, value) in endpoint.params {
if let images = value as? [UIImage] {
for i in 0..<images.count {
if let data = UIImageJPEGRepresentation(images[i], 0.6) {
multipart.append(data, withName: "\(key)\(i)", fileName: "note.jpg", mimeType: "image/jpeg")
}
}
}
}
}, usingThreshold: UInt64.init(),
to: endpoint.path,
method: endpoint.method,
headers: endpoint.headers,
encodingCompletion: {...}
Some people advised using a stream but couldn't find an example. How can I fix the issue?

Related

Sending Custom Object as form-data in Alamofire

I have an endpoint in Java to upload a file on my server. It accepts MultipartFile and an Info object in form-data.
#PostMapping(path = "/mobile/upload", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
public HttpEntity<String> fileUpload(#RequestPart Info fileInfo, #RequestPart MultipartFile file, #RequestHeader("x-auth") String token){}
Using Alamofire my first approach was this but I was getting 415 error, probably because the way I was sending fileInfo was not correct. I made sure of this by commenting the Info object from server side and then tried sending the file alone and it was a success.
func uploadDocument(fileUrl: URL, fileInfo: Info, authKey: String) {
let headers: HTTPHeaders = [ "x-auth": authKey ]
AF.upload(multipartFormData: { multipartFormData in
multipartFormData.append(fileUrl, withName: "file")
multipartFormData.append(fileInfo, withName: "fileInfo")
}, to: "my_url", method: .post,headers: headers)
.responseDecodable(of: UploadResponse.self) { response in }
}
Then I tried encoding my fileInfo object, again 415 error code.
let jsonObj = (try? JSONEncoder().encode(fileInfo))!
//
multipartFormData.append(jsonObj, withName: "fileInfo")
Also tried but getting exception 'NSInvalidArgumentException'
multipartFormData.append(((fileInfo as AnyObject).data(using: String.Encoding.utf8.rawValue))!, withName: "fileInfo")
How can I send this object in Alamofire ?
I am able to perform the request in Postman with 200.

Alamofire.uploadmultipartFormData with JSONEncoding.default (Swift)

i have to send photo and json to server.
my json is :
{"anticorona":"Anti_Covid","time":"Time","navigateds":[{"collection_public_key":"Origin_Station.collection_public_key","station_public_key":"Origin_Station.public_key"},{"station_public_key":"Des_Station.public_key","collection_public_key":"Des_Station.collection_public_key"}],"seats":"Seats","date":"Date"}
how can i send this json with Alamofire.uploadmultipartFormData
i know i can use encoding: JSONEncoding.default in Alamofire.request but can in use JSONEncoding.default when use Alamofire.uploadmultipartFormData?
thanks
You seem to be new and it would be nice if you could perhaps add some code you have tried in future questions of yours. Anyways, as far as I can see this should be possible in the following way. I am assuming the key "navigateds" remains the same. Otherwise you could also check if the value (in the for-in-loop) is an array, too:
// set parameters for request
let antiCoronaParameters: Parameters = [
"anticorona" : "Anti_Covid",
"time":"Time",
"navigateds":[
["collection_public_key":"Origin_Station.collection_public_key", "station_public_key":"Origin_Station.public_key"],
["station_public_key":"Des_Station.public_key", "collection_public_key":"Des_Station.collection_public_key"]
],
"seats":"Seats",
"date":"Date"
]
let upload = AF.upload(multipartFormData: { (formData) in
// I would append file data here first
for (key, value) in antiCoronaParameters {
if key == "navigateds" {
do {
let arrayData = try JSONSerialization.data(withJSONObject: value, options: .prettyPrinted)
formData.append(arrayData, withName: key)
} catch {
print("could not append array, failed with error:", error)
}
} else if let string = value as? String, let stringData = string.data(using: String.Encoding.utf8, allowLossyConversion: false) {
formData.append(stringData, withName: key)
} else {
print("could not append some data in parameters")
}
}
}, to: "https://www.yourURLhere.com/link.php", method: .post).validate()
upload.responseString { (responseString) in
print(responseString)
}
This answer is based on this question. In the future, I would recommend trying lots of keywords relating to your question. Initially, searching for questions will consume more time but you will get the hang of it soon enough I'm sure. Just keep trying a little more next time maybe ;)
However, as you already mentioned there's another way, too, and I prepared it for you if you would like to check it out. You essentially mentioned it already and I'd always prefer it over the upload unless there's a very good reason:
// set parameters for request
let antiCoronaParameters: Parameters = [
"anticorona" : "Anti_Covid",
"time":"Time",
"navigateds":[
["collection_public_key":"Origin_Station.collection_public_key", "station_public_key":"Origin_Station.public_key"],
["station_public_key":"Des_Station.public_key", "collection_public_key":"Des_Station.collection_public_key"]
],
"seats":"Seats",
"date":"Date"
]
// request with json encoded parameters (e.g. sending to php)
let antiCoronaRequest = AF.request("https://www.yourURLhere.com/link.php", method: .post, parameters: antiCoronaParameters, encoding: JSONEncoding.default).validate()
antiCoronaRequest.responseString(completionHandler: { (response) in
print(response)
})
Hit me up if you have any questions.

iOS Swift uploading PDF file with Alamofire (Multipart)

I'm currently developing an application using iOS 10 and Swift 3 and Alamofire 4
The purpose of this application is to upload a PDF file generated previously.
The PDF generation is working perfectly and the file is created.
However the upload doesn’t work…
I received a success response but the file is not uploaded.
My server response
Multi part Content-Type => multipart/form-data; boundary=alamofire.boundary.56958be35bdb49cb
Multi part Content-Length => 293107
Multi part Content-Boundary => alamofire.boundary.56958be35bdb49cb
responses
SUCCESS: {
uploadedFiles = (
{
details = " Key=Content-Disposition - values=[form-data; name=\"pdfDocuments\"] length=8";
storedFileName = "/var/www/pdf/17/009/22/TMP104150531290406.tmp";
type = PDF;
uploadedDate = 1483999296701;
uploadedFileName = UnknownFile;
}
);
}
end responses
I’m using multi-part to upload my file as Data as you can see here
File url is fine.
I have searched on SO but didn’t find any solution working…
Here you can see my Controller
Alamofire.upload(
multipartFormData: {
multipartFormData in
if let urlString = urlBase2 {
let pdfData = try! Data(contentsOf: urlString.asURL())
var data : Data = pdfData
multipartFormData.append(data as Data, withName:"test.pdf", mimeType:"application/pdf")
for (key, value) in body {
multipartFormData.append(((value as? String)?.data(using: .utf8))!, withName: key)
}
print("Multi part Content -Type")
print(multipartFormData.contentType)
print("Multi part FIN ")
print("Multi part Content-Length")
print(multipartFormData.contentLength)
print("Multi part Content-Boundary")
print(multipartFormData.boundary)
}
},
to: url,
method: .post,
headers: header,
encodingCompletion: { encodingResult in
switch encodingResult {
case .success(let upload, _, _):
upload.responseJSON { response in
print(" responses ")
print(response)
print("end responses")
onCompletion(true, "Something bad happen...", 200)
}
case .failure(let encodingError):
print(encodingError)
onCompletion(false, "Something bad happen...", 200)
}
})
Thanks in advance for the help.
Regards
I have just found my solution to fix this bug.
I have forgot a parameter for the file name.
multipartFormData.append(pdfData, withName: "pdfDocuments", fileName: namePDF, mimeType:"application/pdf")
Thanks for the help.

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.

Alamofire MultiPartForm files in NSTemporaryDirectory

I have not been able to find answer to my question anywhere so I figured I've ask.
I am using Alamofire 3.1.5 for uploading rather large volume of pictures, we are talking in hundreds of MB.
There is a code snippet:
self.manager.upload(.POST, url, headers: headers, multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(fileURL: generalURL, name: "general", fileName: "general", mimeType: "image/jpeg")
multipartFormData.appendBodyPart(fileURL: img1URL, name: "img1", fileName: "img1", mimeType: "image/jpeg")
multipartFormData.appendBodyPart(fileURL: img2URL, name: "img2", fileName: "img2", mimeType: "image/jpeg")
multipartFormData.appendBodyPart(fileURL: img3URL, name: "img3", fileName: "img3", mimeType: "image/jpeg")
}, encodingCompletion: { encodingResult in
.
.
.
As I understand Alamofire handles creating those request by saving them to disk, for better RAM optimalization. Which is smart and I am really happy about it. It just work flawless.
On the other hand that means that it is basically doubling the data payload on disk.
The things is that those files a are not getting deleted, It even causes iOS default screen warning that the device is running low on free space.
I know how to delete content of this directory, but in my current code flow it is safe to delete the content after all the request are finished, it may be even 100 requests, and each one of them takes roughly 20MB of payload. So the thing is that the device might not even have the capacity of storing this amount of data.
My question is:
Can I make Alamofire to delete every single one of these files after it gets successfully uploaded?
Sorry for rather long question, I would post you a potato here, but this is not 9gag.
According to this this issue, you will need to delete it yourself.
It's simple, just delete all files Alamofire generated after you get a response from server. Here's how I did it:
// Just some upload
Alamofire.upload(
.POST, uploadURL,
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(fileURL: somePath, name: "file")
},
encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.responseJSON { response in
if let JSON = response.result.value {
/*** delete temp files Alamofire generated ***/
let temporaryDirectoryPath = NSTemporaryDirectory()
let dir = NSURL(fileURLWithPath: temporaryDirectoryPath, isDirectory: true)
.URLByAppendingPathComponent("com.alamofire.manager")
.URLByAppendingPathComponent("multipart.form.data")
do {
try NSFileManager.defaultManager().removeItemAtPath(dir.path!)
} catch {}
}
}
}
)

Resources