简体   繁体   English

为什么@optional 修改的属性会变成不可变的?

[英]Why do properties modified by @optional become immutable?

I have an Objective-C protocol that contains a property as follows:我有一个包含如下属性的Objective-C协议:

#import <Foundation/Foundation.h>

@protocol Playback <NSObject>

@optional

@property (nonatomic, nonnull) NSURL *assetURL;

@end

PlayerController has a property of type id<Playback> : PlayerController有一个id<Playback>类型的属性:

@interface PlayerController: NSObject

@property (nonatomic, strong, nonnull) id<Playback> currentPlayerManager;

@end

I tried to write the following code in Swift , but I got an error :我尝试在Swift中编写以下代码,但出现错误

var player = PlayerController()
var pla = player.currentPlayerManager

pla.assetURL = URL(string: "123") // ❌ Cannot assign to property: 'pla' is immutable

If I comment out the @optional for the Playback protocol, then it compiles fine.如果我注释掉Playback协议的@optional ,那么它编译得很好。

This makes me wonder why @optional would cause this error?这让我想知道为什么@optional会导致这个错误?

Since it's optional, Swift can't guarantee the setter is implemented.由于它是可选的,Swift 不能保证 setter 的实现。

From Jordan Rose (who worked on Swift at the time that SE-0070 was implemented) on the forums :来自论坛上的Jordan Rose(在实施SE-0070时从事 Swift 工作):

Normally optional requirements add an extra level of optionality:通常可选要求会增加额外的可选性:

  • Methods become optional themselves ( f.bar?() )方法本身成为可选的( f.bar?()
  • Property getters wrap the value in an extra level of Optional ( if let bar = f.bar )属性 getter 将值包装在Optional的额外级别( if let bar = f.bar

But there's nowhere to put that extra level of Optional for a property setter.但是对于属性设置器来说,没有地方可以放置额外的 Optional 级别。 That's really the entire story: we never figured out how to expose optional property setters in a safe way, and didn't want to pick any particular unsafe solution.这就是整个故事:我们从来没有想出如何以安全的方式公开可选的属性设置器,并且不想选择任何特定的不安全解决方案。 If someone can think of something that'd be great!如果有人能想到一些很棒的东西!

So the answer appears to be: at the time that optional protocol requirements were intentionally limited to Objective-C protocols in Swift ( SE-0070 ), no spelling for an explicit implementation of this was decided on, and it appears that this functionality is uncommon enough that this hasn't really come up since.所以答案似乎是:在optional协议要求被故意限制为 Swift ( SE-0070 ) 中的 Objective-C 协议时,没有决定明确实现的拼写,而且看起来这个功能并不常见,以至于从那以后就没有真正出现过。

Until (and if) this is supported, there are two potential workarounds:直到(如果)这被支持,有两种潜在的解决方法:

  1. Introduce an explicit method to Playback which assigns a value to assetURLPlayback引入一个显式方法,该方法为assetURL

    • Sadly, this method cannot be named -setAssetURL: because it will be imported into Swift as if it were the property setter instead of a method, and you still won't be able to call it.遗憾的是,此方法不能命名为-setAssetURL:因为它将被导入 Swift 中,就好像它是属性设置器而不是方法一样,您仍然无法调用它。 (This is still true if you mark assetURL as readonly ) (如果您将assetURL标记为readonly ,这仍然是正确的)
    • Also sadly, this method won't be able to have a default implementation, since Objective-C doesn't support default protocol implementations, and you can't give the method an implementation in a Swift extension because you still can't assign to the protocol同样可悲的是,此方法将无法具有默认实现,因为 Objective-C 不支持默认协议实现,并且您无法在 Swift extension中为该方法提供实现,因为您仍然无法分配给协议
  2. Do like you would in Swift and introduce a protocol hierarchy , where, for example, an AssetBackedPlayback protocol inherits from Playback and offers assetURL as a non- @optional -property instead:像在 Swift 中那样做,并引入协议层次结构,例如, AssetBackedPlayback协议继承自Playback并提供assetURL作为非@optional

     @protocol Playback <NSObject> // Playback methods @end @protocol AssetBackedPlayback: Playback @property (nonatomic, nonnull) NSURL *assetURL; @end

    You would then need to find a way to expose PlayerController.currentPlayerManager as an AssetBackedPlayback in order to assign the assetURL .然后,您需要找到一种方法将PlayerController.currentPlayerManager公开为AssetBackedPlayback以便分配assetURL


Some additional alternatives from Jordan:约旦的一些其他替代品:

I think the original recommended workaround was "write a static inline function in Objective-C to do it for you", but that's not wonderful either.我认为最初推荐的解决方法是“在 Objective-C 中编写一个static inline function 来为你做这件事”,但这也不是很好。 setValue(_:forKey:) can also be good enough in practice if it's not in a hot path.如果setValue(_:forKey:)不在热门路径中,它在实践中也足够好。

The static inline function recommendation can function similarly to a default protocol implementation, but you do need to remember to call that function instead of accessing the property directly. The static inline function recommendation can function similarly to a default protocol implementation, but you do need to remember to call that function instead of accessing the property directly.

setValue(_:forKey:) will also work, but incurs a noticeable performance penalty because it supports a lot of dynamism through the Objective-C runtime, and is significantly more complicated than a simple assignment. setValue(_:forKey:)也可以工作,但会导致明显的性能损失,因为它通过 Objective-C 运行时支持很多动态,并且比简单的分配要复杂得多。 Depending on your use-case, the cost may be acceptable in order to avoid complexity!根据您的用例,成本可能是可以接受的,以避免复杂性!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM