Why delegate object is nil? - ios

I am facing little weird issue with delegate pattern and I couldn't make it work like I expect. I guess I am missing something here but I couldn't figure it out myself.
Here's how my classes are defined,
class NetworkManager {
weak var delegate: DownloaderProtocol?
func downloadFile(downloadUrl: URL ) {
downloadTask(with: downloadUrl).resume()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
delegate?.downloadCompleted(task: task, error: error)
}
}
class DownloadManager: DownloaderProtocol {
var networkManager: NetworkManager
init(networkManager: NetworkManager) {
self.networkManager = networkManager
}
func downloadMatches(urls: [String]) {
self.networkManager.delegate = self
for(....) {
self.networkManager.downloadFile(url: url)
}
}
func downloadCompleted(task: URLSessionTask, error: Error) {
// Implementation
}
}
class Matches {
var networkManager: NetworkManager
init() {
self.networkManager = NetworkManager()
}
func getMatchSchedules(urls: [String] , completionHandler: #escaping (Result<Data, Error>) -> Void) {
return DownloadManager.downloadMatches(urls: [String])
}
}
What is the issue ?
When the urlSession - didCompleteWithError method is called, I can see that delegate object is nil.
What I have tried ?
If I remove the weak reference of the delegate i.e weak var delegate: DownloaderProtocol? to var delegate: DownloaderProtocol?, then the delegate object is not nil. It works fine.
I even tried to set the delegates in Matches class, but the delegate is still shown as nil
Any help or point out is welcome.

well I recommend this approach since is not all you code that posted in the question but just the relevant one.
class NetworkManager {
weak var delegate: DownloaderProtocol?
func downloadFile(downloadUrl: URL ) {
downloadTask(with: downloadUrl).resume()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
delegate?.downloadCompleted(task: task, error: error)
}
}
class DownloadManager: DownloaderProtocol {
var networkManager: NetworkManager
init(networkManager: NetworkManager) {
self.networkManager = networkManager
self.networkManager.delegate = self
}
func downloadMatches(urls: [String]) {
for url in urls {
guard let url = URL(string: url) else {return}
self.networkManager.downloadFile(downloadUrl: url)
}
}
func downloadCompleted(task: URLSessionTask, error: Error) {
// Implementation
}
}
class Matches {
var downloadManager: DownloadManager//NetworkManager
init() {
//self.networkManager = NetworkManager()
//self.networkManager.delegate = DownloadManager()
let networkManager = NetworkManager()
self.downloadManager = DownloadManager(networkManager: networkManager)
}
func getMatchSchedules(urls: [String] , completionHandler: #escaping (Result<Data, Error>) -> Void) {
return downloadManager.downloadMatches(urls: ["asadsdad"])
}
}
you need a download manager in matches that have a network manager when you create matches you create the network manger and set to the init of the download manager so you have a delegate set.

You can use callback in place of delegate.
It's much easier approach than delegate.
typealias APIServiceSuccessCallback = ((Any?) -> ())
ViewController A:
//call from A -> B result fire if completionHandler fire in VC B
addCompanyTargetsController.responseCreateBookingObj = { [unowned self] (returnObject) in
if let object = returnObject as? [String:Any] {
if object["success"] as? Bool == true {
self.loadTargetList()//success block
}
}
}
ViewController B:
var responseCreateBookingObj : APIServiceSuccessCallback? //declaration
func completionHandler() { //from where you fire call this method
guard let callBack = self.responseCreateBookingObj else{
return
}
callBack( true as AnyObject)
}

Related

How to show Progress in swift - iOS [duplicate]

