1

I have a class which is derived from a base class that has a property.


@interface TestProp: NSObject
@property (nonnull, nonatomic) NSString *prop;
@end

@interface TestBase: NSObject
@end

@interface TestClass: TestBase
- (nullable TestProp *)testGetter;
- (void)testSetter:(nullable TestProp *)test;
@property (nullable, nonatomic, readonly, getter=testGetter) TestProp *test;
@end

@implementation TestProp
@end

@implementation TestBase
@end

@implementation TestClass { TestProp *_test; }
- (TestProp *)testGetter { return _test; }
- (void)testSetter:(TestProp *)test { _test = test; }
@end

I wrote a swift extension to make the property available in the base class.


extension TestBase {
    var test: TestProp? {
        guard let testClass = self as? TestClass else { return nil }
        return testClass.test
    }
}

The problem is testClass.test calls the getter from TestBase instead of TestClass. This results in infinite recursion that smashes the stack.


Is this a defect? Is there a way I can make it work?


UPDATE

When I first thought of it, I was hoping I could treat it like a function.

extension TestBase {
    var test: TestProp? {
        return nil
    }
}

and the following would happen:

func callback(_ testBase: TestBase) {
    guard let prop = testBase.prop else { return }

    …
}

callback(TestBase()) // uses TestBase.prop
callback(TestClass()) // uses TestClass.prop

That didn't work. The actual behavior was

callback(TestBase()) // uses TestBase.prop
callback(TestClass()) // uses TestBase.prop

Properties don't work like functions. So the extension

extension TestBase {
    var test: TestProp? {
        guard let testClass = self as? TestClass else { return nil }
        return testClass.test
    }
}

was my attempt to force the use of TestCase.prop.

I'm trying to avoid

func callback(_ testBase: TestBase) {
    guard let prop = (testBase as? TestClass)?.prop else { return }

    …
}

FINAL UPDATE

I discovered a method that worked, see my answer if your are interested.

However, it's just not worth it. The solution was too complex. I created a private global function.

private func prop(_ testBase: TestBase) -> TestProp {
    return (testBase as? TestClass)?.prop
}

then in the callbacks

func callback(_ testBase: TestBase) {
    guard let prop = prop(testBase) else { return }

    …
}

Not object oriented, but simple.

Jeffery Thomas
  • 42,202
  • 8
  • 92
  • 117
  • 3
    Your getter is called testGetter and thats what you need to call. – Sulthan Sep 05 '18 at 16:49
  • What's the point of `testGetter` and `testSetter` functions? Why not just use the property syntax to auto-synthesize the getter/setter? – Alexander Sep 05 '18 at 16:52
  • @Sulthan I get an error: Value of type 'TestClass' has no member 'testGetter'. – Jeffery Thomas Sep 05 '18 at 16:53
  • That's not how polymorphism works. Once a method is overridden by a subclass, you can't invoke the superclass' implementation of it, except from within the subclass' overload's implementation. – Alexander Sep 05 '18 at 16:53
  • @Alexander I would love for it to work that way, but it's calling the implementation in the base class not in the subclass. – Jeffery Thomas Sep 05 '18 at 16:57
  • @JefferyThomas That's fundamentally not how it works, and it shouldn't. A subclass can impose certain invariants, that are preserved by the behaviour of its overridden implementations of superclass methods. If the superclass implementations were allowed to be invoked, that would bypass any such invariant-preserving behaviour that the subclass put in place. It would be a complete disaster. – Alexander Sep 05 '18 at 17:06
  • @JefferyThomas What exactly are you trying to achieve? – Alexander Sep 05 '18 at 17:07
  • You used the tag `Swift` but you are asking for help with Objective-C. You should delete the `Swift` tag. – Duncan C Sep 05 '18 at 18:25
  • @DuncanC The extension is in swift. – Jeffery Thomas Sep 05 '18 at 23:23
  • @Alexander Instead of calling `(testBase as? TestClass)?.prop`, I want to add prop to `TestBase`. – Jeffery Thomas Sep 05 '18 at 23:26
  • @Alexander I added an updated to the problem. – Jeffery Thomas Sep 05 '18 at 23:53
  • @JefferyThomas You're still not getting to the root of *why* you're trying to do this. What's the problem you're trying to solve, that requires you to invoke superclass implementations of methods? – Alexander Sep 06 '18 at 19:59
  • @Alexander I was never trying to invoke a superclass implementation. I was trying to extend a property of a subclass into the superclass. If your interested, see my answer and the final update to my question. – Jeffery Thomas Sep 07 '18 at 14:56
  • @JefferyThomas I see, but in any case, this discussion is too narrow-minded and focused on the mechanics of some particular (very strange, red flag) approach to a solution, to a problem that has not even been defined for us. If it works for you, great, but this is some fishy stuff going on. – Alexander Sep 07 '18 at 16:21

2 Answers2

2

Leaving aside the fact that your code "smells" (you reference a subclass from a superclass, you add a property to a subclass and then to the base class in an extension instead of adding it right to the base class - override in a superclass), what happens is that Swift, as many other programming languages, is statically dispatched, which means that it will eagerly resolve method calls (the getter in this situation) to fixed memory addresses.

This results in Swift resolving the getter address to the one from the base class, since stuff declared in extensions are not overridable: Overriding methods in Swift extensions, http://blog.flaviocaetano.com/post/this-is-how-to-override-extension-methods/.

This is why you get the infinite recursion.

Solution is simple, declare the property in the base class, return nil there and the actual property in the subclass.

Cristik
  • 30,989
  • 25
  • 91
  • 127
0

Thanks to @Cristik tip on static vs dynamic dispatch, I managed to get it to work.

protocol TestBaseProp {
    var test: TestProp? { get }
}

extension TestBaseProp {
    var test: TestProp? {
        guard let testClass = self as? TestClass else { return nil }
        return testClass.test
    }
}

extension TestBase: TestBaseProp { }

NOTE: I tried

extension TestBaseProp {
    var test: TestProp? { return nil }
}

but that didn't work.

Jeffery Thomas
  • 42,202
  • 8
  • 92
  • 117