Default implementation of protocol method with Swift extension - ios

I'm trying to write default behaviour for a delegate method using a Swift extension as below, but it is never called. Does anyone know why or how to do it the right way?
extension NSURLSessionDelegate {
public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
//default behaviour here
}
}
Adding override does not work either.
According to this,
Apple's default implementation looks like:
extension NSURLSessionDelegate {
func URLSession(session: NSURLSession, didBecomeInvalidWithError error: NSError?) { }
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) { }
}
My DataTask calls typically look like this:
let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
sessionConfiguration.HTTPCookieStorage = NSHTTPCookieStorage.sharedHTTPCookieStorage()
let session = NSURLSession(configuration: sessionConfiguration)
let requestURL = NSURL(string:"https:www.google.com/blabla")
session.dataTaskWithURL(requestURL!, completionHandler: completion).resume()
Where completion will typically be a Swift closure received via parameter.
I need to implement the URLSession(... didReceiveChallenge ...) function for all nsurlsessiontask implementations throughout my app, but can't set my session's delegate as I need to use the completionHandler (as mentioned in my comment below).

You can extends the NSURLSessionDelegate protocol for adding default implementation, but your NSURLSession objects needs a delegate.
This delegate can only be set using +sessionWithConfiguration:delegate:delegateQueue: (since the delegate property is read only), so your only way to set it is to subclass NSURLSession, override +sessionWithConfiguration: and call the initializer with the delegate property. The issue here is that you have to replace all your NSURLSession objects to MyCustomSessionClass. objects.
I suggest you to create a SessionCreator class which will conforms to NSURLSessionDelegate protocol and will create NSURLSessionobjects. You still have to replace the creation of your objects, but at least the object isn't the delegate of itself.
public class SessionCreator:NSObject,NSURLSessionDelegate {
//MARK: - Singleton method
class var sharedInstance :SessionCreator {
struct Singleton {
static let instance = SessionCreator()
}
return Singleton.instance
}
//MARK: - Public class method
public class func createSessionWithConfiguration (configuration:NSURLSessionConfiguration) -> NSURLSession {
return sharedInstance.createSessionWithConfiguration(configuration)
}
//MARK: - Private methods
private func createSessionWithConfiguration (configuration:NSURLSessionConfiguration) -> NSURLSession {
return NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
//MARK: - NSURLSessionDelegate protocol conformance
public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
// Always called since it's the delegate of all NSURLSession created using createSessionWithConfiguration
}
}
// Let create a NSURLSession object :
let session = SessionCreator.createSessionWithConfiguration(NSURLSessionConfiguration())

Related

URLSession in swift 4 without completion handler

can anyone tell me what all URLSession delegate methods should me implemented along with an example in swift 4.
I DO NOT WANT TO USE COMPLETION HANDLER.
My app is in objective c, and i am converting it in swift.
earlier it uses NSURLConnection which is now deprecated.
So it's little confusing for me to use URLSession.
Thanks!
EDIT:
I am using something like
let defaultSession = URLSession(configuration: defaultSessionConfiguration, delegate: self, delegateQueue: nil)
let sessionTask = defaultSession.dataTask(with: requests)
sessionTask.resume()
Now i want to implement methods which will handle senarios when it receives response, data or error.
I am looking for equivalent of
func connection(_ connection: NSURLConnection, didReceive data: Data){}
func connection(_ connection: NSURLConnection, didFailWithError error: Error){}
func connection(_ connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: URLProtectionSpace) -> Bool {
return protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
}
func connection(_ connection: NSURLConnection, didReceive challenge: URLAuthenticationChallenge) {
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
if let aTrust = challenge.protectionSpace.serverTrust {
challenge.sender?.use(URLCredential(trust: aTrust), for: challenge)
}
}
challenge.sender?.continueWithoutCredential(for: challenge)
}

show progress indicator for multiple image upload in UITableViewCell

