NSURLSession with WatchOS 2.2 very slow - ios

I try to download some JSON data from a web server with NSURLSession in a WatchOS 2.2 app. The same code that runs flawlessly on the iPhone itself, takes forever on the watch (using the simulator).
I checked out this example: https://github.com/shu223/watchOS-2-Sampler, which has a function to download and display an image via NSURLSession, and it has the same problem.
The code of this example is:
let url = NSURL(string:"https://pbs.twimg.com/profile_images/3186881240/fa714ece16d0fabccf903cec863b1949_400x400.png")!
let conf = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: conf)
task = session.dataTaskWithURL(url) { (data, res, error) -> Void in
if let e = error {
print("dataTaskWithURL fail: \(e.debugDescription)")
return
}
if let d = data {
let image = UIImage(data: d)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if self.isActive {
self.image.setImage(image)
}
})
}
}
task!.resume()
Playing around with it, I figured out that if I change the code to
let session = NSURLSession.sharedSession()
it works fine.
However, I cannot use this in my app because I need to setup delegates.
What can I do to get it working?

I'm still experiencing the same issue in watchOS3. Slow network request. Especially when the watch app was freshly installed, the first network request is usually very slow. It can take up to 30s and timeout, but instantly in iOS.
But I find immediate improvement by using the iOS app to make the network request and send it to the watch app.
I use WCSession sendMessage:replyHandler:errorHandler in watchOS to notify the iOS app to make a specific network request. Then I send back the response from the network request via the replyHandler in session:didReceiveMessage:replyHandler. There's only a short lag.
As a fallback, whenever sendMessage fails, I make the same request via the watch app, so it should still work if iPhone is not nearby.

Related

Calling multiple URLs in succession using UIApplication.shared.open - only opens first

