URLSessionDelegate methods not called - swift

Firstly, I already checked similar existing questions, and none of the answers apply.
NSURLSession delegates not called
URLSessionDelegate Function Not Being Called
I am trying to download a file using URLSessionDownloadTask, like so
class MyNetworkManager : NSObject
{
static let instance = MyNetworkManager()
var downloadSession : URLSession?
init()
{
super.init()
let downloadConfiguration = URLSessionConfiguration.default
downloadSession = URLSession(configuration: downloadConfiguration, delegate: self, delegateQueue: nil)
}
func download(_ url : URL)
{
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "GET"
let downloadTask = downloadSession?.downloadTask(with: urlRequest)
downloadTask?.resume()
}
}
extension MyNetworkManager : URLSessionDelegate
{
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64)
{
//
}
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL)
{
//
}
func urlSession(_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?)
{
//
}
}
However, no URLSessionDelegate methods are called.
Normally, delegate methods are not called if you create a task with completion handler - that is not the case, I'm only using URLRequest as parameter when creating a task.
Session's delegate is properly set, and after calling downloadTask?.resume() its state property is running
MyNetworkManager is a singleton, I'm using it like so
MyNetworkManager.instance.download(someURL)
so an instance is definitely retained.
Am I missing something here?

You must conform to the relevant protocols, e.g.:
extension MyNetworkManager: URLSessionDelegate {
// this is intentionally blank
// obviously, if you implement any delegate methods for this protocol, put them here
}
extension MyNetworkManager: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64) {
print(#function)
}
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL) {
print(#function)
}
}
extension MyNetworkManager: URLSessionTaskDelegate {
func urlSession(_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?) {
print(#function, error ?? "No error")
}
}
If you don’t conform to URLSessionDownloadDelegate, it won’t call URLSessionDownloadDelegate methods.

Related

Selector type of expression is ambiguous without more context

I'm trying to get ARSessionDelegate method using selector, but im getting this error:
Type of expression is ambiguous without more context
There is mine code:
#selector(ARSessionDelegate.session(_:didUpdate:) as ((ARSessionDelegate) -> (ARSession, ARFrame) -> Void))
That's how this method looks like:
public protocol ARSessionDelegate : ARSessionObserver {
optional func session(_ session: ARSession, didUpdate frame: ARFrame)
}
And also, I am trying to make an rx extension for the ARKit session using this answer, but im not sure it's caused the problem.
Because there are multiple methods with the same selector name, you are forced to implement the method in the delegate and forward the calls using a subject. Like this:
extension ARSession: HasDelegate { }
extension Reactive where Base: ARSession {
var delegate: ARSessionDelegateProxy {
return ARSessionDelegateProxy.proxy(for: base)
}
var didUpdate: Observable<ARFrame> {
return delegate.didUpdate.asObservable()
}
}
final class ARSessionDelegateProxy
: DelegateProxy<ARSession, ARSessionDelegate>
, DelegateProxyType
, ARSessionDelegate {
init(parentObject: ARSession) {
super.init(
parentObject: parentObject,
delegateProxy: ARSessionDelegateProxy.self
)
}
deinit {
didUpdate.onCompleted()
}
public static func registerKnownImplementations() {
self.register { ARSessionDelegateProxy(parentObject: $0) }
}
func session(_ session: ARSession, didUpdate frame: ARFrame) {
didUpdate.onNext(frame)
}
fileprivate let didUpdate = PublishSubject<ARFrame>()
}

How to know when a PDF is rendered using PDFKit

I'm trying to show a loading while a PDF ins't already been shown on the screen. The problem is, my loading always stops before the document is already rendered, sometimes it can't take 2 or 3 seconds and I need to know when the PDF is already rendered to stop the activyIndicator. IS IT possible using PDFKIT? My code:
class PDFViewController: URLSessionDownloadDelegate {
#IBOutlet weak var pdfView: PDFView!
var pdfDocument: PDFDocument!
override func viewDidLoad() {
super.viewDidLoad()
setupPDFView()
setupNavigationBar()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.startLoading()
}
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadPDF()
}
private func loadPDF() {
DispatchQueue.main.async {
guard let url = URL(string: self.viewModel.pdfURL) else {
self.showAlert(with: self.viewModel.strings.invalidInvoice)
return
}
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
let downloadTask = urlSession.downloadTask(with: url)
downloadTask.resume()
}
}
private func setupPDFView() {
self.pdfView.displayMode = .singlePageContinuous
self.pdfView.autoScales = true
}
func startLoading() {
guard let window = UIApplication.shared.keyWindow,
!window.subviews.contains(where: { $0 is LoadingView }) else { return }
let loadingView = LoadingView(frame: window.bounds)
window.addSubview(loadingView)
Thread.performUIUpdate {
loadingView.startAnimation()
}
}
func stopLoading() {
guard let window = UIApplication.shared.keyWindow,
let view = window.subviews.first(where: { $0 is LoadingView }),
let loadingView = view as? LoadingView else { return }
Thread.performUIUpdate {
loadingView.stopAnimation()
loadingView.removeFromSuperview()
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
DispatchQueue.main.async {
self.pdfDocument = PDFDocument(url: location)
if let pdfDocument = self.pdfDocument {
self.pdfView.document = pdfDocument
}
self?.stopLoading()
}
}
}
This seems like a really bad idea:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
DispatchQueue.main.async {
self.pdfDocument = PDFDocument(url: location)
The problem is that while you are getting off the current thread and the asynchronous code starts, the method finishes and the temporary document at location can be destroyed. I would suggest writing like this:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
let pdf = PDFDocument(url: location)
DispatchQueue.main.async {
self.pdfDocument = pdf
I'm not saying that that will solve your issue, but it seems a lot less dangerous than what you're doing.
As for your actual issue, I would suggest registering for a notification such as this one and see whether it arrives at the right moment.
You have to observe the PDFViewVisiblePagesChanged notification to know when a PDFView pages are visible, add your code in the method.
NotificationCenter.default.addObserver(self, selector: #selector(pdfViewVisiblePagesChanged(sender:)), name: .PDFViewVisiblePagesChanged, object: nil)
#objc func pdfViewVisiblePagesChanged(sender: Notification) {
//Add your code here
}

URLSessionDataDelegate methods not being called

I'm starting a download session in a view controller:
class MyController: UIViewController {
func startDownload(withURL url: URL) {
downloadSession = URLSession(configuration: URLSessionConfiguration.default,
delegate: self,
delegateQueue: OperationQueue.main)
downloadTask = downloadSession.dataTask(with: url)
downloadTask.resume()
}
}
The delegate methods are defined in an extension:
extension MyController: URLSessionDelegate {
func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive data: Data) {
...
}
func urlSession(_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?) {
...
}
}
Why are these delegate methods never called?
You only declare conformance to URLSessionDelegate in your extension. If you declare conformance to URLSessionDataDelegate instead, your delegate methods will be called.

URLSessionDelegate's didWriteData not call when app is going to background in iOS12

I want to implement downloading functionality which can show completed status of downloading task with the percentage. And I'm able to do that but the problem is when the app is moving to the background and come back to the foreground at that time the delegate method didWriteData is not called in iOS12. Can anyone please help me? Here is my code
protocol DownloadDelagate {
func downloadingProgress(value:Float)
func downloadCompleted(identifier: Int,url: URL)
}
class DownloadManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
static var shared = DownloadManager()
var delegate: DownloadDelagate?
var backgroundSessionCompletionHandler: (() -> Void)?
var session : URLSession {
get {
let config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).background")
config.isDiscretionary = true
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
}
}
private override init() {
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
if let completionHandler = self.backgroundSessionCompletionHandler {
self.backgroundSessionCompletionHandler = nil
completionHandler()
}
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
delegate?.downloadCompleted(identifier: downloadTask.taskIdentifier, url: location)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if totalBytesExpectedToWrite > 0 {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
let progressPercentage = progress * 100
delegate?.downloadingProgress(value: progressPercentage)
print("Download with task identifier: \(downloadTask.taskIdentifier) is \(progressPercentage)% complete...")
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print("Task failed with error: \(error)")
} else {
print("Task completed successfully.")
}
}
}
Based on this thread this is a bug in NSURLSesstion. Currently there are known workaround for this (approved by Apple Engineers):
var session: URLSession?
...
func applicationDidBecomeActive(_ application: UIApplication) {
session?.getAllTasks { tasks in
tasks.first?.resume() // It is enough to call resume() on only one task
// If it didn't work, you can try to resume all
// tasks.forEach { $0.resume() }
}
}
Please try your code in AppDelegate's applicationWillEnterForeground(). You can make changes here when the app makes transition from Background to Active state.