I have UITableView with custom cells. I want to show progress indicator for multiple images upload.
I have tried reloadRowAtIndexPath method of UITableView but it not sufficient solution because cell is continuously blinks which looks weird.
Another one solution i found is to store reference of my progress indicator view placed in UITableViewCell in global variable and then modify it outside UITableView datasource methods, but in this solution i faced one problem which is i have to keep track of multiple progress indicator view objects of UITableViewCell which is difficult because UITableView datasource is two dimensional NSMutableArray(In short array inside array) so i don't have unique IndexPath.row because of multiple sections. So how can i manage objects of progress indicator view?
And also Is there any better solution to do this type of job?
Ok, so here is what I used in one of my projects when I could not find anything else.
Swift 3
Make a sub class of NSObject (because a sub class of URLSession won't let you set configuration and other parameters as the only designated initializer there is init()) that includes the information of the cell that started the upload process as in IndexPath and also a URLSession object.
Use this sub class to create new URLSession whenever you want to upload (I used uploadTask method of URLSession).
Create uploadTask and start uploading.
You will also have to make your own protocol methods that are called by normal protocol methods of URLSession, to send your custom sub class instead of URLSession object to the delegate you want.
Then in that delegate, you may check for the information of indexPath that is stored in the custom sub class you got from the previous step and update the appropriate cell.
The same could be achieved by using Notifications I guess.
Below is the screenshot of the sample application I wrote:
public class TestURLSession:NSObject, URLSessionTaskDelegate {
var cellIndexPath:IndexPath!
var urlSession:URLSession!
var urlSessionUploadTask:URLSessionUploadTask!
var testUrlSessionDelegate:TestURLSessionTaskDelegate!
init(configuration: URLSessionConfiguration, delegate: TestURLSessionTaskDelegate?, delegateQueue queue: OperationQueue?, indexPath:IndexPath){
super.init()
self.urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: queue)
self.cellIndexPath = indexPath
self.testUrlSessionDelegate = delegate
}
func uploadTask(with request: URLRequest, from bodyData: Data) -> URLSessionUploadTask{
let uploadTask = self.urlSession.uploadTask(with: request, from: bodyData)
self.urlSessionUploadTask = uploadTask
return uploadTask
}
public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64){
self.testUrlSessionDelegate.urlSession(self, task: self.urlSessionUploadTask, didSendBodyData: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend)
}
}
protocol TestURLSessionTaskDelegate : URLSessionDelegate {
func urlSession(_ session: TestURLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64)
}
Edits are welcome.
Here the solution which i applied, may be helpful to someone who wants same implementations as i want, without using third party library or classes.
I have created one custom UIView and design circular progress indicator using CALayer and some animations. This is not a big deal. But the thing which is difficult for me is i want this progress indicator in several cells which indicates multiple image progress in percentages.
So i have created one custom class with properties like
#property (nonatomic,retain) NSIndexPath *indexPath;
#property (nonatomic,strong) NSURLSessionTask *uploadtask;
Then i maintain one NSMutableArray which contains my custom class objects which has values for each uploadTask for currently uploading images and merged string which contains indexPath. Now i have track of my all uploading images so i have change uploaded percentage in my custom progress indicator UIView with help of indexPath values whenever i receive response from delegate method of NSURLSession.
I had a similar stuff to do where in which I wanted to download files and show progress bar. My idea was to create a Custom object which keep track of a particular download and all the cells will internally listen to the changes in this object. Each cell will have its own object uniquely identified by the task identifier. I have written a sample code in Swift 3 available in the below link (skeleton code also added)
FileDownloader
class DownLoadData: NSObject {
var fileTitle: String = ""
var downloadSource: String = ""
var downloadTask: URLSessionDownloadTask?
var taskResumeData: Data?
var downloadProgress: Float = 0.0
var isDownloading: Bool = false
var isDownloadComplete: Bool = false
var taskIdentifier: Int = 0
var groupDownloadON:Bool = false
var groupStopDownloadON:Bool = false
init(with title:String, and source:String){
self.fileTitle = title
self.downloadSource = source
super.init()
}
func startDownload(completion:#escaping (Result<Bool, Error>)->Void,progressHandler:#escaping (Float)->Void ){
}
func resumeDownload(completion:#escaping (Result<Bool, Error>)->Void,progressHandler:#escaping (Float)->Void ){
}
func pauseDownload(){
}
func stopDownload(){
if self.isDownloading{
}
}
func cleanUpHandlers(){
// remove the completion handlers from the network manager as resume is taken as a new task.
}
func handleDownloadSuccess(){
}
func handleDownloadError(){
}
}
Use URLSessionTaskDelegate method and do necessary calculation:
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64)
Below is a solution, that is tested for a single file upload. But you can modify it to support multiple file uploads. Make sure to add necessary IBOutlets and IBAction as necessary. The image is added in 'Assets.xcassets'.
My UI looks like below:
Below is the code for ViewController.
import UIKit
class UploadProgressViewController: UIViewController, URLSessionTaskDelegate {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var progressView: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
progressView.progress = 0.0
}
#IBAction func didTapOnStartUploadButton(_ sender: Any) {
startDownload()
}
func startDownload () {
// 1. Prepare data to download
var data = Data()
if let image = UIImage(named: "swift.jpg") {
data = image.pngData()!
}
// 2. Creation of request
var request = URLRequest(url: NSURL(string: "http://127.0.0.1:8000/swift.png")! as URL)
request.httpMethod = "POST"
request.setValue("Keep-Alive", forHTTPHeaderField: "Connection")
// 3. Configuring the Session
let configuration = URLSessionConfiguration.default
let mainqueue = OperationQueue.main
// 4. Start the upload task
let session = URLSession(configuration: configuration, delegate:self, delegateQueue: mainqueue)
let dataTask = session.uploadTask(with: request, from: data)
dataTask.resume()
}
// URLSessionTaskDelegate Handling
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
let uploadProgress: Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
print("session \(session) uploaded \(uploadProgress * 100)%.")
progressView.progress = uploadProgress
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print(error.debugDescription)
}