I'm trying to create a wifi tress test by opening multiple URLs in succession in Xcode/Swift for iPad. It seems to open only the first successfully. The "sleep(x)" call makes no difference. Code snippet:
func counter()
{
seconds -= 1
label.text = String(seconds) + " Seconds"
if (seconds == 0)
{
let url1 = URL(string: "http://www.wix.com")!
let url2 = URL(string: "http://www.activistpost.com")!
let url3 = URL(string: "http://www.time.com")!
let url4 = URL(string: "http://www.steemit.com")!
let url5 = URL(string: "http://www.youtube.com")!
let url6 = URL(string: "http://www.cptts.net/61m.jpg")!
if #available(iOS 10.0, *) {
UIApplication.shared.open (url1)
sleep (5)
UIApplication.shared.open (url2)
sleep (5)
UIApplication.shared.open (url3)
sleep (5)
UIApplication.shared.open (url4)
sleep (5)
UIApplication.shared.open (url5)
sleep (5)
UIApplication.shared.open (url6)
} else {
// Fallback on earlier versions
}
Xcode 10.0 Beta 2
I'm trying to create a wifi tress test
Well, that's not how to do it. Also, I wonder whether you really need to do it; the developer tools already allow you to simulate a busy network for testing purposes.
As i quote from Apple documentation, it appears to be it will only execute the first and the app will quit and it will launch the other app if found.
The URL you pass to this method can identify a resource in the app
that calls the method, or a resource to be handled by another app. If
the resource is to be handled another app, invoking this method might
cause the calling app to quit so the other can launch.
So eventually ones your app quits then your other calls won't by executed.

Is it okay to download data directly to watch OS from server

So I'm trying to make a watchOS app for a music streaming app, and I found an example pretty much close to what I'm going to make.
(https://github.com/belm/BaiduFM-Swift)
But It seems like the project is kinda outdated. According to the codes below, watch extension is getting required datas like sound, images via HttpRequest. From what I read, watchOS 3 supports Background Connectivity, (which enables app to transfer data more efficiently) and Apple encourages developers to process and get data from the main app.
What is right way to do it? Is there any good example to see?
// play song method in interface controller
HttpRequest.getSongLink(info.id, callback: {(link:SongLink?) -> Void in
if let songLink = link {
DataManager.shareDataManager.curSongLink = songLink
DataManager.shareDataManager.mp.stop()
var songUrl = Common.getCanPlaySongUrl(songLink.songLink)
DataManager.shareDataManager.mp.contentURL = NSURL(string: songUrl)
DataManager.shareDataManager.mp.prepareToPlay()
DataManager.shareDataManager.mp.play()
DataManager.shareDataManager.curPlayStatus = 1
Async.main{
self.songTimeLabel.setText(Common.getMinuteDisplay(songLink.time))
}
HttpRequest.getLrc(songLink.lrcLink, callback: { lrc -> Void in
if let songLrc = lrc {
DataManager.shareDataManager.curLrcInfo = Common.praseSongLrc(songLrc)
//println(songLrc)
}
})
}
})

Send request in applicationWillTerminate

In my app I need to send some instructions to server when the user terminated an app. In applicationWillTerminate func I tried to send it, but it never came to server. I tried to use Alamofire and native URLSession but it doesn't work. Does anybody know how can I send it?
I use this code
let request = "\(requestPrefix)setDriverOrderStatus"
if let url = URL(string:request) {
var parameters : [String : String] = [:]
parameters["access_token"] = UserSession.accessToken
parameters["driver_id"] = UserSession.userID
parameters["status"] = status
var req = URLRequest(url: url)
req.httpMethod = HTTPMethod.put.rawValue
do {
req.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
}
_ = URLSession.shared.dataTask(with: req, completionHandler: { data, response, error in
guard error == nil else {
print(error ?? "error")
return
}
guard let data = data else {
print("Data is empty")
return
}
let json = try! JSONSerialization.jsonObject(with: data, options: [])
print(json)
}).resume
}
One solution that worked for me is to add sleep at the end of the applicationWillTerminate function like this :
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
// HERE YOU will make you HTTP request asynchronously
self.postLogoutHistory()
// 3 is the number of seconds in which you estimate your request
// will be finished before system terminate the app process
sleep(3)
print("applicationWillTerminate")
// self.saveContext()
}
put breakpoint in applicationWillTerminate and check that, function is getting called or not because applicationWillTerminate is not called everytime when application is getting terminated, especially when user quit application manually from multitasking window, applicationWillTerminate will not get called! When system terminates the application at that time applicationWillTerminate will get called and you will got approximately five seconds to complete your task!! So, it is not good idea to perform network related task on applicationWillTerminate!!
Refer Apple Documentation for applicationWillTerminate, It states,
This method lets your app know that it is about to be terminated and
purged from memory entirely. You should use this method to perform any
final clean-up tasks for your app, such as freeing shared resources,
saving user data, and invalidating timers. Your implementation of this
method has approximately five seconds to perform any tasks and return.
If the method does not return before time expires, the system may kill
the process altogether.
For apps that do not support background execution or are linked
against iOS 3.x or earlier, this method is always called when the user
quits the app. For apps that support background execution, this method
is generally not called when the user quits the app because the app
simply moves to the background in that case. However, this method may
be called in situations where the app is running in the background
(not suspended) and the system needs to terminate it for some reason.
After calling this method, the app also posts a
UIApplicationWillTerminate notification to give interested objects a
chance to respond to the transition.

Firebase Storage download task is not completing after the app has spent some time in the background

I am downloading an image from Firebase storage as follows:
let storage = FIRStorage.storage()
// Create a storage reference from our storage service
let storageRef = storage.reference(forURL: "MY_STORAGE_URL")
let imageRef = storageRef.child("Path_to_image")
// Download image in memory
let downloadTask = imageRef.data(withMaxSize: 1 * 1024 * 1024) {
(data, error) -> Void in
if (error != nil) {
//Handle the error
} else {
guard let imageData = data else {
print("Unable to unwrap image data.")
return
}
let downloadedImage = UIImage(data: imageData)
//Do some stuff with the image
}
}
I am also monitoring what happens with the download using the following observers:
// Observe changes in status
downloadTask.observe(.resume) { (snapshot) -> Void in
// Download resumed, also fires when the download starts
}
downloadTask.observe(.pause) { (snapshot) -> Void in
// Download paused
}
downloadTask.observe(.progress) { (snapshot) -> Void in
// Download reported progress
}
downloadTask.observe(.success) { (snapshot) -> Void in
// Download completed successfully
}
downloadTask.observe(.failure) { (snapshot) -> Void in
//Download failed
}
This all works just fine when the app is first started. However, I am getting problems if the app enters the background and I play around with some other applications (Facebook, Twitter, etc.), then bring the app back to the foreground. I also have problems if I leave the app open and running in the foreground for greater than or equal to 1 hour.
The problem is that the completion handler in let downloadTask = imageRef.data(withMaxSize: blah blah blah (in the first block of code above) is never called. If the completion handler is never called, I can never unwrap the data and attempt to use the image in my application.
Also, in the downloadTask observers, the only completion handlers that get fired are .resume and .progress. The .success or .failure events are never triggered. This seems to be a Firebase Storage bug to me, but I am not sure. Has anyone else encountered a similar issue? I don't understand why the code would work just fine from a fresh launch, but then after some time in the foreground or after some time in the background the image download stops working. Thanks in advance for any input you may have.
This is currently the expected behavior, unfortunately. Firebase Storage (at present) is foreground only: if the app is backgrounded, we haven't persisted the upload URL, and can't upload in the background nor restart it after it gets out of the background, so it probably is killed by the OS and the item isn't uploaded.
It's The Next Big Thing™ we'd like to tackle (our Android SDK makes it possible, though not easy), but unfortunately for now we haven't made more progress on this.
As a bit of a side note, your observers won't exist after the activity change--downloadTask is gone once the app is backgrounded, so when it comes back into the foreground, we basically need a method that retrieves all tasks that are currently backgrounded, and allows you to hook observers back up. Something like:
FIRStorage.storage().backgroundedTasks { (tasks) -> Void in
// tasks is an array of upload and download tasks
// not sure if it needs to be async
}

iOS: Perform upload task while app is in background

Is there really no way to run an UPLOAD task while an iOS app is in the background? This is ridiculous. Been looking at various stuff like NSURLSessionUploadTask, dispatch_after and even NSTimer, but nothing works for more than the meager 10 seconds the app lives after being put in the background.
How do other apps that have uploads work? Say, uploading an image to Facebook and putting the app in the background, will that cancel the upload?
Why cannot iOS have background services or agents like Android and Windows Phone has?
This is a critical feature of my app, and on the other platforms is works perfectly.
Any help is appreciated :(
You can continue uploads in the background with a “background session”. The basic process of creating a background URLSessionConfiguration with background(withIdentifier:) is outlined in Downloading Files in the Background. That document focuses on downloads, but the same basic process works for upload tasks, too.
Note:
you have to use the delegate-based URLSession;
you cannot use the completion handler renditions of the task factory methods with background sessions;
you also have to use uploadTask(with:fromFile:) method, not the Data rendition ... if you attempt to use uploadTask(with:from:), which uses Data for the payload, with background URLSession you will receive exception with a message that says, “Upload tasks from NSData are not supported in background sessions”; and
your app delegate must implement application(_:handleEventsForBackgroundURLSession:completionHandler:) and capture that completion handler which you can then call in your URLSessionDelegate method urlSessionDidFinishEvents(forBackgroundURLSession:) (or whenever you are done processing the response).
By the way, if you don't want to use background NSURLSession, but you want to continue running a finite-length task for more than a few seconds after the app leaves background, you can request more time with UIApplication method beginBackgroundTask. That will give you a little time (formerly 3 minutes, only 30 seconds in iOS 13 and later) complete any tasks you are working on even if the user leave the app.
See Extending Your App's Background Execution Time. Their code snippet is a bit out of date, but a contemporary rendition might look like:
func initiateBackgroundRequest(with data: Data) {
var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
// Request the task assertion and save the ID.
backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Finish Network Tasks") {
// End the task if time expires.
if backgroundTaskID != .invalid {
UIApplication.shared.endBackgroundTask(backgroundTaskID)
backgroundTaskID = .invalid
}
}
// Send the data asynchronously.
performNetworkRequest(with: data) { result in
// End the task assertion.
if backgroundTaskID != .invalid {
UIApplication.shared.endBackgroundTask(backgroundTaskID)
backgroundTaskID = .invalid
}
}
}
Please don’t get lost in the details here. Focus on the basic pattern:
begin the background task;
supply a timeout clause that cleans up the background task if you happen to run out of time;
initiate whatever you need to continue even if the user leaves the app; and
in the completion handler of the network request, end the background task.
class ViewController: UIViewController, URLSessionTaskDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://0.0.0.0")!
let data = "Secret Message".data(using: .utf8)!
let tempDir = FileManager.default.temporaryDirectory
let localURL = tempDir.appendingPathComponent("throwaway")
try? data.write(to: localURL)
let request = URLRequest(url: url)
let config = URLSessionConfiguration.background(withIdentifier: "uniqueId")
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
let task = session.uploadTask(with: request, fromFile: localURL)
task.resume()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("We're done here")
}

Resources