为什么@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 协议,没有拼写已决定明确实现此功能,而且此功能似乎并不常见,从那以后就没有真正出现过。
直到(如果)支持此功能,有两种可能的解决方法:
给Playback
引入显式方法,给assetURL
赋值
- 遗憾的是,此方法无法命名为
-setAssetURL:
,因为它将被导入 Swift,就好像它是 属性 setter 而不是方法,而您仍然无法调用它。 (如果您将 assetURL
标记为 readonly
,这仍然适用)
- 同样遗憾的是,此方法将无法拥有默认实现,因为 Objective-C 不支持默认协议实现,并且您无法在 [=73] 中为该方法提供实现=]
extension
因为你仍然不能分配给协议
像在 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 运行时支持大量动态,并且比简单的赋值要复杂得多。根据您的用例,为了避免复杂性,成本可能是可以接受的!
我有一个 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 协议,没有拼写已决定明确实现此功能,而且此功能似乎并不常见,从那以后就没有真正出现过。
直到(如果)支持此功能,有两种可能的解决方法:
给
赋值Playback
引入显式方法,给assetURL
- 遗憾的是,此方法无法命名为
-setAssetURL:
,因为它将被导入 Swift,就好像它是 属性 setter 而不是方法,而您仍然无法调用它。 (如果您将assetURL
标记为readonly
,这仍然适用) - 同样遗憾的是,此方法将无法拥有默认实现,因为 Objective-C 不支持默认协议实现,并且您无法在 [=73] 中为该方法提供实现=]
extension
因为你仍然不能分配给协议
- 遗憾的是,此方法无法命名为
像在 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 运行时支持大量动态,并且比简单的赋值要复杂得多。根据您的用例,为了避免复杂性,成本可能是可以接受的!