Emitting a Warning for a Deprecated Swift Protocol Method in Implementing Types

Deprecating protocols for Swift 3 upgrade

Exactly what you are asking for is not possible in Swift (nor Objective-C?) to my knowledge. Quoting a response to a related question:

The primary problem with throwing a deprecation warning on any class which conforms to MyProtocol and implemented myOldFunction() is that there's nothing wrong with classes implementing functions and properties that are not part of your protocol.

That is, that the protocol's method is deprecated wouldn't necessarily mean that the method blueprint is universally to be avoided, it could just mean that for the purposes of conformance to that protocol, the method or property in question is now deprecated.

I totally see the point for this and I'd like this feature too, but to my knowledge Swift 3 at least does not offer it (neither does Objective-C to my knowledge).

One solution for this would be to deprecate the entire protocol, and produce a new protocol you need to declare conformance to in your Swift 3 code. So, this works:

@available(*, deprecated, message="use ModernX instead")
protocol X {}

class A: X {}

… and in your ModernX protocol simply include all the methods except for the deprecated method(s). Using a base protocol without the deprecated method could make this slightly less clunky, but it is a pretty boilerplate heavy workaround for sure:

protocol BaseX {
func foo()
func bar()
}

@available(*, deprecated, message: "Use ModernX instead")
protocol X: BaseX {
func theDeprecatedFunction()
}

protocol ModernX: BaseX {
func theModernFunction()
}

// you'll get a deprecation warning here.
class A: X {
func foo() {}
func bar() {}

func theDeprecatedFunction() {
}
}

Non-'@objc' method does not satisfy optional requirement of '@objc' protocol

While I think I can answer your question, it's not an answer you will like.

TL;DR: @objc functions may not currently be in protocol extensions. You could create a base class instead, though that's not an ideal solution.

Protocol Extensions and Objective-C

First, this question/answer (Can Swift Method Defined on Extensions on Protocols Accessed in Objective-c) seems to suggest that because of the way protocol extensions are dispatched under the hood, methods declared in protocol extensions are not visible to the objc_msgSend() function, and therefore are not visible to Objective-C code. Since the method you are trying to define in your extension needs to be visible to Objective-C (so UIKit can use it), it yells at you for not including @objc, but once you do include it, it yells at you because @objc is not allowed in protocol extensions. This is probably because protocol extensions are not currently able to be visible to Objective-C.

We can also see that the error message once we add @objc states "@objc can only be used with members of classes, @objc protocols, and concrete extensions of classes." This is not a class; an extension to an @objc protocol is not the same as being in the protocol definition itself (i.e. in requirements), and the word "concrete" would suggest that a protocol extension does not count as a concrete class extension.

Workaround

Unfortunately, this pretty much completely prevents you from using protocol extensions when the default implementations must be visible to Objective-C frameworks. At first, I thought perhaps @objc was not allowed in your protocol extension because the Swift Compiler could not guarantee that conforming types would be classes (even though you have specifically specified UIViewController). So I put a class requirement on P1. This did not work.

Perhaps the only workaround is to simply use a base class instead of a protocol here, but this is obviously not completely ideal because a class may only have a single base class but conform to multiple protocols.

If you choose to go this route, please take this question (Swift 3 ObjC Optional Protocol Method Not Called in Subclass) into account. It appears that another current issue in Swift 3 is that subclasses do not automatically inherit the optional protocol requirement implementations of their superclass. The answer to that questions uses a special adaption of @objc to get around it.

Reporting the Issue

I think this is being discussed already among those working on the Swift open source projects, but you could be sure they are aware by either using Apple's Bug Reporter, which would likely eventually make its way to the Swift Core Team, or Swift's bug reporter. Either of these may find your bug too broad or already known, however. The Swift team may also consider what you are looking for to be a new language feature, in which case you should first check out the mailing lists.

Update

In December 2016, this issue was reported to the Swift community. The issue is still marked as open with a medium priority, but the following comment was added:

This is intended. There is no way to add the implementation of the method to every adopter, since the extension could be added after the conformance to the protocol. I suppose we could allow it if the extension is in the same module as the protocol, though.

Since your protocol is in the same module as your extension, however, you may be able to do this in a future version of Swift.

Update 2

In February 2017, this issue was officially closed as "Won't Do" by one of the Swift Core Team members with the following message:

This is intentional: protocol extensions cannot introduce @objc entry points due to limitations of the Objective-C runtime. If you want to add @objc entry points to NSObject, extend NSObject.

Extending NSObject or even UIViewController will not accomplish exactly what you want, but it unfortunately does not look like it will become possible.

In the (very) long-term future, we may be able to eliminate reliance on @objc methods entirely, but that time will likely not come anytime soon since Cocoa frameworks are not currently written in Swift (and cannot be until it has a stable ABI).

Update 3

As of Fall 2019, this is becoming less of a problem because more and more Apple frameworks are being written in Swift. For example, if you use SwiftUI instead of UIKit, you sidestep the problem entirely because @objc would never be necessary when referring to a SwiftUI method.

Apple frameworks written in Swift include:

  • SwiftUI
  • RealityKit
  • Combine
  • CryptoKit

One would expect this pattern to continue over time now that Swift is officially ABI and module stable as of Swift 5.0 and 5.1, respectively.

typealias as emoji with invalid expression

A typealias is for giving a type another name. You can't use typealias to give Swift keywords another name.

In other words, you can't do what you are trying to do.

And to be clear, the issue has nothing to do with the use of an Emoji.

Does let _ = ... (let underscore equal) have any use in Swift?

You will get a compiler warning if the method has been marked with a warn_unused_result from the developer documentation:

Apply this attribute to a method or function declaration to have the compiler emit a warning when the method or function is called without using its result.

You can use this attribute to provide a warning message about incorrect usage of a nonmutating method that has a mutating counterpart.

How to open HTTP stream on iOS using ndjson

iOS can connect to HTTP stream using now deprecated API URLConnection. The API was deprecated in iOS 9, however it's still available for use (and will be in iOS 16 - tested).

First you need to create URLRequest and setup the NSURLConnection:

let url = URL(string: "\(baseURL)/v2/path/\(keyID)")!

var urlRequest = URLRequest(url: url)
urlRequest.setValue("application/x-ndjson", forHTTPHeaderField: "Accept")

let connnection = NSURLConnection(request: urlRequest, delegate: self, startImmediately: true)
connnection?.start()

Notice that the argument for delegate in the code above is of type Any which doesn't help to figure out what protocol(s) to implement. There are two - NSURLConnectionDelegate and NSURLConnectionDataDelegate.

Let's receive data:

public func connection(_ connection: NSURLConnection, didReceive data: Data) {
let string = String(data: data, encoding: .utf8)
Logger.shared.log(level: .debug, "didReceive data:\n\(string ?? "N/A")")
}

Then implement a method for catching errors:

public func connection(_ connection: NSURLConnection, didFailWithError error: Error) {
Logger.shared.log(level: .debug, "didFailWithError: \(error)")
}

And if you have custom SSL pinning, then:

public func connection(_ connection: NSURLConnection, willSendRequestFor challenge: URLAuthenticationChallenge) {
guard let certificate = certificate, let identity = identity else {
Logger.shared.log(level: .info, "No credentials set. Using default handling. (certificate and/or identity are nil)")
challenge.sender?.performDefaultHandling?(for: challenge)
return
}

let credential = URLCredential(identity: identity, certificates: [certificate], persistence: .forSession)
challenge.sender?.use(credential, for: challenge)
}

There is not much info on the internet, so hopefully it will save someone days of trial and error.



Related Topics



Leave a reply



Submit