URLProtocol doesn't get initialized - swift

I am implementing an URLProtocol in an app.
import Cocoa
class MyURLProtocol: URLProtocol {
override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
super.init(request: request, cachedResponse: cachedResponse, client: client)
}
override class func canInit(with request: URLRequest) -> Bool {
return true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
return request
}
override func startLoading() {
print("loading")
}
}
Although canInit(with request: URLRequest) always returns true, neither init(…) nor canonicalRequest(…) nor startLoading() get called.
URLProtocol.registerClass for MyURLProtocol is called in willFinishLaunching in the AppDelegate
I don't know what to do. Yesterday, day code called at least the functions.
Thanks for your help.

Are you using URLSession? URLSession bypasses the normal protocol registration and instead has you explicitly configure the protocols in the URLSessionConfiguration. See URLSessionConfiguration.protocolClasses.

Actually I'am working on a macOS app and not an iOS one, but it has fixed the problem when I was changing from WKWebView to WebView.
Thanks to Kevin Ballard on his comment.

Related

Difference between a Callback and Competition Handler in Swift

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.

Swift - Log all requests in mac app

I have tried to use NSURLProtocol to log all requests in a Swift 2.3 project. However not all URL requests are being logged. Specifically all the Alamofire requests are not being recorded.
Sample code
class AppDelegate: NSObject, NSApplicationDelegate{
func applicationDidFinishLaunching(aNotification: NSNotification) {
NSURLProtocol.registerClass(TestURLProtocol)
Alamofire.request(.GET, SomeURL).responseSwiftyJSON({ (request, response, json, error) in })
}
}
class TestURLProtocol: NSURLProtocol {
override class func canInitWithRequest(request: NSURLRequest) -> Bool {
print("request \(request.URL!)") // never called
return false
}
}
I think this is because Alamofire uses the new URLSession API, which is not affected by the NSURLProtocol.registerProtocol call.
You have to create a URLSession with URLSessionConfiguration that has its protocolClasses array set to [TestURLProtocol.self].
But with this you would have to use a custom SessionManager everywhere to log the requests, instead of using the implicit Alamofire.request I think.
What I ended up using was the pod OHHTTPStubs. I added the following code to my app delegate to log every host being used.
func applicationDidFinishLaunching(aNotification: NSNotification) {
var hosts = [String: Int]()
stub({ req in
if let url = req.URL, let host = url.host{
var count = 1
if let c = hosts[host]{
count = c + 1
}
hosts[host] = count
print("Request #\(count): Host = \(host)")
}
return false
},
response:{_ in return OHHTTPStubsResponse()}
);
}

NSURLConnection to NSURLSession

