12

I am uploading images on server via Alamofire.upload as multipart data. Unlike Alamofire.request it's not returning Request object, which I usually use to cancel requests.

But it's very reasonable to be able to cancel such a consuming requests like uploading. What are the options for this in Alamofire?

simd
  • 1,779
  • 3
  • 17
  • 23

4 Answers4

17

Using the Uploading MultiPartFormData example from the Alamofire README:

Alamofire.upload(
    .POST,
    "https://httpbin.org/post",
    multipartFormData: { multipartFormData in
        multipartFormData.appendBodyPart(fileURL: unicornImageURL, name: "unicorn")
        multipartFormData.appendBodyPart(fileURL: rainbowImageURL, name: "rainbow")
    },
    encodingCompletion: { encodingResult in
        switch encodingResult {
        case .Success(let upload, _, _):
            upload.responseJSON { response in
                debugPrint(response)
            }
        case .Failure(let encodingError):
            print(encodingError)
        }
    }
)

Here, upload.responseJSON returns a Request, which should allow you to assign it to something for cancellation later. For example:

let request = upload.responseJSON {  ...

...

request.cancel()
Paul Young
  • 1,489
  • 1
  • 15
  • 34
1

I'm afraid you can't, according to the Alamofire source code the upload function returns an Request type in all of its overloads except in these that support MultipartFormData see the following code:

// MARK: MultipartFormData

/**
Creates an upload request using the shared manager instance for the specified method and URL string.

- parameter method:                  The HTTP method.
- parameter URLString:               The URL string.
- parameter headers: The HTTP headers. `nil` by default.
- parameter multipartFormData:       The closure used to append body parts to the `MultipartFormData`.
- parameter encodingMemoryThreshold: The encoding memory threshold in bytes. `MultipartFormDataEncodingMemoryThreshold` 
                                by default.
- parameter encodingCompletion:      The closure called when the `MultipartFormData` encoding is complete.
*/
public func upload(
  method: Method,
  URLString: URLStringConvertible,
  headers: [String: String]? = nil,
  multipartFormData: MultipartFormData -> Void,
  encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold,
  encodingCompletion: (Manager.MultipartFormDataEncodingResult -> Void)?)
{
   return Manager.sharedInstance.upload(
      method,
      URLString,
      headers: headers,
      multipartFormData: multipartFormData,
      encodingMemoryThreshold: encodingMemoryThreshold,
      encodingCompletion: encodingCompletion
   )
}

/**
Creates an upload request using the shared manager instance for the specified method and URL string.

- parameter URLRequest:              The URL request.
- parameter multipartFormData:       The closure used to append body parts to the `MultipartFormData`.
- parameter encodingMemoryThreshold: The encoding memory threshold in bytes. `MultipartFormDataEncodingMemoryThreshold`
                                by default.
- parameter encodingCompletion:      The closure called when the `MultipartFormData` encoding is complete.
*/
public func upload(
  URLRequest: URLRequestConvertible,
  multipartFormData: MultipartFormData -> Void,
  encodingMemoryThreshold: UInt64 =    Manager.MultipartFormDataEncodingMemoryThreshold,
  encodingCompletion: (Manager.MultipartFormDataEncodingResult -> Void)?)
{
   return Manager.sharedInstance.upload(
      URLRequest,
      multipartFormData: multipartFormData,
      encodingMemoryThreshold: encodingMemoryThreshold,
      encodingCompletion: encodingCompletion
   )
}

It's recommended when you are writing client-side code, use multipart/form-data when your form includes any <input type="file"> elements.

So if you want to just upload an image to the server you can use the another upload function overloads that returns an Request object and you can cancel it like in this ways proposed in the Alamofire documentation:

let fileURL = NSBundle.mainBundle().URLForResource("Default", withExtension: "png")
ler request =Alamofire.upload(.POST, "https://httpbin.org/post", file: fileURL)
// request.cancel()

I hope this help you.

Victor Sigler
  • 23,243
  • 14
  • 88
  • 105
  • Here have 2 problems: 1. the upload always hang up on 2.03M, then the request to be timeout; 2. cannot get the server response data; – Raymond Liao Jan 08 '17 at 06:52
1

It's possible to prepare a closure, and transfer a request out of "encodingCompletion"

class NetworkManager {
    private var closure: ((Request)->())?

    func startUpload() {
        Alamofire.upload(
            .POST,
            "https://httpbin.org/post",
            multipartFormData: { multipartFormData in
                multipartFormData.appendBodyPart(fileURL: unicornImageURL, name: "unicorn")
                multipartFormData.appendBodyPart(fileURL: rainbowImageURL, name: "rainbow")
            },
            encodingCompletion: { encodingResult in
                switch encodingResult {
                case .Success(let upload, _, _):
                    self.closure?(upload)

                    upload.responseJSON { response in
                        debugPrint(response)
                        uploadRequest = nil
                    }
                case .Failure(let encodingError):
                    print(encodingError)
                }
            }
        )
    }
}
Naloiko Eugene
  • 2,453
  • 1
  • 28
  • 18
1

In my case I created "sessionManager" instance in my API class and assigned Alamofire's session manager with configuration to it.

    var sessionManager: SessionManager!

    // Setup request manager
    let configuration = URLSessionConfiguration.default
    configuration.timeoutIntervalForResource = TimeInterval(15.0)
    configuration.timeoutIntervalForRequest = TimeInterval(15.0)
    sessionManager = Alamofire.SessionManager(configuration: configuration)


    sessionManager.upload(multipartFormData: { multipartFormData in

        // AppendMultipart parts
        multipartFormData.append(metadataBodyPart, withName: PartName.Metadata, mimeType: MimeType.MultiPart)
        multipartFormData.append(imageDataBodyPart, withName: PartName.Photo, mimeType: MimeType.ImageJPG)

    } // ... rest of the code

Then I could create a method to cancel any current request type. Note that here "(_, uploadTasks, _)" you can also have "dataTasks" & "downloadTasks" which you can also cancel if you want "(dataTasks, uploadTasks, downloadTasks)"

func cancelUploadRequest() {
    sessionManager.session.getTasksWithCompletionHandler { (_, uploadTasks, _) in
        uploadTasks.forEach { $0.cancel() }
    }
}

You can also have smth like this:

func cancel(request: CancelRequestType) {
    sessionManager.session.getTasksWithCompletionHandler { (dataTasks, uploadTasks, downloadTasks) in
        switch request {
            case .DataTask:
                dataTasks.forEach { $0.cancel() }
                print("- - - Data task was canceled!")
            case .UploadTask:
                uploadTasks.forEach { $0.cancel() }
                print("- - - Upload task was canceled!")
            case .DownloadTask:
                downloadTasks.forEach { $0.cancel() }
                print("- - - Download task was canceled!")
            case .ZeroTask:
                print("- - - Zero tasks was found!")
        }
    }
}

Where "CancelRequestType" is enum. So you can call the method like API.cancel(request: .UploadTask)

enum CancelRequestType: String {
    case DownloadTask = "DownloadTask"
    case DataTask = "DataTask"
    case UploadTask = "UploadTask"
    case ZeroTask = "Zero"
}