Jeffery Thomas
Jeffery Thomas

Reputation: 42598

overriding a property causes infinite recursion

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.

Upvotes: 1

Views: 2652

Answers (2)

Jeffery Thomas
Jeffery Thomas

Reputation: 42598

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.

Upvotes: 0

Cristik
Cristik

Reputation: 32925

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.

Upvotes: 2

Related Questions