Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift / SwiftUI: why can't I override a method that returns some View?

In the following example

import SwiftUI

class AbstractOverride {
    open func configurationView() -> AnyView {
        if Features.TEST_VERSION {
            return AnyView(Text("override configurationView()"))
        } else {
            return AnyView(EmptyView())
        }
    }
    
    open func someConfigurationView() -> some View {
        if Features.TEST_VERSION {
            return AnyView(Text("override someConfigurationView()"))
        } else {
            return AnyView(EmptyView())
        }
    }

}

class SubclassOverride: AbstractOverride {
    override func configurationView() -> AnyView { // no compiler error
        return AnyView(Text("Test"))
    }

    override func someConfigurationView() -> some View { // compiler error "Method does not override any method from its superclass"
        return AnyView(Text("Test"))
    }
}

I get a compiler error compiler error "Method does not override any method from its superclass" at override func someConfigurationView() -> some View.

There is no compiler error returning AnyView.

can anybody explain what is happening here? Why does Swift not recognise that the method signatures are the same?

This is iOS 13, Xcode 11.5

like image 715
Gerd Castan Avatar asked Nov 16 '22 09:11

Gerd Castan


1 Answers

The language design has dictated that it is completely impossible for the programmer to statically be provided with information about the relationship of two opaque types, such as whether they exist in a class inheritance chain.

var instanceOfType1: some ExpressibleByBooleanLiteral = true

var anotherInstanceOfType1 = instanceOfType1 as! Bool // "fine" 😝

// Cannot assign value of type 'some ExpressibleByBooleanLiteral' to type 'Bool'
anotherInstanceOfType1 = instanceOfType1

var instanceOfType2: some ExpressibleByBooleanLiteral = instanceOfType1 // fine
type(of: instanceOfType1) == type(of: instanceOfType2) // true

// Cannot assign value of type 'some ExpressibleByBooleanLiteral' (type of 'instanceOfType1')
// to type 'some ExpressibleByBooleanLiteral' (type of 'instanceOfType2')
instanceOfType2 = instanceOfType1

Originally, using opaque types in non-final declarations was forbidden.

opaque result types cannot be used for a non-final declaration within a class…

This restriction could conceivably be lifted in the future, but it would mean that override implementations would be constrained to returning the same type as their super implementation, meaning they must call super.method() to produce a valid return value.

The restriction has been lifted, but it is currently impossible to make use of it. E.g. even the simplest possible override will not compile.

override func someConfigurationView() -> some View {
  super.someConfigurationView()
}

Opaque types can provide no protection against breaking the 'L'-rule in SOLID. some View may actually be the same or a subtype of some View (as your AnyViews are), but there's no way for the language to enforce that.

…for example, that contract-breaking happens if you write without AnyView, as you should be be doing:

class AbstractOverride {
  @ViewBuilder func someConfigurationView() -> some View {
    if Features.TEST_VERSION {
      Text("override someConfigurationView()")
    }
  }
}

final class SubclassOverride {
  func someConfigurationView() -> some View {
    Text("Test")
  }
}

The least painful solution is to just completely disallow yourself from using class inheritance in Swift. It has never been idiomatic—it's really only there for interaction with Objective-C.

struct ContentView<
  ConfigurationViewProvider: ModuleName.ConfigurationViewProvider
>: View {
  var body: some View {
    ConfigurationViewProvider.someConfigurationView
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView<SubclassOverride>()
  }
}
protocol ConfigurationViewProvider {
  associatedtype ConfigurationView: View

  @ViewBuilder static var someConfigurationView: ConfigurationView { get }
}

enum AbstractOverride: ConfigurationViewProvider {
  static var someConfigurationView: some View {
    if Features.TEST_VERSION {
      Text("override someConfigurationView()")
    }
  }
}

enum SubclassOverride: ConfigurationViewProvider {
  static var someConfigurationView: some View {
    Text("Test")
  }
}
like image 134
Jessy Avatar answered May 31 '23 03:05

Jessy