Is it right to call class func from final class?

Hello, I'm a student studying Swift. I am writing to ask for your opinion because I have a question.

We know that class func is a type method with static func. Also, it can be used in class types. One of the important features is that if the class keyword is given, it allows override.

In fact, testing on structures and enumerations results in compilation errors if class func is defined.

Attaching the final keyword to a class does not allow inheritance. In my opinion, class func is meaningless because overriding is not necessary in this case.

Nevertheless, there is no compilation error, and both static func and class func in the final class work well.

Is there a reason why class func is allowed in the final class?

final class Temp {          // Case 1) final class
  static func method1() {
    print("static method")
  }
  
  class func method2() {    // âś… impossible
    print("class method")
  }
}

struct Sample {             // Case 2) struct
  static func method1() {
    print("static method")
  }

  class func method2() {    // ❌ impossible
    print("class method")
  }
}

enum Sample2 {              // Case 3) Enum
  static func method1() {
    print("static method")
  }

  class func method2() {    // ❌ impossible
    print("class method")
  }
}

I think of it similarly to how you can have a struct that has mutable properties, but they no longer can be changed when declared with let.

For example with a struct like:

struct SomeValue {
  var value: Int = 1
}

var mutableCopy = SomeValue()
mutableCopy.value = 5 // this is ok!

let inertCopy = SomeValue()
inertCopy.value = 7 // doesn’t compile

The way that I view it is that any class can have a class func, and this also works I believe:

class CustomizableClass {
  class func customAction() {}
}

final class Customization: CustomizableClass {
  override class func customAction() {
    // call super and do custom stuff
  }
}

class DisallowedClass: Customization {} // doesn’t compile 

Final overrides the inheritance but doesn’t change the other behaviors the class can have.

2 Likes

Why would there be a compilation error? Since both method1() and method2() are associated with the type Temp, it's perfectly valid to call them. As you pointed out, marking the class with final only impacts inheritance.

2 Likes

Is there a reason why class func is allowed in the final class?

It makes sense when you consider inheritance. Expanding on your example:

class TempSuper {
    class func method2() {
        print("class method")
    }
}

final class Temp: TempSuper {
    override class func method2() {
        print("class method")
    }
}

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes

Thank you for your reply. I think I missed the part that I can define class func when I want to use it as a super class.

I think you're right. All classes can have class functionality, and it is correct that they work properly depending on the situation.

Thank you :slight_smile:

ps.
The example given by the structure above was very helpful to me.

1 Like

That's right, compilation errors don't occur! I was thinking the wrong way. 'final' is literally a keyword that only affects inheritance. Thank you for your reply.

Thank you for kindly writing down the examples as well :) I think my thoughts were closed. That was a very helpful answer for a student who just started studying!

Taking OP position in this case "override class func method2()" should be allowed but not "class func anotherMethod() {}"

In this example "override class func method2()" is effectively:

    override static func method2() { ... }

which, btw, compiles :crazy_face:

This also compiles:

final class Temp {
  final class func method2() {}
}

The question is (rephrasing): shouldn't compiler help you (noticing that methods of the final class are not overridable) by prohibiting having non-final methods? Including instance methods, BTW, as the same logic applies to them:

final class Temp {
    final class func foo() {}
    final func bar() {}
    /* non final */ class func baz() {} // 🔶
    /* non final */ func qux() {}       // 🔶
}

On one hand, yes that makes sense, and prohibiting the last two would remind you about their "non overridability". OTOH it is not a DRY approach (you are repeating your intent in two places) and will be PITA in practice when you toggle the final-ness of the class and have to adjust the final-ness of all its methods. All in all, the current behaviour is good and if to do anything perhaps IDE could be enhanced to highlight overridable vs non-overridable methods differently.


The other question is why do we have "static func" methods to begin with... given that that's the same as "final class func", and that this latter notation is more general as it also works with instance "final func" methods.

1 Like

In case you want to be very explicit, SwiftLint comes with a new rule non_overridable_class_declaration in its latest 0.53.0 release. It triggers on non-final class declarations in final classes and offers automatic fixes with either the final class or static modifiers.

1 Like

A clunky attempt by us Swift 1.0ers to come to consistency across classes, structs and enums, and protocols. :-/ All of these have “type methods”, but you can’t really say structs and enums have “class methods”, and you can’t say classes have “static” methods that are actually overridable and thus dynamic, and we couldn’t figure out a good way to say “type method” because we didn’t want to claim type as a keyword* and because a “type function” sounds like a function that operates on types as much as a function that lives on a type.

Our compromise was to allow static everywhere, but for classes we’d make it equivalent to final class, as you know very well today. Not a great answer, but when you’re trying to keep most of Objective-C’s functionality for classes and make your structs smart and allow protocols that work with both, you have to compromise somewhere.

* It’s a common convenient variable name, as well as a not-uncommon argument label, from before we allowed keywords as argument labels.

6 Likes