Class as a delegate placeholder for a struct

I have a simple struct where I use NSURLSession to get the data from the web. I would like to extend it to provide progress of downloading. I cannot use struct as a NSURLSession delegate because it needs to be a NSObject so I have created simple class where I pass to NSURLSession delegate. This is short version of my code:
struct Resource {
private var progressDelegate: ProgressSessionDelegate?
private var session: NSURLSession!
init() {
self.progressDelegate = ProgressSessionDelegate()
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
session = NSURLSession(configuration: configuration, delegate: self.progressDelegate!, delegateQueue: nil)
}
func loadAsynchronous(callback: A? -> ()) {
session.dataTaskWithURL(resourceURL) {
data, response, error in
let json = data.flatMap {
try? NSJSONSerialization.JSONObjectWithData($0, options: NSJSONReadingOptions())
}
callback(mycalback)
}.resume()
}
}
and thats my cass which should handle the delegate methods:
class ProgressSessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
var expectedContentLength = 0
var currentLength = 0
override init() {
super.init()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
expectedContentLength = Int(response.expectedContentLength)
print("0: \(expectedContentLength)")
completionHandler(NSURLSessionResponseDisposition.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.currentLength += data.length
let percentageDownloaded = Float(self.currentLength) / Float(self.expectedContentLength)
print("1: \(percentageDownloaded)")
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
print("Downloaded")
}
}
The problem is that the delegate methods are not called.
When I move the code from Resource and ProgressSessionDelegate struct to my ViewController and set up ViewController as a NSURLSession delegate all is working fine. I think the problem is in a way how the class is stored is struct.
Any help would be most appreciated.