3

我有一个包含如下属性的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

如果我@optionalPlayback 协议注释掉,那么它编译得很好。

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

4

2 回答 2

2

来自论坛上的Jordan Rose(在SE-0070实施时从事 Swift 工作) :

通常可选要求会增加额外的可选性:

  • 方法本身成为可选的 ( f.bar?())
  • Optional属性 getter 将值包装在( if let bar = f.bar)的额外级别中

但是对于属性设置器来说,没有地方可以放置额外的 Optional 级别。这就是整个故事:我们从来没有想出如何以安全的方式公开可选的属性设置器,并且不想选择任何特定的不安全解决方案。如果有人能想到一些很棒的东西!

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

直到(如果)这被支持,有两种潜在的解决方法:

  1. 引入一个显式方法Playback,将值分配给assetURL

    • 遗憾的是,这个方法无法命名-setAssetURL:,因为它将被导入 Swift,就好像它是属性设置器而不是方法一样,您仍然无法调用它。assetURL(如果您标记为 ,这仍然是正确的readonly
    • 同样可悲的是,这个方法不能有一个默认实现,因为 Objective-C 不支持默认协议实现,你不能在 Swift 中给这个方法一个实现,extension因为你仍然不能分配给协议
  2. 像你在 Swift 中所做的那样,引入一个协议层次结构,例如,一个AssetBackedPlayback协议继承自PlaybackassetURL作为非属性提供@optional

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

    然后,您需要找到一种公开方式PlayerController.currentPlayerManagerAssetBackedPlayback以便分配assetURL.


约旦的一些其他替代品:

我认为最初推荐的解决方法是“static inline在 Objective-C 中编写一个函数来为你做这件事”,但这也不是很好。setValue(_:forKey:)如果它不在热门路径中,在实践中也可以足够好。

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

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

于 2021-12-07T02:43:14.053 回答
0

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

于 2021-12-06T04:59:32.827 回答