Declarations from extensions cannot be overridden yet in Swift 4 - ios

I have recently migrated my code to Swift 4. There is an issue that I am facing with extensions, i.e.:
Declarations from extensions cannot be overridden yet
I have already read multiple posts regrading this issue. But none of them entertains the scenario described below:
class BaseCell: UITableViewCell
{
//Some code here...
}
extension BaseCell
{
func isValid() -> String?
{
//Some code here...
}
}
class SampleCell: BaseCell
{
//Some code here...
override func isValid() -> String? //ERROR..!!!
{
//Some code here...
}
}
According to Apple,
Extensions can add new functionality to a type, but they cannot override existing functionality.
But in the above scenario, I am not overriding the method isValid() in extension. It is overridden in the SampleCell class definition itself. Still, it is giving the error.

But in the above scenario, I am not overriding the method isValid() in an extension.
isValid gets declared in an extension.
The error pretty much says that if a function is declared this way, it cannot be overridden.
The statement is valid for both from an extension and in an extension.

You can override declarations from extensions as long as you #objc the protocol. In Swift 4.2:
class BaseClass {}
class SubclassOfBaseClass: BaseClass {}
#objc protocol IsValidable {
func isValid() -> Bool
}
extension BaseClass: IsValidable {
func isValid() -> Bool { return false }
}
extension SubclassOfBaseClass {
override func isValid() -> Bool { return !super.isValid() }
}
BaseClass().isValid() // -> false
SubclassOfBaseClass().isValid() // -> true

In Swift 3, you were able to override the function of extension if extension was of a class that is getting derived from Objective-C (http://blog.flaviocaetano.com/post/this-is-how-to-override-extension-methods/), but I guess its not possible now in Swift 4. You can ofcourse do something like this:
protocol Validity {
func isValid() -> String?
}
class BaseCell: UITableViewCell, Validity {
}
extension Validity
{
func isValid() -> String? {
return "false"
}
}
class SampleCell: BaseCell {
func isValid() -> String? {
return "true"
}
}
let base = BaseCell()
base.isValid() // prints false
let sample = SampleCell()
sample.isValid() // prints true

I think this is self-explanatory.
declarations FROM extensions cannot be overridden yet
You are trying to override the function func isValid() -> String? which was declared within an extension of BaseCell, not the BaseCell class itself.
It is clearly saying that you can't override something that was declared inside an extension.
Hope it is helpful.

I too had a huge legacy of Swift 3 code that used this old trick to achieve what I wanted, so when I moved to Swift 4 and started getting these errors, I was somewhat distressed. Fear not, there is a solution.
This error has to do with the way Swift 4 compiles classes and the new way it treats Objective-C classes and functions. Under Swift 3, if a class is derived from NSObject, then all the variables and functions in that class would use Objective-C's dynamic naming and lookup conventions. This approach inhibited Swift's ability to optimise the code and improve code performance and size.
To overcome these penalties, in Swift 4, only variables and functions explicitly tagged with #objc get the Objective-C treatment, everything else uses standard Swift conventions: hence the error.
Armed with this knowledge, the solution to your problem is to tag the functions in the extension you wish to be overridden as #objc, then in the child classes, override the function, but remember to include the #objc tag so your code will get called at runtime.
WARNING The is a little gotcha here: if you forget to include the #objc in the override, the compiler will not complain, but your code lacks the dynamic lookup, so never gets called at runtime.
So your code should look a bit like this:
class BaseCell: UITableViewCell {
//Some code here...
}
extension BaseCell {
#objc func isValid() -> String? {
//Some code here...
}
}
class SampleCell: BaseCell {
//Some code here...
#objc override func isValid() -> String? {
//Some code here...
}
}

It is invalid in Swift, however not in Objective-C. So, if your method signature allows it (no objc forbidden constructs), you can declare it #objc func myMethod() and override it freely in Swift.

Related

Override of instance method form extension depends on deprecated inference of '#objc'