Is there any way to get progress from dataTaskWithURL in swift while the data is downloading?
NSURLSession.sharedSession().dataTaskWithURL(...)
I need to show progress bar while the data is downloading.
You can simply observe progress property of the URLSessionDataTask object.
Example:
import UIKit
class SomeViewController: UIViewController {
private var observation: NSKeyValueObservation?
deinit {
observation?.invalidate()
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://source.unsplash.com/random/4000x4000")!
let task = URLSession.shared.dataTask(with: url)
observation = task.progress.observe(\.fractionCompleted) { progress, _ in
print("progress: ", progress.fractionCompleted)
}
task.resume()
}
}
Playground example:
import Foundation
import PlaygroundSupport
let page = PlaygroundPage.current
page.needsIndefiniteExecution = true
let url = URL(string: "https://source.unsplash.com/random/4000x4000")!
let task = URLSession.shared.dataTask(with: url) { _, _, _ in
page.finishExecution()
}
// Don't forget to invalidate the observation when you don't need it anymore.
let observation = task.progress.observe(\.fractionCompleted) { progress, _ in
print(progress.fractionCompleted)
}
task.resume()
you can use this code for showing download process with progress bar with its delegate functions.
import UIKit
class ViewController: UIViewController,NSURLSessionDelegate,NSURLSessionDataDelegate{
#IBOutlet weak var progress: UIProgressView!
var buffer:NSMutableData = NSMutableData()
var session:NSURLSession?
var dataTask:NSURLSessionDataTask?
let url = NSURL(string:"http://i.stack.imgur.com/b8zkg.png" )!
var expectedContentLength = 0
override func viewDidLoad() {
super.viewDidLoad()
progress.progress = 0.0
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let manqueue = NSOperationQueue.mainQueue()
session = NSURLSession(configuration: configuration, delegate:self, delegateQueue: manqueue)
dataTask = session?.dataTaskWithRequest(NSURLRequest(URL: url))
dataTask?.resume()
// Do any additional setup after loading the view, typically from a nib.
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
//here you can get full lenth of your content
expectedContentLength = Int(response.expectedContentLength)
println(expectedContentLength)
completionHandler(NSURLSessionResponseDisposition.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
buffer.appendData(data)
let percentageDownloaded = Float(buffer.length) / Float(expectedContentLength)
progress.progress = percentageDownloaded
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
//use buffer here.Download is done
progress.progress = 1.0 // download 100% complete
}
}
Update for Swift4:
Supports execution of multiple simultaneous operations.
File: DownloadService.swift. Keeps reference to URLSession and tracks executing tasks.
final class DownloadService: NSObject {
private var session: URLSession!
private var downloadTasks = [GenericDownloadTask]()
public static let shared = DownloadService()
private override init() {
super.init()
let configuration = URLSessionConfiguration.default
session = URLSession(configuration: configuration,
delegate: self, delegateQueue: nil)
}
func download(request: URLRequest) -> DownloadTask {
let task = session.dataTask(with: request)
let downloadTask = GenericDownloadTask(task: task)
downloadTasks.append(downloadTask)
return downloadTask
}
}
extension DownloadService: URLSessionDataDelegate {
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
guard let task = downloadTasks.first(where: { $0.task == dataTask }) else {
completionHandler(.cancel)
return
}
task.expectedContentLength = response.expectedContentLength
completionHandler(.allow)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
guard let task = downloadTasks.first(where: { $0.task == dataTask }) else {
return
}
task.buffer.append(data)
let percentageDownloaded = Double(task.buffer.count) / Double(task.expectedContentLength)
DispatchQueue.main.async {
task.progressHandler?(percentageDownloaded)
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let index = downloadTasks.index(where: { $0.task == task }) else {
return
}
let task = downloadTasks.remove(at: index)
DispatchQueue.main.async {
if let e = error {
task.completionHandler?(.failure(e))
} else {
task.completionHandler?(.success(task.buffer))
}
}
}
}
File: DownloadTask.swift. Lightweight interface just to hide concrete implementation.
protocol DownloadTask {
var completionHandler: ResultType<Data>.Completion? { get set }
var progressHandler: ((Double) -> Void)? { get set }
func resume()
func suspend()
func cancel()
}
File: GenericDownloadTask.swift. Concrete implementation of DownloadTask interface.
class GenericDownloadTask {
var completionHandler: ResultType<Data>.Completion?
var progressHandler: ((Double) -> Void)?
private(set) var task: URLSessionDataTask
var expectedContentLength: Int64 = 0
var buffer = Data()
init(task: URLSessionDataTask) {
self.task = task
}
deinit {
print("Deinit: \(task.originalRequest?.url?.absoluteString ?? "")")
}
}
extension GenericDownloadTask: DownloadTask {
func resume() {
task.resume()
}
func suspend() {
task.suspend()
}
func cancel() {
task.cancel()
}
}
File: ResultType.swift. Reusable type to keep result or error.
public enum ResultType<T> {
public typealias Completion = (ResultType<T>) -> Void
case success(T)
case failure(Swift.Error)
}
Usage: Example how to run two download tasks in parallel (macOS App):
class ViewController: NSViewController {
#IBOutlet fileprivate weak var loadImageButton1: NSButton!
#IBOutlet fileprivate weak var loadProgressIndicator1: NSProgressIndicator!
#IBOutlet fileprivate weak var imageView1: NSImageView!
#IBOutlet fileprivate weak var loadImageButton2: NSButton!
#IBOutlet fileprivate weak var loadProgressIndicator2: NSProgressIndicator!
#IBOutlet fileprivate weak var imageView2: NSImageView!
fileprivate var downloadTask1: DownloadTask?
fileprivate var downloadTask2: DownloadTask?
override func viewDidLoad() {
super.viewDidLoad()
loadImageButton1.target = self
loadImageButton1.action = #selector(startDownload1(_:))
loadImageButton2.target = self
loadImageButton2.action = #selector(startDownload2(_:))
}
}
extension ViewController {
#objc fileprivate func startDownload1(_ button: NSButton) {
let url = URL(string: "http://localhost:8001/?imageID=01&tilestamp=\(Date.timeIntervalSinceReferenceDate)")!
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
downloadTask1 = DownloadService.shared.download(request: request)
downloadTask1?.completionHandler = { [weak self] in
switch $0 {
case .failure(let error):
print(error)
case .success(let data):
print("Number of bytes: \(data.count)")
self?.imageView1.image = NSImage(data: data)
}
self?.downloadTask1 = nil
self?.loadImageButton1.isEnabled = true
}
downloadTask1?.progressHandler = { [weak self] in
print("Task1: \($0)")
self?.loadProgressIndicator1.doubleValue = $0
}
loadImageButton1.isEnabled = false
imageView1.image = nil
loadProgressIndicator1.doubleValue = 0
downloadTask1?.resume()
}
#objc fileprivate func startDownload2(_ button: NSButton) {
let url = URL(string: "http://localhost:8002/?imageID=02&tilestamp=\(Date.timeIntervalSinceReferenceDate)")!
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
downloadTask2 = DownloadService.shared.download(request: request)
downloadTask2?.completionHandler = { [weak self] in
switch $0 {
case .failure(let error):
print(error)
case .success(let data):
print("Number of bytes: \(data.count)")
self?.imageView2.image = NSImage(data: data)
}
self?.downloadTask2 = nil
self?.loadImageButton2.isEnabled = true
}
downloadTask2?.progressHandler = { [weak self] in
print("Task2: \($0)")
self?.loadProgressIndicator2.doubleValue = $0
}
loadImageButton2.isEnabled = false
imageView2.image = nil
loadProgressIndicator2.doubleValue = 0
downloadTask2?.resume()
}
}
Bonus 1. File StartPHPWebServer.command. Example script to run 2 Build-in PHP servers to simulate simultaneous downloads.
#!/bin/bash
AWLScriptDirPath=$(cd "$(dirname "$0")"; pwd)
cd "$AWLScriptDirPath"
php -S localhost:8001 &
php -S localhost:8002 &
ps -afx | grep php
echo "Press ENTER to exit."
read
killall php
Bonus 2. File index.php. Example PHP script to implement slow download.
<?php
$imageID = $_REQUEST["imageID"];
$local_file = "Image-$imageID.jpg";
$download_rate = 20.5; // set the download rate limit (=> 20,5 kb/s)
if (file_exists($local_file) && is_file($local_file)) {
header('Cache-control: private');
header('Content-Type: image/jpeg');
header('Content-Length: '.filesize($local_file));
flush();
$file = fopen($local_file, "r");
while(!feof($file)) {
// send the current file part to the browser
print fread($file, round($download_rate * 1024));
flush(); // flush the content to the browser
usleep(0.25 * 1000000);
}
fclose($file);}
else {
die('Error: The file '.$local_file.' does not exist!');
}
?>
Misc: Contents of directory which simulates 2 PHP servers.
Image-01.jpg
Image-02.jpg
StartPHPWebServer.command
index.php
in class declare
class AudioPlayerViewController: UIViewController, URLSessionDelegate, URLSessionDataDelegate, URLSessionDownloadDelegate{
var defaultSession: URLSession!
var downloadTask: URLSessionDownloadTask!
in download method
defaultSession = Foundation.URLSession(configuration: .default, delegate: self, delegateQueue: nil)
downloadProgress.setProgress(0.0, animated: false)
downloadTask = defaultSession.downloadTask(with: audioUrl)
downloadTask.Resume()
And add following delegates:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
DispatchQueue.main.async {
self.downloadProgressBar.setProgress(Float(totalBytesWritten)/Float(totalBytesExpectedToWrite), animated: true)
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
DispatchQueue.main.async {
//DOWNLOAD SUCCESSFUL AND FILE PATH WILL BE IN URL.
}
}
In reference to Dharmesh's work, there have been quite a number of minor changes with URLSessionDelegate and URLSessionDataDelegate delegate methods. Here come the code for Swift 4.2 compatibility.
import UIKit
class ViewController: UIViewController, URLSessionDelegate, URLSessionDataDelegate {
// MARK: - Variables
// MARK: - IBOutlet
#IBOutlet weak var progress: UIProgressView!
// MARK: - IBAction
#IBAction func goTapped(_ sender: UIButton) {
let url = URL(string: "http://www.example.com/file.zip")!
fetchFile(url: url)
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
func fetchFile(url: URL) {
progress.progress = 0.0
let configuration = URLSessionConfiguration.default
let mainQueue = OperationQueue.main
session = URLSession(configuration: configuration, delegate: self, delegateQueue: mainQueue)
dataTask = session?.dataTask(with: URLRequest(url: url))
dataTask?.resume()
}
var buffer: NSMutableData = NSMutableData()
var session: URLSession?
var dataTask: URLSessionDataTask?
var expectedContentLength = 0
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
buffer.append(data)
let percentageDownloaded = Float(buffer.length) / Float(expectedContentLength)
progress.progress = percentageDownloaded
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: (URLSession.ResponseDisposition) -> Void) {
expectedContentLength = Int(response.expectedContentLength)
completionHandler(URLSession.ResponseDisposition.allow)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
progress.progress = 1.0
}
}
for the data been download you need to set the NSURLSessionDownloadDelegate and to implement URLSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)
There is a nice tutorial about it here but is in object-c.