Identify NSURLSession in completion block

I want to identify a session within my didFinishDownloadingToURL method:
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL)
{
if (session.sessionType == EnumImageRequestSession)
{
// I want to check with sessionType, but NSURLSession does not have
// any such property. How to add this type property while creating the session?
}
if (session.sessionType == EnumAudioRequestSession)
{
}
}
How to achieve this? Should I create a subclass of NSURLSession and add a sessionType property?
Here are a few options that I have used in the past.
When you start the NSURLSession, you can add it to an Dictionary or Set with a key value associated to the Session. When it completes, find the Session using it's identifier in the Set, and then you will have your associated key.
You can check the URL associated with the session.
Option 1 is my tried and true method so far, using a Set.
As NSURLSession is an NSObject subclass, you can use an Objective-C associated object, and by doing so you avoid doing housekeeping of for instance a map of sessions by type that you would otherwise manually have to create.
Here's a short example (where I'm cutting some corners with forced unwraps I actually would not in production oriented code):
import Foundation
import ObjectiveC
enum SessionType:Int {
case Audio
case Image
}
func someFunctionWhereYouCreateTheSession() {
let session:NSURLSession = NSURLSession()
objc_setAssociatedObject(session, "sessionType",
NSNumber(integer:SessionType.Audio.rawValue), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL)
{
let sessionType:SessionType = SessionType(rawValue:(objc_getAssociatedObject(session, "sessionType") as! NSNumber).integerValue)!
switch sessionType {
case .Audio:
print("foo")
case .Image:
print("bar")
}
}

A background URLSession with identifier backgroundSession already exists

I'm fetching xml data from server by using NSURLSession and NSURLSessionDelegate. Depends on some conditions I'm connecting with server. If I'm connecting with server everything works fine without any error but if I'm not connecting (depends on condition) to server and moving to another View Controller (by using storyboard?.instantiateViewControllerWithIdentifier(id)) I'm getting the following IOS error:
'A background URLSession with identifier backgroundSession already exists!'
Here is my code:
class MainClass: UITableViewController, NSURLSessionDelegate {
var task_service = NSURLSessionDataTask?()
override func viewDidLoad() {
super.viewDidLoad()
if(condition) {
getXMLFromServer()
}
}
func getXMLFromServer(){
task_service = getURLSession().dataTaskWithRequest() {
(data, response, error) -> Void in
dispatch_async(dispatch_get_main_queue(), {
// Fetching data from server
// In the end
self.session.invalidateAndCancel()
}
}
}
func getURLSession() -> NSURLSession {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.timeoutIntervalForRequest = 30.0
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
return session
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)) // Bypassing SSL error
}
}
EDIT: Found the reason for the error.
Error occurred because of the creation of NSURLSession in the Called View Controller.Called VC contains code to download PDF from server. But I don't know how to solve this. Below is the code of Called VC
class MainFormsController: UIViewController, UIPickerViewDelegate, UITextFieldDelegate, NSURLSessionDownloadDelegate, UIDocumentInteractionControllerDelegate, MFMailComposeViewControllerDelegate{
var download_task = NSURLSessionDownloadTask?()
var backgroundSession = NSURLSession()
override func viewDidLoad() {
super.viewDidLoad()
createNSURLSession()
}
/** Error occurred while creating this NSURLSession **/
func createNSURLSession() {
let backgroundSessionConfiguration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("backgroundSession")
backgroundSession = NSURLSession(configuration: backgroundSessionConfiguration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
}
func downloadPDF() {
//Download PDF
download_task = backgroundSession.downloadTaskWithURL(url)
download_task?.resume()
}
}
Add this code in your MainFormsController:
deinit {
self.backgroundSession.finishTasksAndInvalidate();
}
Your code probably calls createNSURLSession() more than once which invalidate the NSURLSession behavior, as documentation says:
"You must create exactly one session per identifier (specified when
you create the configuration object). The behavior of multiple
sessions sharing the same identifier is undefined."
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/UsingNSURLSession.html
Make sure createNSURLSession is called only once (singletone) for the life cycle of your app.
I think you already have an URLSession with identifier backgroundSession. First Call
- (void)invalidateAndCancel
on that. and then try with your code.

