9

The service I'm connecting to is using a self signed certificate. For dev purposes I do not want to validate that chain.

Using swift 3 with Alamofire 4. Fixed the ATS accordingly:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>url.com</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSExceptionRequiresForwardSecrecy</key>
            <false/>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
    </dict>
</dict>

Code to connect and disable evaluation.

    let serverTrustPolicies: [String: ServerTrustPolicy] = [
        "example.domain.com": .pinCertificates(
            certificates: ServerTrustPolicy.certificates(),
            validateCertificateChain: false,
            validateHost: true
        ),
        "sub.url.com": .disableEvaluation
    ]

    let sessionManager = Alamofire.SessionManager(
        serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
    )

    let headers = ["Authorization": "Basic /*...*/"]

    sessionManager.request("https://sub.url.com/path", headers: headers).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

        debugPrint(response)

        if let JSON = response.result.value {
            print("JSON: \(JSON)")
        }
    }

Error log from dumpPrint

[Result]: FAILURE: Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLKey=https://sub.url.com/path, NSLocalizedDescription=cancelled, NSErrorFailingURLStringKey=https://sub.url.com/path}

URL has been masked.

D.Dimitrioglo
  • 3,413
  • 2
  • 21
  • 41
Sami M'Barek
  • 137
  • 1
  • 1
  • 9

6 Answers6

13

To retain SessionManager instance you need to capture it in closure passed to responseJSON:

sessionManager.request("https://sub.url.com/path", headers: headers).responseJSON { response in
    let _ = sessionManager // retain
    // ...
}

Otherwise sessionManager is deallocated shortly it goes out of scope and any executing requests are cancelled.

mixel
  • 25,177
  • 13
  • 126
  • 165
  • This is not a proper solution and is an anti-pattern. You should make your manager instance a singleton, or use some other way of persisting it beyond local usage. – Jon Shier Jul 17 '17 at 20:29
  • @JonShier It's not so widely used solution to be an anti-pattern :) You can run into situation when you do not need to keep `SessionManager` during the whole lifetime of an application, so it's wise to deallocate it after request is completed. – mixel Jul 17 '17 at 20:38
  • 2
    Then the lifetime should be properly extended, perhaps by storing the manager somewhere and properly canceling it out when the time comes. Forcing a local reference just to keep something alive is always an anti-pattern, as it means you're not properly handling the lifetimes of your variables. – Jon Shier Jul 17 '17 at 20:40
  • @JonShier I agree with you. The above "workaround" is usually used to retain some instance defined in method scope after method returns. We can define `var sessionManager: SessionManager?` variable at class scope and set it to `nil` in `responseJSON`. This is cleaner solution but how does it differ from the "workaround" in terms of memory management and other underlying things? – mixel Jul 17 '17 at 21:33
8

Please add this statement to the end of responseJson block:

manager.session.invalidateAndCancel()

It happens if the object of the manager is not retained till execution of the block completes, so this would ensure its retention.

Cheers!

  • sessionManager.request("https://sub.url.com/path", headers: headers).responseJSON { response in /*...*/ if let JSON = response.result.value { print("JSON: \(JSON)") } sessionManager.session.invalidateAndCancel() } – Sami M'Barek Oct 11 '16 at 19:44
  • sorry about that, I screwed up my config when I tried. – Sami M'Barek Oct 11 '16 at 19:51
4
self.getSessionManager().request(urlstring, method: methods, parameters: parameters, encoding: JSONEncoding.prettyPrinted, headers: Header).responseJSON(queue: nil, options: JSONSerialization.ReadingOptions.allowFragments) { (responseObject) in ... .... }.session.finishTasksAndInvalidate()

Just put the method of invalidate after task finish, means session.finishTasksAndInvalidate()

2

Please check in sessiondidReceiveChallenge: delegate implementation of NSURLSession. Chances are NSURLSessionAuthChallengeCancelAuthenticationChallenge is getting executed somewhere.

mixel
  • 25,177
  • 13
  • 126
  • 165
john7ric
  • 53
  • 1
  • 6
1

You need to properly manage the lifetime of your SessionManager instance. Most commonly this is done by making a singleton instance. Otherwise, when your manager goes out of scope and is deinited, all outstanding requests will be cancelled.

Jon Shier
  • 12,200
  • 3
  • 35
  • 37
0

There could be a lot of reason to why your requests "cancelled".
In your case your request cancels immediately. You can refer to this issue in Alamofire repository issues

jshier commented on Oct 10, 2016

An unexpected error -999 almost always means your SessionManager was deallocated, cancelling any ongoing requests. I suggest you create a singleton value for your custom SessionManager, or perhaps just reevaluate if you really need one.

if you create a singleton value for your object it remains in memory and prevent from deallocate

and another thing that i avoid is to name your variables diffrent, a sessionManager is in Alamofire and your variable is also called sessionManager.

Alamofire 4.7 , Swift 4

import Alamofire

class Networking {

  public static let sharedManager: SessionManager = {
      let configuration = URLSessionConfiguration.default
      configuration.timeoutIntervalForRequest=20
      let manager = Alamofire.SessionManager(configuration: configuration, delegate: SessionManager.default.delegate)
      return manager
  }()
}

Alamofire 5.4.4 , Siwft 5.2

import Alamofire

class Networking {

  static let APIManager: Session = {
      let configuration = URLSessionConfiguration.default
      configuration.timeoutIntervalForRequest = 20
      let delegate = Session.default.delegate
      let manager = Session.init(configuration: configuration,
                                 delegate: delegate,
                                 startRequestsImmediately: true,
                                 cachedResponseHandler: nil)
      return manager
  }()
}
Mohammad Reza Koohkan
  • 1,656
  • 1
  • 16
  • 36