How to use URLSession delegate with custom class

I'm used to making a custom class for URLSession using singleton. So I'm using it with URLSession delegate first time and I'm getting confused. Because delegate method is not called!
Can I know any solution? Did I miss something?
And I just want to check whether making a custom class for URLSession is a good thing?
Here is my code. This is my custom API for URLSession.
import UIKit
class CustomNetworkAPI {
static let shared = CustomNetworkAPI()
var session: URLSession?
private var sessionDataTask: URLSessionDataTask?
private var sessionDownloadTask: URLSessionDownloadTask?
var cache: URLCache?
private init() {}
func downloadTaskForImage(_ url: URL, _ completionHandler: #escaping (Result<URL, Error>) -> ()) {
// print(session.delegate)
sessionDownloadTask = session?.downloadTask(with: url, completionHandler: { (url, response, error) in
if let error = error {
completionHandler(.failure(error))
return
}
guard let url = url else {
completionHandler(.failure(URLRequestError.dataError))
return
}
completionHandler(.success(url))
})
sessionDownloadTask?.resume()
}
}
And this is sample code(not whole thing) of VC using the api and delegate.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let api = CustomNetworkAPI.shared
let config = URLSessionConfiguration.default
api.session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
guard let url = URL(string: "...") else { return }
api.downloadTaskForImage(url) { (result) in
switch result {
case .success(let url):
print(url)
case .failure(let error):
print(error)
}
}
}
}
extension ViewController: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("down load complete")
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
print("ing")
}
}
I was missing something, which is this quote below that I found in apple docs.
Your URLSession object doesn’t need to have a delegate. If no delegate is assigned, a system-provided delegate is used, and you must provide a completion callback to obtain the data.
So I tried to remove the completion handler block, and the delegate method was called.

