5 Replies
      Latest reply: Nov 11, 2016 3:29 AM by eskimo RSS
      waynehend Level 1 Level 1 (5 points)

        My app uses web data.  When running in the foreground, the following code works very nicely.  (I've defined it in the view controller class but also in a separate Central.swift file.  Same behavior.)

         

        It also works during a background fetch, but only sporadically.  It's not 100% reliable:

         

            var urlString: String = "some website"
            if let url = URL(string: urlString) {
                var url = URL(string: urlString)
                var request = URLRequest(url: url!)
                request.httpMethod = "GET"
                request.addValue("text/html", forHTTPHeaderField: "Content-Type")
                let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
                       print("The Error is \(error)")
                    if data != nil {
                        var return1 = NSString(data: data!, encoding: String.Encoding.utf8.rawValue) as! String
        //     Process the web data here
                        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                            var defaults: UserDefaults = UserDefaults.standard
                            defaults.set(myNumber, forKey: "myKey")
                            defaults.set(Date(), forKey: "myUpdate")
                            self.UILabel?.text =  x
                           Central().someFunction()  
        //  This function is defined in the class Central.swift. It uses data stored in user defaults.
        //   It sends a notification if the app state is inactive.
        
                        }
                    }
                }
                task.resume()
            }
        
        
        
        
        
        
        

         

        When it fails, it seems to be stuck just before the DispatchQueue statement.  Sometimes everything inside the Dispatch will execute when I bring the app to the foreground, instead of executing in the background beforehand.  When it works, the commands in Dispatch execute normally, including the sending of an an alert notification to the user that's called in the Central().someFunction.  This tells the user that new data has arrived.

         

        Should the Session be something besides URLSession.shared ?  What else?

         

        This is really making me nuts.  As I write this, it just worked.  But I know it often will not.

         

        BTW, I do my testing with my iPhone 6, not iOS simulator.  Part of my confusion is that printing to the console may fail even when the code seems to have otherwise worked.

         

        PS:  I add a print to line  #08 above and get this on the console:

        The Error is Optional(Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSUnderlyingError=0x170052bd0 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, NSErrorFailingURLStringKey= some website, NSErrorFailingURLKey=some website, _kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102, NSLocalizedDescription=The request timed out.})

         

        A similar discussion is here: https://forums.developer.apple.com/thread/22032

        • Re: URL session during background fetch
          eskimo Apple Staff Apple Staff (5,995 points)

          Check out this post.

          Share and Enjoy

          Quinn “The Eskimo!”
          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
          let myEmail = "eskimo" + "1" + "@apple.com"

            • Re: URL session during background fetch
              waynehend Level 1 Level 1 (5 points)

              I've been studying the issue in that post but I'm still confused about which direction to go.  My web tasks complete in just seconds, so the 30 second limit is not a problem for a user with good internet service.  And I am not triggering (yet) from a push notification, so I thought a system-triggered backgound fetch was a good option.

               

              Is my problem that the app is going into a suspended state, as opposed to merely in the background?  I have the following in the AppDelegate:

               

                  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
                  UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
                          UNUserNotificationCenter.current().requestAuthorization(
                              options: [.alert,.sound,.badge],
                              completionHandler: { (granted,error) in
                              }
                          )
                  return true
                  }
              
                  func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
                      print("Calling up the Windows Fetch")
                      Windows().fetch {    // "Windows" is a view controller
                      }
                      print("Calling up the Central Fetch")
                      Central().fetch {    // "Central" is a .swift file
              
              
                      completionHandler(.newData)
                      }
                  }
              

               

              And I have added the UIBackgroundModes key to my Info.plist file with both "App downloads..." option items.

               

              I thought this was enough to allow background fetches to be triggered by the iOS at any time.  It seems to work at least part of the time.   The Apple documentation says:

               

              Fetching Small Amounts of Content Opportunistically

              Apps that need to check for new content periodically can ask the system to wake them up so that they can initiate a fetch operation for that content. To support this mode, enable the Background fetch option from the Background modes section of the Capabilities tab in your Xcode project. (You can also enable this support by including the UIBackgroundModes key with the fetch value in your app’s Info.plist file.) Enabling this mode is not a guarantee that the system will give your app any time to perform background fetches. The system must balance your app’s need to fetch content with the needs of other apps and the system itself. After assessing that information, the system gives time to apps when there are good opportunities to do so.

               

              As I understand that, my app should occasionally be awoken from even a suspended state, into the background where a quick fetch can be processed.  And in fact it works some of the time.  But more often, I'm getting the "timed out" error above.

              • Re: URL session during background fetch
                waynehend Level 1 Level 1 (5 points)

                Well today is a brighter day.  I installed Xcode 8.2 beta 8C23 last night and things seem to be working far more reliably today.  I'm not saying that's cause and effect, but at least a coincidence.

                 

                I've noticed also that the "Comment Selection" menu command is now functioning again.  This was not working in Xcode 8.1 for me and many others.  I was able to temporarily fix it by renaming the app (by appending the version 8.1 to the app file name) but the problem soon returned. Maybe my Xcode installation was somehow buggered, and installing the beta fixed it.  Whatever, it seems to be working for now.

                 

                The error is still happening, just at a much lower rate.  Maybe it has something to do with my network?  Still looking for a way to quash this.

                 

                Domain=NSURLErrorDomain Code=-1005 "The network connection was lost."

                • Re: URL session during background fetch
                  waynehend Level 1 Level 1 (5 points)

                  After the previous post I've continued to have a high failure rate and have now started to adopt the background download task approach instead of the data task.  I'm still working on it but I'm optimisitc it will be up and running soon.

                   

                  In the meanwhile I've seen quite a few errors about reading and writing to the user defaults while in the background.  I came across this thread:

                  https://forums.developer.apple.com/thread/15685

                   

                  Do you still advise agaisnt using UserDefaults during background operation?  It's the only method I've learned so far for saving data, so I'm reluctant to give it up.  I need to process the web data from my session and place a few things into storage.  Should I not use UserDefaults ?

                    • Re: URL session during background fetch
                      eskimo Apple Staff Apple Staff (5,995 points)

                      Do you still advise [against] using UserDefaults during background operation?

                      User defaults was intended for storing things like preferences, which are small and not particularly significant (that is, if they get dropped it’s not the end of the world).  If that describes your needs, it’s fine to use user default in general.

                      The obvious gotcha is background execution.  If your app might run in a situation where the files backing user defaults are not available, you will run into problems.

                      It's the only method I've learned so far for saving data …

                      Look on the bright side, it’s an opportunity to learn something new!

                      Seriously though, putting this data into a file won’t be too difficult.  You don’t need anything complex:

                      • use PropertyListSerialization to turn simple data (dictionary, array, string, and so on) into Data

                      • use FileManager to get a URL for the Documents directory

                      • use URL.appendPathComponent() to get a URL for your file

                      • read and write that using Data(contentsOf:) and write(to:)

                      Share and Enjoy

                      Quinn “The Eskimo!”
                      Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                      let myEmail = "eskimo" + "1" + "@apple.com"