I am trying to convert my code (written in Swift 3) to Swift 4, for that I am adding #objc where needed. Xcode has done quite a good job to automatically fix them but I am struggling with a few (all using the same 2 methods), where Xcode can't help, it just puts #objc somewhere in my code.
I am overriding a method called navbarRightButtonAction(button:) like this in my ViewController class.
class ViewController: PBViewController {
override func navbarRightButtonAction(button: PBAdaptiveButton) {
...
}
}
This is where I get the warning saying:
Override of instance method 'navbarRightButtonAction(button:)' from extension of PBViewController depends on deprecated inference of '#objc'
Then I thought the problem us be in the PBViewController class which looks like this:
extension PBViewController: PBNavigationBarDelegate {
func navbarRightButtonAction(button: PBAdaptiveButton) {
print("Override this method")
}
}
So I added #objc func navbarRightButtonAction(button: PBAdaptiveButton) but it didn't help.
Then I looked into the PBNavigationBarDelegate protocol
protocol PBNavigationBarDelegate {
func navbarRightButtonAction(button:PBAdaptiveButton)
}
I added #objc protocol PBNavigationBarDelegate but it didn't help either.
I have no other idea what to do to fix the deprecation warning.
Put #objc or #nonobjc in front of the extension:
#objc extension PBViewController: PBNavigationBarDelegate
Take a look at Limiting #objc Inference, SE-0160 at Swift Evolution for more details. It contains the following example regarding extensions:
Enabling/disabling #objc inference within an extension
There might be certain regions of code for which all of (or none of) the entry points should be exposed to Objective-C. Allow either #objc or #nonobjc to be specified on an extension. The #objc or #nonobjc will apply to any member of that extension that does not have its own #objc or #nonobjc annotation. For example:
class SwiftClass { }
#objc extension SwiftClass {
func foo() { } // implicitly #objc
func bar() -> (Int, Int) // error: tuple type (Int, Int) not
// expressible in #objc. add #nonobjc or move this method to fix the issue
}
#objcMembers
class MyClass : NSObject {
func wibble() { } // implicitly #objc
}
#nonobjc extension MyClass {
func wobble() { } // not #objc, despite #objcMembers
}

JSQMessages cannot override method Swift 3

For some reason I cannot override methods in Swift 3 using JSQMessages.
These methods are defined in the JSQMessagesCollectionViewDataSource
public func senderDisplayName() -> String!
public func senderId() -> String!
When I subclass JSQMessagesViewController I try to implement the methods as such:
override func senderId() -> String {
return User.Wozniak.rawValue
}
override public func senderDisplayName() -> String! {
return getName(.Wozniak)
}
However I get the error that it does not override any method from its super class. When I remove override it says it conflicts with an Obj-C selector.
I have implemented this functionality in swift 3 with following Properties
self.senderId = "my ID"
self.senderDisplayName = "Wozniac"
You might try this instead:
open override func senderId() -> String {
...
}
but I'm not sure if it will fix your issue completely.
According to SE-0117: Allow distinguishing between public access and public overridability, which introduces the open keyword, the rules for imported Objective-C code are (emphasis mine):
Objective-C classes and methods are always imported as open. This means that the synthesized header for an Objective-C class would pervasively replace public with open in its interface.
Of course, assuming JSQMessages is still implemented in pure Objective-C.

Is use of generics valid in XCTestCase subclasses?

