2

I am new to Swift and thus not very experienced. I do not know why this is not working.

I am trying to download a music file and then send it to the AVAudoPlayer to play.

Here is the code:

@IBAction func startDownload(_ sender: Any) {
    weak var weakSelf = self
    let url = URL(string: "http://www.noiseaddicts.com/samples_1w72b820/280.mp3")!
    let task = DownloadManager.shared.activate().downloadTask(with: url as URL, completionHandler: { (URL, response, error) -> Void in

        print("URL = \(URL)")

        weakSelf!.plays(url: URL! as URL)

    })
    task.resume()
}

and there error I am getting is:

DownloadTaskExample[31140:1527666] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Completion handler blocks are not supported in background sessions. Use a delegate instead.'

*** First throw call stack: ( 0 CoreFoundation 0x0000000110bbf34b exceptionPreprocess + 171 1 libobjc.A.dylib 0x000000010db2f21e objc_exception_throw + 48 2 CFNetwork 0x00000001111ada2c -[__NSURLBackgroundSession validateSerializabilityForRequest:completion:] + 172 3 CFNetwork 0x00000001111b035c -[__NSURLBackgroundSession _onqueue_downloadTaskForRequest:resumeData:completion:] + 36 4 CFNetwork 0x00000001111af37c __90-[__NSURLBackgroundSession downloadTaskForRequest:downloadFilePath:resumeData:completion:]_block_invoke + 38 5 CFNetwork 0x00000001111adddb __68-[__NSURLBackgroundSession performBlockOnQueueAndRethrowExceptions:]_block_invoke + 67 6 libdispatch.dylib 0x0000000111aa00cd _dispatch_client_callout + 8 7 libdispatch.dylib 0x0000000111a7d30a _dispatch_barrier_sync_f_invoke + 340 8 CFNetwork 0x00000001111add44 -[__NSURLBackgroundSession performBlockOnQueueAndRethrowExceptions:] + 174 9 CFNetwork 0x00000001111af2e5 -[__NSURLBackgroundSession downloadTaskForRequest:downloadFilePath:resumeData:completion:] + 243 10 DownloadTaskExample 0x000000010d54086d _TFC19DownloadTaskExample14ViewController13startDownloadfP_T_ + 525 11 DownloadTaskExample 0x000000010d540e33 _TToFC19DownloadTaskExample14ViewController13startDownloadfP_T_ + 67 12 UIKit 0x000000010e3685b8 -[UIApplication sendAction:to:from:forEvent:] + 83 13 UIKit 0x000000010e4ededd -[UIControl sendAction:to:forEvent:] + 67 14 UIKit 0x000000010e4ee1f6 -[UIControl _sendActionsForEvents:withEvent:] + 444 15 UIKit 0x000000010e4ed0f2 -[UIControl touchesEnded:withEvent:] + 668 16 UIKit 0x000000010e3d5ce1 -[UIWindow _sendTouchesForEvent:] + 2747 17 UIKit 0x000000010e3d73cf -[UIWindow sendEvent:] + 4011 18 UIKit 0x000000010e38463f -[UIApplication sendEvent:] + 371 19 UIKit 0x000000010eb7671d __dispatchPreprocessedEventFromEventQueue + 3248 20 UIKit 0x000000010eb6f3c7 __handleEventQueue + 4879 21 CoreFoundation 0x0000000110b64311 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION + 17 22 CoreFoundation 0x0000000110b4959c __CFRunLoopDoSources0 + 556 23 CoreFoundation 0x0000000110b48a86 __CFRunLoopRun + 918 24 CoreFoundation 0x0000000110b48494 CFRunLoopRunSpecific + 420 25 GraphicsServices 0x000000011444da6f GSEventRunModal + 161 26 UIKit 0x000000010e366964 UIApplicationMain + 159 27 DownloadTaskExample 0x000000010d54597f main + 111 28 libdyld.dylib 0x0000000111aec68d start + 1 29 ??? 0x0000000000000001 0x0 + 1 )

libc++abi.dylib: terminating with uncaught exception of type NSException

Please could someone help me so that Once the file is downloaded it is passed over to the plays function so I can play it :)

P.s Here is the DownloadManager I am using: https://www.ralfebert.de/snippets/ios/urlsession-background-downloads/

JamesG
  • 1,552
  • 8
  • 39
  • 86
  • Wagering a guess - and only a guess? It's about what these two really *are*. A completion handler is just that - what to do once you set something in motion. How can the processing code what to do when a background thread is finished doing it's job? You may not *need* to use a delegate (but that may well be the *best* way) but you **do** need to know when the download is done. Depending on you specific requirement, a delegate will likely "pause" the UI while downloading versus using a `Notification` which will not. –  May 26 '17 at 18:06
  • I wonder if the owner of Download Manager @ralf-ebert can help? – JamesG May 26 '17 at 18:14
  • James, if you're going to use background `URLSession`, you can't use completion blocks because your app may be terminated by the time it finishes. You have to use delegate-based API if you're going to use background session. See https://stackoverflow.com/a/44140059/1271826 for a simple example of background session. – Rob May 26 '17 at 19:13
  • @Rob Thak you for the link but I think you over estimate my knowledge. I look at that code and feel confused. Could I please ask for a more detailed reply specific to my question? I shall give you extra bounty points. – JamesG May 26 '17 at 19:19
  • I might suggest you first watch WWDC 2013 [What's New in Foundation Networking](https://developer.apple.com/videos/play/wwdc2013/705/), which introduces `NSURLSession` and background sessions. The video is a bit dated (e.g. the samples are Objective-C demonstration only, though the basic mechanisms are exactly the same in Swift; they put it in context comparing it to the now deprecated `NSURLConnection`; etc.), but is a good primer on the basic concepts. When you're done with that, you can then return to the question I linked to above, which you're more likely to grok at that point. – Rob May 26 '17 at 19:55

1 Answers1

9

What this is telling you is that

  1. When you created your URLSession, you used a background configuration object, that is, with the background(withIdentifier:) method on URLSessionConfiguration.
  2. You started a download, with a completion block.
  3. This is not allowed.

The reason this isn't allowed is that with background downloads, your app might not be running when the download finishes, but iOS wants to wake up your app to tell it that the download finished. That completion block won't exist once your app stops running, so it's not an effective way of getting that notification.

You have a couple of options:

  • You can stop using a background session. Something like URLSessionConfiguration.default. No background downloads, though.
  • You can remove the callback and instead use methods declared in URLSessionDelegate to get the result of the download.
Tom Harrington
  • 69,312
  • 10
  • 146
  • 170
  • Could I trouble you for an example? – JamesG May 26 '17 at 18:58
  • 1
    If there's some part of the answer that's not clear, please let me know and I'll try to explain better. – Tom Harrington May 26 '17 at 19:00
  • Well, I changed URLSessionConfiguration.default and I get this error: Cannot call value of non-function type 'URLSessionConfiguration' I am a little noobie, well very noobie :/ – JamesG May 26 '17 at 19:12
  • You'd create the `URLSession` with something like `URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: OperationQueue.main)`, where `sessionDelegate` is replaced with some object that implements the `URLSessionDelegate` protocol. – Tom Harrington May 26 '17 at 19:20
  • Nice answer +1. However using a delegate to ensure it will run when the app is not running seems improbable - there has got to be some other reason behind separating the interface for both tasks. Maybe it has got to do with reference counting as delegates are weakly retained but blocks are strongly retained. – Nirav Bhatt Sep 27 '17 at 06:56