I'm new to Swift and NSURLConnection & NSURLSession. I have this code, to load a webpage and this works. But I got this warning that NSURLConnection was deprecated in iOS 9.0, and I have to use NSURLSession.
This is my code:
var authenticated:Bool = false
var urlConnection:NSURLConnection!
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if !authenticated {
self.authenticated = false
self.urlConnection = NSURLConnection(request: request, delegate: self)!
urlConnection.start()
return false
}
return true
}
// We use this method is to accept an untrusted site which unfortunately we need to do, as our PVM servers are self signed.
func connection(connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: NSURLProtectionSpace) -> Bool {
return (protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust)
}
func connection(connection: NSURLConnection, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
if challenge.previousFailureCount == 0 {
self.authenticated = true
let credential: NSURLCredential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!
)
challenge.sender!.useCredential(credential, forAuthenticationChallenge: challenge)
}
else {
challenge.sender!.cancelAuthenticationChallenge(challenge)
}
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
// remake a webview call now that authentication has passed ok.
self.authenticated = true
web.loadRequest(request)
// Cancel the URL connection otherwise we double up (webview + url connection, same url = no good!)
urlConnection.cancel()
}
This works. Now I want to 'convert' it to NSURLSession, but I can't seem to manage. Can somebody help me with this? I'm quite sure it's not so difficult for someone who can code very well.
I've tried several times to change to NSURLSession, but every time I've got this error: NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813). And with NSURLConnection the problem is solved.
This is one of my attempts while using NSURLSession:
var authenticated:Bool = false
var urlSession:NSURLSession!
var urlSessionConfig:NSURLSessionConfiguration!
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if !authenticated {
self.authenticated = false
self.urlSessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
self.urlSession = NSURLSession(configuration: self.urlSessionConfig, delegate: self, delegateQueue:NSOperationQueue.mainQueue())
let task = self.urlSession.dataTaskWithRequest(request){
(myData, myResponse, myError) -> Void in
if(myError == nil){
if(self.authenticated){
self.web.loadRequest(request)
}
}
}
task.resume()
return false
}
return true
}
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust){
if(challenge.protectionSpace.host == "mydomain.org"){
self.authenticated = true
let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential);
}
}
}
You don't have to use NSURLSession. Odds are, NSURLConnection will be in a semi-supported state until the end of time, given how broadly it is used.
With that said, I'm only going to say this once, so listen very carefully. Do not, under any circumstances, publicly ship either version of this code as written. Self-signed certificates are okay, as long as you check them properly. This code is not doing that, which makes them no better than HTTP.
If the data has to be kept secret, either use a real certificate or add code to validate the self-signed certificates properly.
If it isn't even slightly important to keep it secret, just use HTTP.
This document explains how to properly implement modified TLS chain validation by adding trust for a specific certificate:
https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
BTW, you're not doing anything with challenges in other protection spaces or for other hostnames. If you never call the completion handler, then tasks that request any other type of authentication will just hang around forever, in limbo, waiting for you to decide how to handle the authentication request. This is probably not what you want, though I doubt it is causing your problem unless you're behind a proxy. Your NSURLConnection code accepted challenges within a single protection space, so it probably didn't experience that problem (as much).
With that said, I don't see why this is failing with that error code unless there's something else wrong with the cert beyond being self-signed. Oh, and there's a missing underscore in the method declaration. I don't think that is important anymore, but I'm not certain. Be sure your delegate method is actually getting called.
You might also try setting the CFNETWORK_DIAGNOSTICS environment variable to 1 (or more) and see if that provides any further insight.

NSURLSession didReceiveData not being triggered

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()
}

Is this specialized use of NSURLConnection to handle self signed certs convertible to NSURLSession?

I have a self signed certificate in the VM I use to test my service. Using answers found in UIWebView to view self signed websites (No private api, not NSURLConnection) - is it possible? I was able to write functioning swift 2.0 code. Xcode 7 tells me that NSURLConnection is deprecated and I should use NSURLSession instead. None of my attempts to migrate this code succeeded, and none of the usual conversion scenarios described in other answers seem to apply.
If I create a new NSURLSession in order to handle the authentication challenge with my delegate methods, the other loads still happen on the sharedSession, and therefore fail.
var authRequest : NSURLRequest? = nil
var authenticated = false
var trustedDomains = [:] // set up as necessary
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if !authenticated {
authRequest = request
let urlConnection: NSURLConnection = NSURLConnection(request: request, delegate: self)!
urlConnection.start()
return false
}
else if isWebContent(request.URL!) { // write your method for this
return true
}
return processData(request) // write your method for this
}
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let challengeHost = challenge.protectionSpace.host
if let _ = trustedDomains[challengeHost] {
challenge.sender!.useCredential(NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!), forAuthenticationChallenge: challenge)
}
}
challenge.sender!.continueWithoutCredentialForAuthenticationChallenge(challenge)
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
authenticated = true
connection.cancel()
webview!.loadRequest(authRequest!)
}
I was able to suppress the deprecation warning by adding this line before the first method. I would prefer a solution that replaces NSURLConnection but in the absence of that, this will have to do.
// hide NSURLConnection deprecation
#available(iOS, deprecated=9.0)