"Does not conform to protocol" error when extending different class - swift

I'm attempting to test my own class by injecting objects that adapt the URLSession and URLSessionDataTask protocols. I'm extending NSURLSession and NSURLSessionDataTask to adopt those protocols so that I can use the existing objects normally, but use test objects in unit tests.
I have the following code, with the error commented:
typealias SessionHandler = (NSData?, NSURLResponse?, NSError?) -> Void
protocol URLSession {
func dataTaskWithURL(url: NSURL, completionHandler: SessionHandler) -> URLSessionDataTask
}
protocol URLSessionDataTask {
}
// Type 'NSURLSession' does not conform to protocol 'URLSession'
extension NSURLSession : URLSession {}
extension NSURLSessionDataTask : URLSessionDataTask {}
I understand the error, my protocol doesn't exactly match the method as implimented by NSURLSession. How do I fix this?

What I ended up doing was creating a protocol extension that creates the necessary method that NSURLSession requires.
extension NSURLSession : URLSession {
func dataTaskWithURL(url: NSURL, completionHandler: SessionHandler) -> URLSessionDataTask {
return dataTaskWithURL(url, completionHandler: completionHandler) as NSURLSessionDataTask
}
}

Related

Swift: Unable to make UIApplication conform to my protocol

I'm trying to make UIApplication conform to the following protocol:
protocol URLOpenerProtocol {
func open(_ url: URL, completionHandler: ((Bool) -> Void)?)
}
I've added the following extension:
extension UIApplication: URLOpenerProtocol {}
However, I am getting a conforming error.
Given the UIApplication already has the following method on it:
UIApplication.shared.open(_ url: URL, completionHandler: ((Bool) -> Void)?)
why won't this conform? The two seem to be indentical to me...
Error is as follows:
Type UIApplication does not conform to protocol URLOpenerProtocol
The actual open method in UIApplication is declared like this with 3 parameters, and the second and third ones happen to be optional (source):
func open(
_ url: URL,
options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:],
completionHandler completion: ((Bool) -> Void)? = nil
)
UIApplication has no two-parameter method with this signature:
func open(_ url: URL, completionHandler: ((Bool) -> Void)?)
It only has the three-parameter one. Therefore, it does not conform to your protocol.
You should add the options parameter into your protocol too, so that UIApplication can conform to it, and I would suggest that you add a protocol extension with the two-parameter version:
protocol URLOpenerProtocol {
func open(_ url: URL,
options: [UIApplication.OpenExternalURLOptionsKey : Any],
completionHandler: ((Bool) -> Void)?)
}
extension URLOpenerProtocol {
func open(_ url: URL,
completionHandler: ((Bool) -> Void)?) {
open(url, options: [:], completionHandler: completionHandler)
}
}

Unit testing for URLSessionDelegate method - Swift

I have a custom delegate class called CertificatePinningDelegate which conforms to URLSessionDelegate. I'm using the delegate method
urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
to authenticate with the server. Now I want to write a unit test for this.
I've been trying to manually call the delegate method to pass a custom URLAuthenticationChallenge object, which seems impossible. Does anyone know any alternative/better option for this?

URLProtocol doesn't get initialized

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.

Ambiguous use of 'connection(_:didReceive:)'