Can't get WatchKit URLSession background to work

I have a file for a BackgroundSession class
class BackgroundSession: NSObject {
static let shared = BackgroundSession()
static let identifier = "com.***.bg"
private var session: URLSession!
var savedCompletionHandler: (() -> Void)?
private override init() {
super.init()
let configuration = URLSessionConfiguration.background(withIdentifier: BackgroundSession.identifier)
session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
func start(_ request: URLRequest) {
session.downloadTask(with: request).resume()
}
}
extension BackgroundSession: URLSessionDelegate {
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
self.savedCompletionHandler?()
self.savedCompletionHandler = nil
}
}
}
extension BackgroundSession: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
// handle failure here
print("\(error.localizedDescription)")
}
}
}
extension BackgroundSession: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
do {
let data = try Data(contentsOf: location)
let json = try JSONSerialization.jsonObject(with: data)
print("\(json)")
// do something with json
} catch {
print("\(error.localizedDescription)")
}
}
}
I am listening for background location updates to come in later on
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("didUpdateLocations")
if locations.first != nil {
let lastLocation = locations.last
self.lastLocation = lastLocation
print("doing background work")
self.getUserData()
if PubnubController.pubnubChannel != nil {
PubnubController.sharedClient.publish(["action": "newCoordinates", "data": ["coordinates": ["latitude": lastLocation?.coordinate.latitude, "longitude": lastLocation?.coordinate.longitude]]], toChannel: PubnubController.pubnubChannel!, compressed: false)
}
}
}
self.getUserData() looks like this
func getUserData() {
print("getUserData")
if (self.userId != -1 && self.userAuthToken != nil) {
let httpUrl: String = "https://api.***.com/dev/users/\(self.userId)"
guard let url = URL(string: httpUrl) else {
return
}
var request = URLRequest(url: url)
request.setValue(self.userAuthToken, forHTTPHeaderField: "Authorization")
let session = BackgroundSession.shared
session.start(request)
}
}
In my ExtensionDelegate.swift I have the typical func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>)
with a for loop and switch set with a case for WKURLSessionRefreshBackgroundTask that looks like this
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
print("WKURLSessionRefreshBackgroundTask")
// Be sure to complete the URL session task once you’re done.
urlSessionTask.setTaskCompletedWithSnapshot(false)
In my controller, I also have pasted the function the class is supposed to call
func application(_ application: WKExtension, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
print("handleEventsForBackgroundURLSession")
BackgroundSession.shared.savedCompletionHandler = parseUserData
}
It seems that both the delegate function and this pasted function are not being called with my data. I'm having a really hard time trying to understand this background URLSession flow
Note the BackgroundSession class came from this Stackoverflow question
URLSession.datatask with request block not called in background
This handleEventsForBackgroundURLSession is an iOS pattern. This is a method of the UIApplicationDelegate protocol. You can’t just add that to some random controller. It’s only applicable for your iOS app's UIApplicationDelegate.
For watchOS, I suspect the idea is the same, except rather than calling the completion handler that iOS provides, supply your own completion handler to the BackgroundSession that calls the setTaskCompletedWithSnapshot of the WKURLSessionRefreshBackgroundTask:
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
// Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
for task in backgroundTasks {
// Use a switch statement to check the task type
switch task {
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
// Be sure to complete the URL session task once you’re done.
BackgroundSession.shared.savedCompletionHandler = {
urlSessionTask.setTaskCompletedWithSnapshot(false)
}
...
}
}
}
But, effectively, the idea is the same. We’re deferring the setTaskCompletedWithSnapshot until urlSessionDidFinishEvents(forBackgroundURLSession:) is called.
If you want the BackgroundSession to call your controller’s parser, you can specify a protocol for that interface:
protocol Parser: class {
func parse(_ data: Data)
}
You can then give your BackgroundSession a property to keep track of the parser:
weak var parser: Parser?
You can have the didFinishDownloadingTo call the parser:
extension BackgroundSession: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
do {
let data = try Data(contentsOf: location)
parser?.parse(data)
} catch {
os_log(.error, log: log, "Error retrieving data for %{public}#: %{public}#", downloadTask.originalRequest?.url?.absoluteString ?? "Unknown request", error.localizedDescription)
}
}
}
You can then have your controller (or whatever) (a) conform to this protocol; (b) implement the parse(_:) method of that protocol; and (c) specify itself as the parser:
BackgroundSession.shared.parser = self