NSURLProtocol. requestIsCacheEquivalent never called

I'm not sure what the deal is here, but the function:
class func requestIsCacheEquivalent(a: NSURLRequest, toRequest b: NSURLRequest) -> Bool
is never called within my NSURLProtocol subclass. I've even seen cases of the cache being used (verified by using a network proxy and seeing no calls being made) but this method just never gets invoked. I'm at a loss for why this is.
The problem I'm trying to solve is that I have requests that I'd like to cache data for, but these requests have a signature parameter that's different for each one (kind of like a nonce). This makes it so the cache keys are not the same despite the data being equivalent.
To go into explicit detail:
I fire a request with a custom signature (like this:
www.example.com?param1=1&param2=2&signature=1abcdefabc312093)
The request comes back with an Etag
The Etag is supposed to be managed by the NSURLCache but since it thinks that a different request (www.example.com?param1=1&param2=2&signature=1abdabcda3359809823) is being made it doesn't bother.
I thought that using NSURLProtocol would solve all my problems since Apple's docs say:
class func requestIsCacheEquivalent(_ aRequest: NSURLRequest,
toRequest bRequest: NSURLRequest) -> Bool
YES if aRequest and bRequest are equivalent for cache purposes, NO
otherwise. Requests are considered equivalent for cache purposes if
and only if they would be handled by the same protocol and that
protocol declares them equivalent after performing
implementation-specific checks.
Sadly, the function is never called. I don't know what the problem could be...
class WWURLProtocol : NSURLProtocol, NSURLSessionDataDelegate {
var dataTask: NSURLSessionDataTask?
var session: NSURLSession!
var trueRequest: NSURLRequest!
private lazy var netOpsQueue: NSOperationQueue! = NSOperationQueue()
private lazy var delegateOpsQueue: NSOperationQueue! = NSOperationQueue()
override class func canInitWithRequest(request: NSURLRequest) -> Bool {
println("can init with request called")
return true
}
override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
println("canonical request for request called")
return request
}
override class func requestIsCacheEquivalent(a: NSURLRequest, toRequest b: NSURLRequest) -> Bool {
// never ever called?!?
let cacheKeyA = a.allHTTPHeaderFields?["CacheKey"] as? String
let cacheKeyB = b.allHTTPHeaderFields?["CacheKey"] as? String
println("request is cache equivalent? \(cacheKeyA) == \(cacheKeyB)")
return cacheKeyA == cacheKeyB
}
override func startLoading() {
println("start loading")
let sharedSession = NSURLSession.sharedSession()
let config = sharedSession.configuration
config.URLCache = NSURLCache.sharedURLCache()
self.session = NSURLSession(configuration: config, delegate: self, delegateQueue: self.delegateOpsQueue)
dataTask = session.dataTaskWithRequest(request, nil)
dataTask?.resume()
}
override func stopLoading() {
println("stop loading")
dataTask?.cancel()
}
//SessionDelegate
func URLSession(session: NSURLSession, didBecomeInvalidWithError error: NSError?) {
println("did become invalid with error")
client?.URLProtocol(self, didFailWithError: error!)
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
println("did complete with error")
if error == nil {
client?.URLProtocolDidFinishLoading(self)
} else {
client?.URLProtocol(self, didFailWithError: error!)
}
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
println("did receive response")
client?.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .Allowed)
completionHandler(.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
println("did receive data called")
client?.URLProtocol(self, didLoadData: data)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler: (NSCachedURLResponse!) -> Void) {
println("will cache response called")
client?.URLProtocol(self, cachedResponseIsValid: proposedResponse)
completionHandler(proposedResponse)
}
I registered the protocol in my app delegate as follows:
NSURLProtocol.registerClass(WWURLProtocol.self)
I trigger the protocol as follows:
#IBAction func requestData(endpointString: String) {
let url = NSURL(string: endpointString)
let request = NSMutableURLRequest(URL: url!)
var cacheKey = endpointString
request.setValue("\(endpointString)", forHTTPHeaderField: "CacheKey")
request.cachePolicy = .UseProtocolCachePolicy
NSURLConnection.sendAsynchronousRequest(request, queue: netOpsQueue) { (response, data, error) -> Void in
if data != nil {
println("succeeded with data:\(NSString(data: data, encoding: NSUTF8StringEncoding)))")
}
}
}
I think that in practice, the loading system just uses the canonicalized URL for cache purposes, and does a straight string comparison. I'm not certain, though. Try appending your nonce when you canonicalize it, in some form that is easily removable/detectable (in case it is already there).
Your code seems all right.You just follow documents of Apple about URLProtocol.You could try to use URLSession for NSURLConnection is deprecated in newer iOS version.Good luck.

Resources