I'm relatively new to swift and I'm having issues trying to call a function in a view controller from a delegate I have defined. How can I call the function in my view controller from this delegate? This is a mixed project consisting of mostly Objective-C code with only one Swift controller. The function is inside of the Swift controller. Below is the delegate class:
class DelegateToHandle302:NSObject, URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: #escaping (URLRequest?) -> Void) {
//convert to https
let http = request.url!
var comps = URLComponents(url: http, resolvingAgainstBaseURL: false)!
comps.scheme = "https"
let httpsUrl = comps.url!
ViewControllerFunction(url: httpsUrl)
}
I get an error Use of unresolved identifier 'ViewControllerFunction'. I've tried creating an instance of the view controller but don't think that's the correct way to do it as this view controller also has an audio player (it also didn't work).
Here is where I call the delegate from a function inside the view controller:
let urlString = "https://urlthatredirects.com"
let config = URLSessionConfiguration.default
let url = URL(string: urlString)
//set delegate value equal to SessionDelegate to handle 302 redirect
let delegate = DelegateToHandle302()
//establish url session
let session = URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
//set task with url
let dataTask = session.dataTask(with: url!)
//init call
dataTask.resume()
I'm following part of an example on how to get the final URL from a redirection (https://gist.github.com/mgersty/b565ba4c9e9422637f15f52a5317f07e). My view controllers "header" is:
#objc class AudioPlayerController: UIViewController{........}
I hope I've provided enough info to allow anyone to assist me in figuring out what I'm doing wrong. The only thing I need to do is call that function and pass the redirection URL to it.
I'm a bit confused about what you're trying to do, and why you're trying to call a delegate method back into the VC rather than using the completion handler; but I think you've got your delegate pattern back-to-front. I'm assuming the idea is:
the view controller instiagtes the URL session
the url sessions passes of the result of the URLSession to the DelegateToHandle302 to process
DelegateToHandle302 then tries to run a method back in the view controller that launched it.
If this is the case you actually need the VC to be the delegate of the DelegateToHandle302 class, not the other way around.
So within your view controller
let handlerFor302 = DelegateToHandle302()
handlerFor302.delegate = self.
let session = URLSession(configuration: config, delegate: handlerFor302, delegateQueue: nil
//etc... as before
Create a protocol for the delegate to adopt, which defines the desired function
protocol URLProcessor {
func ViewControllerFunction(url: URL)
}
The adopt the protocol in your view controller and implement the method
extension MyViewController: URLProcessor {
func ViewControllerFunction(url: URL) { .... do whatever ...}
and then use the delegate with the protocol method in your DelegateToHandle302
class DelegateToHandle302:NSObject, URLSessionTaskDelegate {
weak var delegate: URLProcessor?
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: #escaping (URLRequest?) -> Void) {
//Process the output
delegate?.ViewControllerFunction(url: httpsUrl)
}
Related
In the Combine framework, I have found following text
The Combine framework provides a declarative approach for how your app
processes events. Rather than potentially implementing multiple
delegate callbacks or completion handler
Can somebody tell me what is the difference between completion handler and callback in Swift?
A delegate callback is when you have a delegate that you know in advance implements a method (e.g. because it adopts a protocol), and you call that method by name.
A completion handler is when someone hands you a function and you just call it blindly by reference.
to be clear actually you can achieve the same functionality with both ways however the there are completely different approach for designing your app
let me clarify with simple example the difference between both with the same function is making network call
delegate protocol
// enum to define the request type
enum RequestTypes {
case UserRegister
case UserLogin
}
protocol ServiceDelegate {
func didCompleteRequest(responseModel: AnyObject, tag: RequestTypes)
}
// you can also add default impl to the methods here
extension ServiceDelegate {
func didCompleteRequest(responseModel: AnyObject, tag: RequestTypes){}
}
class BaseService<ResponseModel: Codable> {
var session: URLSession!
var delegate: ServiceDelegate?
// MARK: Rebuilt Methods
func FireRequest(){
// Request Preparation
let serviceUrl = URL(string: /* your url */)!
var request = URLRequest(url: serviceUrl)
request.httpMethod = "GET"
// Firing the request
session = URLSession.init(configuration: URLSessionConfiguration.default)
session.dataTask(with: request) { (data, response, error) in
if let data = data {
do {
guard let object = try? JSONDecoder().decode(ResponseModel.self , from: data) else {/* handle error or call delegate error method here */ return }
delegate?.didCompleteRequest(responseModel: object, tag: .UserLogin)
}
}
}.resume()
}
}
class ViewController: UIViewController, ServiceDelegate {
override func viewDidLoad() {
super.viewDidLoad()
fetchNewData()
}
func fetchNewData(){
let service = BaseService<YourModel>()
service.delegate = self
service.FireRequest()
}
func didCompleteRequest(responseModel: AnyObject, tag: RequestTypes) {
if tag == /* the tag you are waiting */ .UserLogin {
// YourModel is available here
}
}
}
completion handler
class BaseService<ResponseModel: Codable> {
var session: URLSession!
// MARK: Rebuilt Methods
func FireRequest(completion: ((ResponseModel?) -> Void)?){
// Request Preparation
let serviceUrl = URL(string: /* your url */)!
var request = URLRequest(url: serviceUrl)
request.httpMethod = "GET"
// Firing the request
session = URLSession.init(configuration: URLSessionConfiguration.default)
session.dataTask(with: request) { (data, response, error) in
if let data = data {
do {
guard let object = try? JSONDecoder().decode(ResponseModel.self , from: data) else {/* handle error or call delegate error method here */ return }
DispatchQueue.main.async {
completion?(object)
}
}
}
}.resume()
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
fetchNewData()
}
func fetchNewData(){
let service = BaseService<YourModel>()
service.FireRequest(completion: { [weak self] (response) in
// yourModel Available here once the request completed
})
}
}
A delegate callback is one to one communication between various ViewControllers and classes. It basically lets you know that a particular change has been done in particular view or any where else and now you can make change after this action.
While completion handler is a block executed after completing a particular process or task.
Callback is a way to sending data back to some other function on some particular occasion. there are 2 ways to implement callbacks in swift.
Using Protocols / Delegate
Using Completion Handler
Using Protocols / Delegate Example:
Declare Protocol
protocol MyDelegate {
public method(param: String);
}
Your ViewController should extend the delegate
class YourViewController: MyDelegate {
// Your Other methods
func method(param: String) {
// Do your stuff
}
}
Now in your other classes you can send callback to ViewController through delegate object like
delegate.method(param: "your_param");
Using Completion Handler Example:
public func method(param: String, completionHandler: #escaping (_ param: String) -> Void)
{
...
// now you can send data back to the caller function using completionHandler on some particular occasion
completionHandler("param");
}
We can call this function like
method(param: String, completionHandler: { (result, alreadyUserId) in
// here you will receive callback
});
Callbacks and Completion Handlers are synonymous when referring to asynchronous methods.
I’ve found the main difference being in how its used in defining what’s returned to the caller where a callback is used when referring to a method where the scope is returned to the previous calling method and a completion handler refers to a method when it returns some Result type to the caller.
I have the problem, that my delegate-class is never reinitialised if I pass it as delegate to NSURLSession:
// Playground-compatible
import Foundation
class Downloader: NSObject, URLSessionDataDelegate {
private var session: URLSession! = nil
private var dataTask: URLSessionDataTask! = nil
init(url: URL) {
super.init()
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 60)
self.session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: OperationQueue.main)
self.dataTask = session.dataTask(with: request)
}
deinit {
print("Downloader released")
}
func dummy() -> String {
self.dataTask = nil // I've also tried it without this
self.session = nil // I've also tried it without this
return "Dummy 👨🏻🎤"
}
}
func test() {
let downloader = Downloader(url: URL(fileURLWithPath: "/"))
print(downloader.dummy())
}
test()
print("After test")
If I pass nil instead of self as delegate, Downloader is deinitialized; but obviously this is not a solution^^
Please read the documentation for URLSession init(configuration:delegate:delegateQueue:) and the description for the delegate parameter:
Important
The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session by calling the invalidateAndCancel() or finishTasksAndInvalidate() method, your app leaks memory until it exits.
You need to call one of those two methods on self.session when your Downloader is finished with the session.
Most of my programming experience is in Shell and Python. I'm fairly new to Swift, like "3 days ago" new. I just can't figure out why didFinishDownloadtingTo is not called when my downloadTask completes. Here's my AppDelegate.swift file:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDelegate, URLSessionDownloadDelegate {
#IBOutlet weak var window: NSWindow!
#IBOutlet var progressind: NSProgressIndicator!
#IBOutlet var outputtext: NSTextField!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
let requestURL: URL = URL(string: "https://www.apple.com")!
let urlRequest: URLRequest = URLRequest(url: requestURL as URL)
let session = URLSession.shared
let downloads = session.downloadTask(with: urlRequest)
print("starting download...")
downloads.resume()
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL){
print("download finished!")
}
}
I'm using Swift 3, and I just cannot find enough documentation on it to figure this out on my own.
I went a little crazy declaring classes trying to figure out why it wasn't working right, so I'm sure there are also some errors there.
It appears to download the file successfully. I've tried with several URLs. The "Disk" and "Network" sections of the debug menu appear consistent with downloading a file of the size at every URL I've tested with.
The thing is that when you use NSURLSession(URLSession in Swift 3) you have to choose if you want to use a delegate or a completion handler, in case of use both, only the completion handler gets called. In your case the delegate is not set so you can't see the call to the delegate methods. Instead you should specify the delegate using the another initializer of NSURLSession like in the following code:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDelegate, URLSessionDownloadDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let requestURL: URL = URL(string: "https://www.apple.com")!
let urlRequest: URLRequest = URLRequest(url: requestURL as URL)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
let downloads = session.downloadTask(with: urlRequest)
print("starting download...")
downloads.resume()
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL){
print("download finished!")
}
}
And then you should see the delegate method called properly.
I hope this help you
The key lies in the word "delegate". It's like the dog in the night-time in the Sherlock Holmes story. The dog did nothing in the night. The word "delegate" never appears in your code!
But it needs to. didFinishDownloadingTo is a delegate method. You can only receive this method if you are the delegate of the NSURLSession. You need to set self as its delegate. The runtime doesn't magically read your mind and know that you intend this object as the delegate; you have to tell it.
I'm having a problem detecting when data is being received using NSURLSession. The equivalent code with NSURLConnection does work, but that's not included here.
In this example, I'm doing a request to google.com. The completionHandler works and "complete" is printed (also the data, etc if you change the code).
However didReceiveData isn't triggered and "received data" is never printed.
I've been through the docs and done a ton of searching and I think this looks right, but I can't seem to get it to work. Definitely would appreciate any help with this.
(I need to use didReceiveData because I'm going to parsing a streaming json api.)
Thanks!
import UIKit
class ViewController: UIViewController, NSURLSessionDelegate, NSURLSessionDataDelegate, NSURLSessionTaskDelegate {
override func viewDidAppear(animated: Bool) {
let session = NSURLSession.sharedSession()
var task = session.dataTaskWithURL(NSURL(string: "https://google.com")!, completionHandler: { (data, response, error) -> Void in
print("complete")
})
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
print("received data")
}
}
There were two issues.
When the session is created, you must define a delegate. That was the main reason didReceiveData wasn't being called.
The second issue is that if you use a completionHandler block, then all the delegates functions are bypassed. In the code for NSURlSession, it says
extension NSURLSession {
/*
* data task convenience methods. These methods create tasks that
* bypass the normal delegate calls for response and data delivery,
* and provide a simple cancelable asynchronous interface to receiving
* data. Errors will be returned in the NSURLErrorDomain,
* see <Foundation/NSURLError.h>. The delegate, if any, will still be
* called for authentication challenges.
*/
You must implement each delegate function you need to check for completion, errors, etc.
The updated code is below:
import UIKit
class ViewController: UIViewController, NSURLSessionDelegate {
override func viewDidAppear(animated: Bool) {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil)
var task = session.dataTaskWithURL(NSURL(string: "https://google.com")!)
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
print("received data")
}
}
You can get data in completionHandler only. Why do you want to use didReceiveData?
Below code will show you how you can get the received data
override func viewDidAppear(animated: Bool) {
let session = NSURLSession.sharedSession()
var task = session.dataTaskWithURL(NSURL(string: "https://google.com")!, completionHandler: { (data, response, error) -> Void in
if NSJSONSerialization.isValidJSONObject(data){
if let jsonParam = try? NSJSONSerialization.dataWithJSONObject(dictData, options: []){
print("Result Data : \(jsonParam)")
}
}
})
task.resume()
}
For a screen scraping project I'm using NSURLSession in Swift to read an HTML page. But already the start fails because the returned page gives a redirection to a new webpage and my code doesn't follow that. I thought redirection would work by default, if no delegate is set for the session. But neither case does the redirection. Here's my test project:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSURLSessionTaskDelegate {
#IBOutlet weak var window: NSWindow!
func URLSession(session: NSURLSession, task: NSURLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse,
newRequest request: NSURLRequest, completionHandler: (NSURLRequest!) -> Void) {
completionHandler(request);
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
var url = NSURL(string: "https://banking.dkb.de");
let defaultConfigObject = NSURLSessionConfiguration.defaultSessionConfiguration();
let session = NSURLSession(configuration: defaultConfigObject, delegate: self, delegateQueue: nil);
let task = session.dataTaskWithURL(url!, completionHandler: { (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
let text = NSString(data: data, encoding: NSUTF8StringEncoding);
// text contains here: <head><meta HTTP-EQUIV="REFRESH" content="0; url=/dkb/"></head>
if var document = NSXMLDocument(data: data, options: Int(NSXMLDocumentTidyHTML), error: nil) {
if let forms = document.nodesForXPath("//form[#name='login']", error: nil) where forms.count > 0 {
let form = forms[0] as! NSXMLNode;
}
}
});
task.resume();
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
What do I have to do to make redirection work here?
Update:
On further investigation I found out that it must have to do with the result from the server. For instance using https://www.google.com indeed triggers the redirection delegate. However, since any browser can handle also redirection from the bank address, there must be a different approach in place to properly handle that and I'd like to learn how.
NSURLSession supports 302 redirect and https://www.google.com uses it.
On the other hand, https://banking.dkb.de/ uses meta tag as described. It returns 200(OK) as status code so you must handle it reading meta tag.