-1

Please tell me how can I START the URLSession in background. I only found the ways how to finish and handle results. The logic is next: the silent push notification orders to perform some request to the web server and get some json, performing additional tasks afterwards. Don't suggest background fetch cause I need to refresh data every couple of minutes.

EDIT:

So, actually my app only prints connected startMonitoring connected, and nothing else. And I'd like to receive the JSON received finally.

Sorry for not providing my code. Here it is:

AppDelegate.swift:

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    let aps = userInfo["aps"] as! [String: AnyObject]
    print("connected")
    // 1
    if (aps["content-available"] as? NSString)?.integerValue == 1 {
        var MLController = ((window?.rootViewController as? UITabBarController)?.viewControllers?.first as? UINavigationController)?.viewControllers.first as? MonitoringListTableViewController
        MLController?.startMonitoring()
        //completionHandler(UIBackgroundFetchResult.noData)
        print("connected")
    }
}

MonitoringListTableViewController.swift

    func startMonitoring() {

        print("startMonitoring")
        self.connect()
        //tokenTimer.invalidate()
        //tokenTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(tokenReloader), userInfo: nil, repeats: true)

    }

    func connect() {

        //Gets encrypted token

        for theCookie in HTTPCookieStorage.shared.cookies! {
            HTTPCookieStorage.shared.deleteCookie(theCookie)
        }
        let request = NSMutableURLRequest(url: URL(string: "http://...")!)

        let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
            guard error == nil && data != nil else {           // check for fundamental networking error
                print("error=\(error)")
                return
            }
            if let httpStatus = response as? HTTPURLResponse , httpStatus.statusCode != 200 {           // check for http errors
                print("statusCode should be 200, but is \(httpStatus.statusCode)")
                print("response = \(response)")
            }

            let responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
            //print("responseString = \(response), \(responseString)")
            for theCookie in HTTPCookieStorage.shared.cookies! {
                if(theCookie.name.hasPrefix("x1")) { self.UUU.x1= theCookie.value }
                if(theCookie.name.hasPrefix("x2")) { self.UUU.x2= theCookie.value }


            print("SUCCESS")
            self.tokenReloader()
        })
        task.resume()

    }

func tokenReloader() {


            let context: JSContext? = {
                let context = JSContext()

                // 1
                guard let
                    commonJSPath = Bundle.main.path(forResource: "x3", ofType: "js") else {
                        print("Unable to read resource files.")
                        return nil
                }

                // 2
                do {
                    let common = try String(contentsOfFile: commonJSPath, encoding: String.Encoding.utf8)
                    _ = context?.evaluateScript(common)
                } catch (let error) {
                    print("Error while processing script file: \(error)")
                }

                return context
            }()

            let parseFunction = context!.objectForKeyedSubscript("x3")
            let parsed = parseFunction?.call(withArguments: [UUU.encryptedToken!]).toString()


            requestT(withTokensOf: UUU)

    //}

     //   else { attempts = attempts + 1 }

    }

func requestT(withTokensOf theUUU : UUUClass) {

        let _UUU = theUUU
        for (index, theWatching) in watchingList.enumerated() {

            _UUU.dictToProps(dict: theWatching.propsToDict())

            self.makeRequest("**some_post_request**", url: "...", num: index, _UUU: _UUU)

        }

    }

    func makeRequest(_ parameters: String, url:String, num: Int, _UUU: UUUClass){
    let uuu = _UUU
    let postData:Data = parameters.data(using: String.Encoding.utf8)!
    //let postLength:NSString = String(postData.length)
    let request = NSMutableURLRequest(url: URL(string: url)!)
    let session = URLSession.shared
    request.httpMethod = "POST"
    request.httpBody = postData

    //request.setValue("text/html,application/xhtml+xml,application/xml;q=0.9,*;q=0.8", forHTTPHeaderField: "Accept")
    request.setValue("*/*", forHTTPHeaderField: "Accept")

    //print(request.allHTTPHeaderFields)

    let task = session.dataTask(with: request as URLRequest, completionHandler: {
        (data1, response, error) -> Void in
        guard error == nil && data1 != nil else {
            let _error = error as? NSError
            let themessage : String

            print("error=\(_error?.code)")
            return
        }

        let httpResponse = response as! HTTPURLResponse
        let statusCode = httpResponse.statusCode
        print(statusCode)
        if (statusCode == 200) {
            print("Everyone is fine, data received successfully.")
            print("response: \(response); data: \(data1)")

            let json = JSON(data: data1!)
            if (json["error"].boolValue == true) { return }
            print("JSON received: \(json.description)")

        }
        else {
            print("Error - no response")
             }
    })

    task.resume()
}
slesher
  • 109
  • 2
  • 13
  • Can you show the code that you have tried? You should start a background task by calling `backgroundTaskWithExpirationHandler` then perform your network operation. Once the operation is complete, end the task. You only have 3 minutes total execution time in the background, so you may eventually exhaust this. The time is reset if the user returns your app to the foreground. – Paulw11 Oct 12 '16 at 21:27
  • provide code your WILL get downvotes soon – David Seek Oct 12 '16 at 21:31
  • http://stackoverflow.com/q/20741618/1271826 – Rob Oct 12 '16 at 22:23
  • @Paulw11 Added my code. Some details are removed due to proprietary rules but the logic is saved – slesher Oct 13 '16 at 10:21
  • @Paulw11 backgroundTaskWithExpirationHandler helped, thanks. One more question: does backgroundTimeRemaining reset after invalidation every new backgroundTask or the sum up? And why the time is less before execution of task than after it? – slesher Oct 13 '16 at 11:33
  • I might suggest using background `URLSession` rather than `URLSession.shared`. It takes more work to implement, but you can start lots of big downloads which can run, even if your app isn't, and you aren't constrained to the limited time that the notification and/or `backgroundTaskWithExpirationHandler` entail. Also, in your `didReceiveRemoteNotification`, make sure to eventually call the completion handler (or else, when time expires, your app may be summarily terminated). – Rob Oct 13 '16 at 17:21

1 Answers1

0

What you're asking almost certainly isn't possible, and for good reason. An app downloading even a small resource every two minutes is basically keeping the cellular radio on nearly constantly. That's a huge battery drain even in the best of circumstances—almost as bad as making a phone call continuously.

In theory, you could use backgroundTaskWithExpirationHandler to let you do something two minutes later, and start a new request two minutes after your previous request finishes or fails.

In practice, if your app frequently wakes up in the background and starts new requests, the NSURLSession machinery will deliberately introduce delays in waking your app up. After a period of time, you'll be running far less frequently than every two minutes, I believe, though I have not looked at the code for that part of NSURLSession, so I can't be certain whether you would exceed its thresholds.

Either way, it's a very bad idea. Fetching occasional data in the background is what background fetch is designed for, and if you're making requests more often than background fetch will let you, that means you're doing something that you shouldn't be doing, and that's a good way to get your app rejected.

And if you really need to find out about something external to your app in a nearly real-time fashion, that's what push notifications are for.

dgatwood
  • 10,129
  • 1
  • 28
  • 49
  • Thanks, I have already solved this with backgroundTaskWithExpirationHandler. I indeed need to check the info every precise and little couple of minutes. And actually users understand and are informed about battery comsuming. When they don't need this app they can just kill it. – slesher Oct 20 '16 at 09:45
  • You're probably going to find that it stops working after four or five cycles, and starts updating exponentially more slowly, but maybe not. Best of luck. – dgatwood Oct 20 '16 at 16:21