1

考虑一个 C++ API,例如const T* foo(). 这清楚地记录了 API 支持的可变性和使用:好的,我们让你看看 T,但请不要更改它。你仍然可以改变它,但你必须明确地使用const_cast来表明你不遵循 API 的意图。

Objective-C API 的很大一部分由属性声明组成。API 的用户应该如何解释@property (readonly) T foo?(假设 T 不是不可变类型)

  • 由于 setter 没有合成,显然foo不意味着要被替换
  • 但是,getter 仍然给了我一个指向foo. 突变 foo 是否安全?(显然我可以

注意:我不是在询问语言规范。我问的是在 Objective-C 社区中对这样的 API 的传统解释是什么。

4

1 回答 1

2

正如马特所说,你有一个指向对象的指针这一事实并不意味着对象本身是可变的。Objective-C 使用类的行为而不是指针来强制执行不变性。因此,通常您应该看到返回的只读属性,例如,NSString而不是NSMutableString.

我查看了 Apple 的 iOS 框架标头来验证这一点:

grep -rn "@property.*readonly.*Mutable" /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/*.h

(Cocoa 中类名的模式是调用类的可变“版本” $PREFIXMutable$CLASSNAMENSString/ NSMutableStringNSDictionary/ NSMutableDictionary。)

./System/Library/Frameworks/AVFoundation.framework/Headers/AVComposition.h:133:@property (nonatomic, readonly) NSArray<AVMutableCompositionTrack *> *tracks; ./System/Library/Frameworks/CoreData.framework/Headers/NSManagedObjectContext.h:149:@property (nonatomic, readonly, strong) NSMutableDictionary *userInfo NS_AVAILABLE(10_7, 5_0); ./System/Library/Frameworks/Foundation.framework/Headers/NSAttributedString.h:54:@property (readonly, retain) NSMutableString *mutableString; ./System/Library/Frameworks/Foundation.framework/Headers/NSExpression.h:127:@property (readonly, copy) id (^expressionBlock)(id __nullable, NSArray *, NSMutableDictionary * __nullable) NS_AVAILABLE(10_6, 4_0); ./System/Library/Frameworks/Foundation.framework/Headers/NSThread.h:24:@property (readonly, retain) NSMutableDictionary *threadDictionary; . /System/Library/Frameworks/GameplayKit.framework/Headers/GKRuleSystem.h:54:@property (nonatomic, retain, readonly) NSMutableDictionary *state; ./System/Library/Frameworks/ModelIO.framework/Headers/MDLMesh.h:137:@property (nonatomic, readonly, retain) NSMutableArray *submeshes;

只有七个结果,其中一个NSExpression不算,因为搜索找到的“可变”是 Block 的一个参数,它实际上是属性的值。

对于其他人,我认为您会发现适当的类参考文档会告诉您可以使用这些值做什么和不能做什么。

例如,文档threadDictionary有这样说:

您可以使用返回的字典来存储特定于线程的数据。[...]您可以为字典定义自己的键。

精确地返回一个可变字典,以便您可以对其进行变异。但是,线程对象不允许您设置它,以便它也可以在那里存储东西。

NSAttributedString.h 中的命中实际上是在NSMutableAttributedString类中,这些文档说明

接收者跟踪这个字符串的变化并保持它的属性映射是最新的。

由于NSAttributedString非常明确地*只是一个NSString打包了一堆属性,类的设计直接暴露了被包装的字符串;可变版本紧随其后。

UIButton评论中提到了,因为您有一个只读标签,其自己的属性是可修改的。再一次,文档是明确的

虽然这个属性是只读的,但它自己的属性是读/写的。主要使用这些属性来配置按钮的文本。

不要使用标签对象来设置文本颜色或阴影颜色。

总之,Objective-C 在语言级别没有办法创建或强制执行可变性限制。正如您所指出的,标记的属性readonly仅意味着您无法将值设置为其他值。**并且没有等效于const_cast将值设置为可变以便您可以更改它:您最终会得到一个供应商对象一无所知的新值。

因此,Cocoa约定是通过使用不可变类来辅助强制属性的状态。(在某些情况下,您甚至可能会获得该类内部保留为可变的数据的不可变副本。)如果 API 为您提供了一个可变对象,您可以假设您可以对其进行变异,但文档应该准确地告诉您如何可以使用它


*它的类描述说:“一个NSAttributedString对象管理字符串和应用于字符串中单个字符或字符范围的相关属性集(例如,字体和字距调整)。”

**有 KVC,但这又是在框架级别,并且框架约定表明您正在自找麻烦。

于 2016-02-11T06:20:30.287 回答