static let didReceiveResponseSelector : Selector = #selector((NSURLConnectionDataDelegate.connection(_:didReceive:)) as (NSURLConnectionDataDelegate) ->(NSURLConnection,URLResponse) -> ())
This code is returning error:
Ambiguous use of 'connection(_:didReceive:)'
I referred to the official evolution thread of Apple on GitHub, I respected the syntax but is not working:
Referencing the Objective-C selector of a method
NSURLConnectionDataDelegate is a protocol, you can't create a Selector using NSURLConnectionDataDelegate.connection(_:didReceive:), you must used an implementation of NSURLConnectionDataDelegate like :
class YourDelegateImplementation: NSURLConnectionDataDelegate {
public func connection(_ connection: NSURLConnection, didReceive data: Data) {
}
}
And then you can create a Selector like this :
let yourDelegate: YourDelegateImplementation = YourDelegateImplementation()
let yourSelector : Selector = #selector(yourDelegate.connection(_:didReceive:))
Don't cast the Selector:
let didReceiveResponseSelector = #selector(NSURLConnectionDelegate.connection(_:didReceive:))
It's also worth noting that the delegate function connection(_ connection: NSURLConnection, didReceive challenge: URLAuthenticationChallenge) has been deprecated in favor of connection(_ connection: NSURLConnection, willSendRequestFor challenge: URLAuthenticationChallenge).
solved, just add "?":
static let didReceiveResponseSelector : Selector = #selector((NSURLConnectionDataDelegate.connection(_:didReceive:)) as ((NSURLConnectionDataDelegate) -> (NSURLConnection,URLResponse) -> void)?)

Error: Generic parameter 'T' could not be inferred.

I have a problem with calling this method:
func setUpFeedbackForm<T:UIViewController>(viewController: T,
viewForScreenshot: UIView,
completionHandler: #escaping () -> ())
where T:FeedbackFormDelegate { ... }
inside this wrapper function:
public class func setUpFeedbackFormWrapper(viewController: UIViewController,
viewForScreenshot: UIView,
completionHandler: #escaping () -> ()) {
setUpFeedbackForm(viewController: viewController,
viewForScreenshot: viewForScreenshot,
completionHandler: completionHandler)
}
I am getting error: Generic parameter 'T' could not be inferred. I do understand what the error means, but I don't know how to implement this call correctly. Also the reason why I have this wrapper is that I want to expose func setUpFeedbackForm to obj-c code and I can't import it directly to obj-c because of swift's generics.
Could you please show me the correct way of calling it?
You have two constraints on the viewController parameter that need to be satisfied when calling setUpFeedbackForm:
inheritance from UIViewController
conformance to FeedbackFormDelegate
, and setUpFeedbackFormWrapper satisfies only one, thus the compiler doesn't know what to do with the other one.
The problem is caused by a limitation of Swift which can't directly express variables/parameters that satisfy both class inheritance and protocol conformance, unless using generics, which breaks the Objective-C compatibility.
Thus a valid UIViewController<FeedbackFormDelegate> construct in Objective-C doesn't have a direct equivalent in Swift.
A workaround to this limitation is to declare a 3rd method that exposes the class inheritance and protocol conformance arguments as two distinct parameters, and call that method from both the Objective-C compatible and the Swift-only versions.
func setUpFeedbackForm<T:UIViewController>(viewController: T,
viewForScreenshot: UIView,
completionHandler: #escaping () -> ())
where T:FeedbackFormDelegate {
setupFeedbackFormImpl(viewController: viewController,
feedbackFormDelegate: viewController,
viewForScreenshot: viewForScreenshot, completionHandler: completionHandler)
}
func setupFeedbackFormImpl(viewController: UIViewController,
feedbackFormDelegate: FeedbackFormDelegate,
viewForScreenshot: UIView,
completionHandler: #escaping () -> ()) {
// actual code here
}
public func setUpFeedbackFormWrapper(viewController: UIViewController,
viewForScreenshot: UIView,
completionHandler: #escaping () -> ()) {
guard let feedbackFormDelegate = viewController as? FeedbackFormDelegate else {
// you can also report errors here, if you want to
// forbid runtime calls with controllers that are not FeedbackFormDelegate
return
}
setupFeedbackFormImpl(viewController: viewController,
feedbackFormDelegate: feedbackFormDelegate,
viewForScreenshot: viewForScreenshot,
completionHandler: completionHandler)
}
If we think in terms of SOLID programming, then this workaround follows the Interface Segregation Principle, as we receive one argument for the view controller stuff, and another one for the delegate stuff, even if they point to the same object behind.