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

Why do properties modified by @optional become immutable?

我有一个 Objective-C 协议,其中包含 属性 如下:

#import <Foundation/Foundation.h>

@protocol Playback <NSObject>

@optional

@property (nonatomic, nonnull) NSURL *assetURL;

@end

PlayerController 有一个 属性 类型 id<Playback>:

@interface PlayerController: NSObject

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

@end

我试图在 Swift 中编写以下代码,但出现 错误:

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

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

如果我为 Playback 协议注释掉 @optional,那么它可以正常编译。

这让我想知道为什么 @optional 会导致这个错误?

由于它是可选的,Swift 不能保证 setter 被实现。

来自 Jordan Rose(他在 SE-0070 was implemented) on the forums 时从事 Swift:

Normally optional requirements add an extra level of optionality:

  • Methods become optional themselves (f.bar?())
  • Property getters wrap the value in an extra level of Optional (if let bar = f.bar)

But there's nowhere to put that extra level of Optional for a property setter. 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!

所以答案似乎是:当时 optional 协议要求有意限制为 Swift (SE-0070) 中的 Objective-C 协议,没有拼写已决定明确实现此功能,而且此功能似乎并不常见,从那以后就没有真正出现过。

直到(如果)支持此功能,有两种可能的解决方法:

  1. Playback引入显式方法,给assetURL

    赋值
    • 遗憾的是,此方法无法命名为 -setAssetURL:,因为它将被导入 Swift,就好像它是 属性 setter 而不是方法,而您仍然无法调用它。 (如果您将 assetURL 标记为 readonly,这仍然适用)
    • 同样遗憾的是,此方法将无法拥有默认实现,因为 Objective-C 不支持默认协议实现,并且您无法在 [=73] 中为该方法提供实现=] extension 因为你仍然不能分配给协议
  2. 像在 Swift 中那样做并引入协议 层次结构 ,例如,AssetBackedPlayback 协议继承自Playback 并提供 assetURL 作为非 @optional-属性 代替:

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

    然后您需要找到一种方法将 PlayerController.currentPlayerManager 公开为 AssetBackedPlayback 以便分配 assetURL.


约旦的一些其他替代品:

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. setValue(_:forKey:) can also be good enough in practice if it's not in a hot path.

static inline 函数推荐的功能类似于默认协议实现,但您确实需要记住调用该函数而不是直接访问 属性。

setValue(_:forKey:) 也可以工作,但会导致明显的性能损失,因为它通过 Objective-C 运行时支持大量动态,并且比简单的赋值要复杂得多。根据您的用例,为了避免复杂性,成本可能是可以接受的!