URLSession delegates not working after app resume

I've recently been integrating Background Transfer Service into an application so that the user is able to download files in the background.
Everything works as expected. But my delegate methods stops getting called after sending the application into the background and then re-opening the application.
File is actually being downloaded in the background but I am not receiving any call to my delegate methods. So cant show any progress to the users. So it feels like download is got stuck.
I had to remove our app from the app store as it is hurting our app. I need to resubmit the app as soon as possible. But with this problem, it's not possible.
My download manager code:
import Foundation
import Zip
import UserNotifications
////------------------------------------------------------
//// MARK: - Download Progress Struct
////------------------------------------------------------
public struct DownloadProgress {
public let name: String
public let progress: Float
public let completedUnitCount: Float
public let totalUnitCount: Float
}
protocol DownloadDelegate: class {
func downloadProgressUpdate(for progress: DownloadProgress)
func unzipProgressUpdate(for progress: Double)
func onFailure()
}
class DownloadManager : NSObject, URLSessionDownloadDelegate {
//------------------------------------------------------
// MARK: - Downloader Properties
//------------------------------------------------------
static var shared = DownloadManager()
private lazy var session: URLSession = {
let config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).bookDownloader")
config.isDiscretionary = true
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
var delegate: DownloadDelegate?
var previousUrl: URL?
var resumeData: Data?
var task: URLSessionDownloadTask?
// ProgressHandler --> identifier, progress, completedUnitCount, totalUnitCount
typealias ProgressHandler = (String, Float, Float, Float) -> ()
//------------------------------------------------------
// MARK: - Downloader Initializer
//------------------------------------------------------
override private init() {
super.init()
}
func activate() -> URLSession {
// Warning: If an URLSession still exists from a previous download, it doesn't create a new URLSession object but returns the existing one with the old delegate object attached!
return session
}
//------------------------------------------------------
// MARK: - Downloader start download
//------------------------------------------------------
func startDownload(url: URL) {
if let previousUrl = self.previousUrl {
if url == previousUrl {
if let data = resumeData {
let downloadTask = session.downloadTask(withResumeData: data)
downloadTask.resume()
self.task = downloadTask
} else {
let downloadTask = session.downloadTask(with: url)
downloadTask.resume()
self.task = downloadTask
}
} else {
let downloadTask = session.downloadTask(with: url)
downloadTask.resume()
self.task = downloadTask
}
} else {
let downloadTask = session.downloadTask(with: url)
downloadTask.resume()
self.task = downloadTask
}
}
//------------------------------------------------------
// MARK: - Downloader stop download
//------------------------------------------------------
func stopDownload() {
if let task = task {
task.cancel { resumeDataOrNil in
guard let resumeData = resumeDataOrNil else {
// download can't be resumed; remove from UI if necessary
return
}
self.resumeData = resumeData
}
}
}
//------------------------------------------------------
// MARK: - Downloader Progress Calculator
//------------------------------------------------------
private func calculateProgress(session : URLSession, completionHandler : #escaping ProgressHandler) {
session.getTasksWithCompletionHandler { (tasks, uploads, downloads) in
let progress = downloads.map({ (task) -> Float in
if task.countOfBytesExpectedToReceive > 0 {
return Float(task.countOfBytesReceived) / Float(task.countOfBytesExpectedToReceive)
} else {
return 0.0
}
})
let countOfBytesReceived = downloads.map({ (task) -> Float in
return Float(task.countOfBytesReceived)
})
let countOfBytesExpectedToReceive = downloads.map({ (task) -> Float in
return Float(task.countOfBytesExpectedToReceive)
})
if let name = UserDefaults.standard.string(forKey: UserDefaultKeys.OnBookDownload) {
if name.isEmpty {
return self.session.invalidateAndCancel()
}
completionHandler(name, progress.reduce(0.0, +), countOfBytesReceived.reduce(0.0, +), countOfBytesExpectedToReceive.reduce(0.0, +))
}
}
}
//------------------------------------------------------
// MARK: - Downloader Notifiers
//------------------------------------------------------
func postUnzipProgress(progress: Double) {
if let delegate = self.delegate {
delegate.unzipProgressUpdate(for: progress)
}
// NotificationCenter.default.post(name: .UnzipProgress, object: progress)
}
func postDownloadProgress(progress: DownloadProgress) {
if let delegate = self.delegate {
delegate.downloadProgressUpdate(for: progress)
}
// NotificationCenter.default.post(name: .BookDownloadProgress, object: progress)
}
func postNotification() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
// Enable or disable features based on authorization.
}
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: "Download Completed".localized(), arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: "Quran Touch app is ready to use".localized(), arguments: nil)
content.sound = UNNotificationSound.default()
content.categoryIdentifier = "com.qurantouch.qurantouch.BookDownloadComplete"
// Deliver the notification in 60 seconds.
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 2.0, repeats: false)
let request = UNNotificationRequest.init(identifier: "BookDownloadCompleted", content: content, trigger: trigger)
// Schedule the notification.
center.add(request)
}
//------------------------------------------------------
// MARK: - Downloader Delegate methods
//------------------------------------------------------
// On Progress Update
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if let name = UserDefaults.standard.string(forKey: UserDefaultKeys.OnBookDownload) {
if name.isEmpty {
return self.session.invalidateAndCancel()
}
} else {
return self.session.invalidateAndCancel()
}
if totalBytesExpectedToWrite > 0 {
calculateProgress(session: session, completionHandler: { (name, progress, completedUnitCount, totalUnitCount) in
let progressInfo = DownloadProgress(name: name, progress: progress, completedUnitCount: completedUnitCount, totalUnitCount: totalUnitCount)
print(progressInfo.progress)
self.postDownloadProgress(progress: progressInfo)
})
}
}
// On Successful Download
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
if let name = UserDefaults.standard.string(forKey: UserDefaultKeys.OnBookDownload) {
if name.isEmpty {
return self.session.invalidateAndCancel()
}
let folder = URL.createFolder(folderName: "\(Config.bookFolder)\(name)")
let fileURL = folder!.appendingPathComponent("\(name).zip")
if let url = URL.getFolderUrl(folderName: "\(Config.bookFolder)\(name)") {
do {
try FileManager.default.moveItem(at: location, to: fileURL)
// Download completed. Now time to unzip the file
try Zip.unzipFile((fileURL), destination: url, overwrite: true, password: nil, progress: { (progress) -> () in
if progress == 1 {
App.quranDownloaded = true
UserDefaults.standard.set("selected", forKey: name)
DispatchQueue.main.async {
Reciter().downloadCompleteReciter(success: true).done{_ in}.catch{_ in}
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let backgroundCompletionHandler =
appDelegate.backgroundCompletionHandler else {
return
}
backgroundCompletionHandler()
self.postNotification()
}
// Select the book that is downloaded
// Delete the downlaoded zip file
URL.removeFile(file: fileURL)
}
self.postUnzipProgress(progress: progress)
}, fileOutputHandler: {(outputUrl) -> () in
})
} catch {
print(error)
}
}
} else {
return self.session.invalidateAndCancel()
}
}
// On Dwonload Completed with Failure
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
debugPrint("Task completed: \(task), error: \(error)")
guard let error = error else {
// Handle success case.
return
}
let userInfo = (error as NSError).userInfo
if let resumeData = userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
self.resumeData = resumeData
}
if let delegate = self.delegate {
if !error.isCancelled {
delegate.onFailure()
}
}
}
// On Dwonload Invalidated with Error
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
guard let error = error else {
// Handle success case.
return
}
if let delegate = self.delegate {
if !error.isCancelled {
delegate.onFailure()
}
}
}
}
// MARK: - URLSessionDelegate
extension DownloadManager: URLSessionDelegate {
// Standard background session handler
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let completionHandler = appDelegate.backgroundCompletionHandler {
completionHandler()
appDelegate.backgroundCompletionHandler = nil
}
}
}
}
And in app delegate:
var backgroundCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
backgroundCompletionHandler = completionHandler
}
Finally found a workaround for the issue. Once the application did return from background mode, make sure to call resume on all running tasks. This seems to reactivate callbacks to the delegate.
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
DownloadManager.shared.session.getAllTasks(completionHandler: { tasks in
for task in tasks {
task.resume()
}
})
}
For more information on this topic, Follow this link:
https://forums.developer.apple.com/thread/77666
You need to resume all tasks once you come back to active state.
URLSession.shared.getAllTasks { (tasks) in
for task in tasks
{
task.resume()
}
}

get progress from dataTaskWithURL in swift

Is there any way to get progress from dataTaskWithURL in swift while the data is downloading?
NSURLSession.sharedSession().dataTaskWithURL(...)
I need to show progress bar while the data is downloading.
You can simply observe progress property of the URLSessionDataTask object.
Example:
import UIKit
class SomeViewController: UIViewController {
private var observation: NSKeyValueObservation?
deinit {
observation?.invalidate()
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://source.unsplash.com/random/4000x4000")!
let task = URLSession.shared.dataTask(with: url)
observation = task.progress.observe(\.fractionCompleted) { progress, _ in
print("progress: ", progress.fractionCompleted)
}
task.resume()
}
}
Playground example:
import Foundation
import PlaygroundSupport
let page = PlaygroundPage.current
page.needsIndefiniteExecution = true
let url = URL(string: "https://source.unsplash.com/random/4000x4000")!
let task = URLSession.shared.dataTask(with: url) { _, _, _ in
page.finishExecution()
}
// Don't forget to invalidate the observation when you don't need it anymore.
let observation = task.progress.observe(\.fractionCompleted) { progress, _ in
print(progress.fractionCompleted)
}
task.resume()
you can use this code for showing download process with progress bar with its delegate functions.
import UIKit
class ViewController: UIViewController,NSURLSessionDelegate,NSURLSessionDataDelegate{
#IBOutlet weak var progress: UIProgressView!
var buffer:NSMutableData = NSMutableData()
var session:NSURLSession?
var dataTask:NSURLSessionDataTask?
let url = NSURL(string:"http://i.stack.imgur.com/b8zkg.png" )!
var expectedContentLength = 0
override func viewDidLoad() {
super.viewDidLoad()
progress.progress = 0.0
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let manqueue = NSOperationQueue.mainQueue()
session = NSURLSession(configuration: configuration, delegate:self, delegateQueue: manqueue)
dataTask = session?.dataTaskWithRequest(NSURLRequest(URL: url))
dataTask?.resume()
// Do any additional setup after loading the view, typically from a nib.
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
//here you can get full lenth of your content
expectedContentLength = Int(response.expectedContentLength)
println(expectedContentLength)
completionHandler(NSURLSessionResponseDisposition.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
buffer.appendData(data)
let percentageDownloaded = Float(buffer.length) / Float(expectedContentLength)
progress.progress = percentageDownloaded
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
//use buffer here.Download is done
progress.progress = 1.0 // download 100% complete
}
}
Update for Swift4:
Supports execution of multiple simultaneous operations.
File: DownloadService.swift. Keeps reference to URLSession and tracks executing tasks.
final class DownloadService: NSObject {
private var session: URLSession!
private var downloadTasks = [GenericDownloadTask]()
public static let shared = DownloadService()
private override init() {
super.init()
let configuration = URLSessionConfiguration.default
session = URLSession(configuration: configuration,
delegate: self, delegateQueue: nil)
}
func download(request: URLRequest) -> DownloadTask {
let task = session.dataTask(with: request)
let downloadTask = GenericDownloadTask(task: task)
downloadTasks.append(downloadTask)
return downloadTask
}
}
extension DownloadService: URLSessionDataDelegate {
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
guard let task = downloadTasks.first(where: { $0.task == dataTask }) else {
completionHandler(.cancel)
return
}
task.expectedContentLength = response.expectedContentLength
completionHandler(.allow)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
guard let task = downloadTasks.first(where: { $0.task == dataTask }) else {
return
}
task.buffer.append(data)
let percentageDownloaded = Double(task.buffer.count) / Double(task.expectedContentLength)
DispatchQueue.main.async {
task.progressHandler?(percentageDownloaded)
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let index = downloadTasks.index(where: { $0.task == task }) else {
return
}
let task = downloadTasks.remove(at: index)
DispatchQueue.main.async {
if let e = error {
task.completionHandler?(.failure(e))
} else {
task.completionHandler?(.success(task.buffer))
}
}
}
}
File: DownloadTask.swift. Lightweight interface just to hide concrete implementation.
protocol DownloadTask {
var completionHandler: ResultType<Data>.Completion? { get set }
var progressHandler: ((Double) -> Void)? { get set }
func resume()
func suspend()
func cancel()
}
File: GenericDownloadTask.swift. Concrete implementation of DownloadTask interface.
class GenericDownloadTask {
var completionHandler: ResultType<Data>.Completion?
var progressHandler: ((Double) -> Void)?
private(set) var task: URLSessionDataTask
var expectedContentLength: Int64 = 0
var buffer = Data()
init(task: URLSessionDataTask) {
self.task = task
}
deinit {
print("Deinit: \(task.originalRequest?.url?.absoluteString ?? "")")
}
}
extension GenericDownloadTask: DownloadTask {
func resume() {
task.resume()
}
func suspend() {
task.suspend()
}
func cancel() {
task.cancel()
}
}
File: ResultType.swift. Reusable type to keep result or error.
public enum ResultType<T> {
public typealias Completion = (ResultType<T>) -> Void
case success(T)
case failure(Swift.Error)
}
Usage: Example how to run two download tasks in parallel (macOS App):
class ViewController: NSViewController {
#IBOutlet fileprivate weak var loadImageButton1: NSButton!
#IBOutlet fileprivate weak var loadProgressIndicator1: NSProgressIndicator!
#IBOutlet fileprivate weak var imageView1: NSImageView!
#IBOutlet fileprivate weak var loadImageButton2: NSButton!
#IBOutlet fileprivate weak var loadProgressIndicator2: NSProgressIndicator!
#IBOutlet fileprivate weak var imageView2: NSImageView!
fileprivate var downloadTask1: DownloadTask?
fileprivate var downloadTask2: DownloadTask?
override func viewDidLoad() {
super.viewDidLoad()
loadImageButton1.target = self
loadImageButton1.action = #selector(startDownload1(_:))
loadImageButton2.target = self
loadImageButton2.action = #selector(startDownload2(_:))
}
}
extension ViewController {
#objc fileprivate func startDownload1(_ button: NSButton) {
let url = URL(string: "http://localhost:8001/?imageID=01&tilestamp=\(Date.timeIntervalSinceReferenceDate)")!
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
downloadTask1 = DownloadService.shared.download(request: request)
downloadTask1?.completionHandler = { [weak self] in
switch $0 {
case .failure(let error):
print(error)
case .success(let data):
print("Number of bytes: \(data.count)")
self?.imageView1.image = NSImage(data: data)
}
self?.downloadTask1 = nil
self?.loadImageButton1.isEnabled = true
}
downloadTask1?.progressHandler = { [weak self] in
print("Task1: \($0)")
self?.loadProgressIndicator1.doubleValue = $0
}
loadImageButton1.isEnabled = false
imageView1.image = nil
loadProgressIndicator1.doubleValue = 0
downloadTask1?.resume()
}
#objc fileprivate func startDownload2(_ button: NSButton) {
let url = URL(string: "http://localhost:8002/?imageID=02&tilestamp=\(Date.timeIntervalSinceReferenceDate)")!
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
downloadTask2 = DownloadService.shared.download(request: request)
downloadTask2?.completionHandler = { [weak self] in
switch $0 {
case .failure(let error):
print(error)
case .success(let data):
print("Number of bytes: \(data.count)")
self?.imageView2.image = NSImage(data: data)
}
self?.downloadTask2 = nil
self?.loadImageButton2.isEnabled = true
}
downloadTask2?.progressHandler = { [weak self] in
print("Task2: \($0)")
self?.loadProgressIndicator2.doubleValue = $0
}
loadImageButton2.isEnabled = false
imageView2.image = nil
loadProgressIndicator2.doubleValue = 0
downloadTask2?.resume()
}
}
Bonus 1. File StartPHPWebServer.command. Example script to run 2 Build-in PHP servers to simulate simultaneous downloads.
#!/bin/bash
AWLScriptDirPath=$(cd "$(dirname "$0")"; pwd)
cd "$AWLScriptDirPath"
php -S localhost:8001 &
php -S localhost:8002 &
ps -afx | grep php
echo "Press ENTER to exit."
read
killall php
Bonus 2. File index.php. Example PHP script to implement slow download.
<?php
$imageID = $_REQUEST["imageID"];
$local_file = "Image-$imageID.jpg";
$download_rate = 20.5; // set the download rate limit (=> 20,5 kb/s)
if (file_exists($local_file) && is_file($local_file)) {
header('Cache-control: private');
header('Content-Type: image/jpeg');
header('Content-Length: '.filesize($local_file));
flush();
$file = fopen($local_file, "r");
while(!feof($file)) {
// send the current file part to the browser
print fread($file, round($download_rate * 1024));
flush(); // flush the content to the browser
usleep(0.25 * 1000000);
}
fclose($file);}
else {
die('Error: The file '.$local_file.' does not exist!');
}
?>
Misc: Contents of directory which simulates 2 PHP servers.
Image-01.jpg
Image-02.jpg
StartPHPWebServer.command
index.php
in class declare
class AudioPlayerViewController: UIViewController, URLSessionDelegate, URLSessionDataDelegate, URLSessionDownloadDelegate{
var defaultSession: URLSession!
var downloadTask: URLSessionDownloadTask!
in download method
defaultSession = Foundation.URLSession(configuration: .default, delegate: self, delegateQueue: nil)
downloadProgress.setProgress(0.0, animated: false)
downloadTask = defaultSession.downloadTask(with: audioUrl)
downloadTask.Resume()
And add following delegates:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
DispatchQueue.main.async {
self.downloadProgressBar.setProgress(Float(totalBytesWritten)/Float(totalBytesExpectedToWrite), animated: true)
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
DispatchQueue.main.async {
//DOWNLOAD SUCCESSFUL AND FILE PATH WILL BE IN URL.
}
}
In reference to Dharmesh's work, there have been quite a number of minor changes with URLSessionDelegate and URLSessionDataDelegate delegate methods. Here come the code for Swift 4.2 compatibility.
import UIKit
class ViewController: UIViewController, URLSessionDelegate, URLSessionDataDelegate {
// MARK: - Variables
// MARK: - IBOutlet
#IBOutlet weak var progress: UIProgressView!
// MARK: - IBAction
#IBAction func goTapped(_ sender: UIButton) {
let url = URL(string: "http://www.example.com/file.zip")!
fetchFile(url: url)
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
func fetchFile(url: URL) {
progress.progress = 0.0
let configuration = URLSessionConfiguration.default
let mainQueue = OperationQueue.main
session = URLSession(configuration: configuration, delegate: self, delegateQueue: mainQueue)
dataTask = session?.dataTask(with: URLRequest(url: url))
dataTask?.resume()
}
var buffer: NSMutableData = NSMutableData()
var session: URLSession?
var dataTask: URLSessionDataTask?
var expectedContentLength = 0
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
buffer.append(data)
let percentageDownloaded = Float(buffer.length) / Float(expectedContentLength)
progress.progress = percentageDownloaded
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: (URLSession.ResponseDisposition) -> Void) {
expectedContentLength = Int(response.expectedContentLength)
completionHandler(URLSession.ResponseDisposition.allow)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
progress.progress = 1.0
}
}
for the data been download you need to set the NSURLSessionDownloadDelegate and to implement URLSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)
There is a nice tutorial about it here but is in object-c.

Resources