I have a XCTestCase subclass that looks something like this. I have removed setup() and tearDown methods for brevity:
class ViewControllerTests <T : UIViewController>: XCTestCase {
var viewController : T!
final func loadControllerWithNibName(string:String) {
viewController = T(nibName: string, bundle: NSBundle(forClass: ViewControllerTests.self))
if #available(iOS 9.0, *) {
viewController.loadViewIfNeeded()
} else {
viewController.view.alpha = 1
}
}
}
And its subclass that looks something like this :
class WelcomeViewControllerTests : ViewControllerTests<WelcomeViewController> {
override func setUp() {
super.setUp()
self.loadControllerWithNibName("welcomeViewController")
// Put setup code here. This method is called before the invocation of each test method in the class.
}
func testName() {
let value = self.viewController.firstNameTextField.text
if value == "" {
XCTFail()
}
}
}
In theory, this should work as expected -- the compiler doesn't complain about anything. But it's just that when I run the test cases, the setup() method doesn't even get called. But, it says the tests have passed when clearly testName() method should fail.
Is the use of generics a problem? I can easily think of many non-generic approaches, but I would very much want to write my test cases like this. Is the XCTest's interoperability between Objective-C and Swift the issue here?
XCTestCase uses the Objective-C runtime to load test classes and find test methods, etc.
Swift generic classes are not compatible with Objective-C. See https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-ID53:
When you create a Swift class that descends from an Objective-C class, the class and its members—properties, methods, subscripts, and initializers—that are compatible with Objective-C are automatically available from Objective-C. This excludes Swift-only features, such as those listed here:
Generics
...
Ergo your generic XCTestCase subclass can not be used by XCTest.
Well, actually its totally doable. You just have to create a class that will be sort of test runner. For example:
class TestRunner: XCTestCase {
override class func initialize() {
super.initialize()
let case = XCTestSuite(forTestCaseClass: ViewControllerTests<WelcomeViewController>.self)
case.runTest()
}
}
This way you can run all your generic tests.
I got it working in a kind of hacky way with protocols & associated types:
import XCTest
// If your xCode doesn't compile on this line, download the lastest toolchain as of 30 november 2018
// or change to where Self: XCTestCase, and do it also in the protocol extension.
protocol MyGenericTestProtocol: XCTestCase {
associatedtype SomeType
func testCallRunAllTests()
}
extension MyGenericTestProtocol {
// You are free to use the associated type here in any way you want.
// You can constraint the associated type to be of a specific kind
// and than you can run your generic tests in this protocol extension!
func startAllTests() {
for _ in 0...100 {
testPrintType()
}
}
func testPrintType() {
print(SomeType.self)
}
}
class SomeGenericTestInt: XCTestCase, MyGenericTestProtocol {
typealias SomeType = Int
func testCallRunAllTests() {
startAllTests()
}
}
class SomeGenericTestString: XCTestCase, MyGenericTestProtocol {
typealias SomeType = String
func testCallRunAllTests() {
startAllTests()
}
}
This approach (https://stackoverflow.com/a/39101121/311889) no longer works. The way I did it in Swift 5.2 is:
class MyFileTestCase: XCTestCase {
override func run() {
let suite = XCTestSuite(forTestCaseClass: FileTestCase<MyFile>.self)
suite.run()
super.run()
}
// At least one func is needed for `run` to be called
func testDummy() { }
}

Override function error in swift

I got a struct :
struct ErrorResultType: ErrorType {
var description: String
var code: Int
}
and a protocol:
protocol XProtocol {
func dealError(error: ErrorResultType)
}
Now I want to make an extention of UIViewController:
extension UIViewController: XProtocol {
func dealError(error: ErrorResultType) {
// do something
}
}
So I can subclass from this and override the function like:
class ABCViewController: UIViewController {
--->override func dealError(error: ErrorResultType) {
super.dealError(error)
// do something custom
}
}
But it goes wrong with: Declarations from extensions cannot be overridden yet
It doesn't make any sense to me. When I replace all ErrorResultType with AnyObject, the error won't appear any more.
Anything I missed?
For now the method in the extension must be marked with #objc to allow overriding it in subclasses.
extension UIViewController: XProtocol {
#objc
func dealError(error: ErrorResultType) {
// do something
}
}
But that requires all types in the method signature to be Objective-C compatible which your ErrorResultType is not.
Making your ErrorResultType a class instead of a struct should work though.
If i am not making mistake this is connected with Swift official extension mechanism for adding methods to classes.
Conclusion :
At the moment, it's not possible to override entities declared in
extension by subclassing, like so:
class Base { }
extension Base {
var foo: String { return "foo" }
}
class Sub: Base {
override var foo: String { return "FOO" } // This is an error
}
Please check this resource for more information : https://github.com/ksm/SwiftInFlux/blob/master/README.md#overriding-declarations-from-extensions

Return an array of type Self

I'm having trouble figuring out a way to return an array of instances of a specific dynamic class type, at runtime, in Swift.
I successfully compiled and tested this version which returns a single instance of a class:
class Generic {
class func all() -> Self {
return self.init()
}
required init() {
}
}
class A: Generic {
}
let a = A.all() // is of type A
The challenge here is to get compilation to allow the all function to be prototyped as follows: class func all() -> [Self] (i.e return an array of instances, working with subclasses, without any cast).
class Generic {
class func all() -> [Self] {
return [self.init()]
}
required init() {
}
}
class A: Generic {
}
let a = A.all() // won't compile
I could return an array of Generic instances with class func all() -> [Generic] but this requires an additional cast with as! to get the correct type A. I'd like to take advantage of begin in the context of class A and using the Self keyword, to let the compiler infer the 'real' type. Do you guys think it's possible?
It seems to be only possible to return single instances, not arrays.
EDIT: Got this to work using AnyObject. Better, but not optimal as it requires a cast to the correct type.
class Generic {
class func all() -> [AnyObject] {
return [self.init()]
}
required init() {
}
}
class A: Generic {
}
let a = A.all() as! [A]
Thanks!
PS: Any other way to do this using generics or protocols/protocol extensions is also an option. If you have a more "Swifty" version in mind, please be my guest. Can't help myself thinking there's maybe a better way to do this, but can't figure out how.
The only option I can see of doing something like that is using protocols instead of a base class, like this:
protocol Generic {
func all() -> [Self]
init()
}
extension Generic {
func all() -> [Self] {
return [self.dynamicType.init()]
}
}
final class A : Generic {
}
A().all()
You have two limitations doing it like this. First, all classes that conform to your protocol have to be final. Second, all classes must obviously implement the init defined in the protocol, otherwise we wouldn't be able to have the all method defined.
Edit: you don't actually need to define the init as long as you don't define any other initializers
Edit 2: I didn't notice you used class functions, you can modify my example to use class functions instead of instance methods by replacing func all() -> [Self] with static func all() -> [Self] and
func all() -> [Self] {
return [self.dynamicType.init()]
}
with
static func all() -> [Self] {
return [self.init()]
}
Unfortunately, there doesn't seem to be a way to do this using Self. Self cannot be used in expressions, so [Self] or Array<Self> are not allowed.
However, I think that your use case is completely valid and you should repot it as a bug.

Resources