Swiftify-Corp / Swiftify

43 stars 24 forks source link

Properties of `id<SomeProtocol>` are sometimes declared as `weak var` after conversion #202

Closed amorde closed 2 years ago

amorde commented 2 years ago

Given:

@import Foundation;

#import "SomeProtocol.h"

@interface SomeObject: NSObject
@property (nonatomic, readonly) id<SomeProtocol> someProperty;
@end

result:

import Foundation

class SomeObject: NSObject {
    private(set) weak var someProperty: SomeProtocol?
}
alex-swiftify commented 2 years ago

Thanks, @amorde for reporting!

As currently implemented, weak var is generated by the converter in the following cases:

The weak keyword is not generated in the following cases (exclusions):

After some Google'ing, and reading the post here:

As a general rule, delegates should be marked as weak because most delegates are referencing classes that they do not own. This is definitely true when a child is using a delegate to communicate with a parent. Using a weak reference for the delegate is what the documentation recommends. (But see this, too.)

... it looks like declaring protocol references as weak is what Apple documentation recommends, although there are always exceptions.

Can you suggest exact changes to our rules explained above with regards to whether we generate the weak keyword or not?

Alternatively, we could introduce a conversion option like "Declare variables or properties of protocol types as weak", but we are trying to have as minimum conversion options as possible to simoplify the testing.

AlexanderJ commented 2 years ago

To me it looks like a delegate is only one of many use cases of this and the recommendation seems to fit that use case. In general it seems safer to not declare this as weak and maybe only do this if the property name contains "delegate". Especially when protocol oriented programming is used this can cause a lot of trouble.

alex-swiftify commented 2 years ago

@AlexanderJ It's an interesting point, although I personally don't find relying on the property name containing "delegate" a good enough solution (while I understand the reasoning for that).

Currently, I don't see a better option than adding a "Declare variables or properties of protocol types as weak" as a configurable option.

amorde commented 2 years ago

The delegate pattern should use weak as you mentioned - however, if the Objective-C code isn't doing this correctly, I don't think the converter should be the one trying to "fix" that issue.

There are many uses for objects of protocol types that are not the delegate pattern, so assuming that any reference to a protocol must be weak does not feel safe to me.

I agree with @AlexanderJ that checking the property name and/or the type name for "delegate" might be better. But if the Objective-C code did not ever use weak then the Swift code shouldn't either

AlexanderJ commented 2 years ago

That's a good point. The Objective-C code might have declared it as weak and only in that case it should be done. Otherwise you change the semantics which is quite dangerous.

alex-swiftify commented 2 years ago

@amorde

The delegate pattern should use weak as you mentioned - however, if the Objective-C code isn't doing this correctly, I don't think the converter should be the one trying to "fix" that issue.

– this is a great point, and completely agreeable.

I agree with @AlexanderJ that checking the property name and/or the type name for "delegate" might be better. But if the Objective-C code did not ever use weak then the Swift code shouldn't either

From what you say, it's not very obvious if you suggest an alternate behavior depending on whether the identifier name or the type name contains delegate:

Finally, I'm going to change the rules from the ones we currently use to the following.

Generate weak var in the following case(s):

Don't generate weak var in the following cases (exclusions):

@amorde @AlexanderJ Please confirm.

AlexanderJ commented 2 years ago

According to the developer documentation a property is strong by default. So unless you see a weak in the property declaration there should be no weak in the Swift translation. https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/EncapsulatingData/EncapsulatingData.html

alex-swiftify commented 2 years ago

Another case to retest - https://github.com/Swiftify-Corp/Swiftify/issues/217

alex-swiftify commented 2 years ago

@amorde @AlexanderJ Fixed by changing the rules to the ones specified below. Note that there are small changes after my last suggestion.

The changes will be available since the next converter update. Your feedback is welcome, as always.

Generate weak var in the following case(s):

Don't generate weak var in the following cases (exclusions):

Converter output:

import Foundation

class SomeObject: NSObject {
    private(set) var someProperty: SomeProtocol?
}