简体   繁体   English

在Objective-C API中安全更改和使用只读属性值

[英]Safe mutation and usage of readonly property value in Objective-C APIs

Consider a C++ API like const T* foo() . 考虑像const T* foo()这样的C ++ API。 This clearly documents the supported mutability and use of the API: OK, we'll let you look at T, but please don't change it . 这清楚地记录了API的受支持的可变性和使用: 好的,我们让您看一下T,但是请不要更改它 You can still mutate it, but you have to explicitly use const_cast to indicate your intention to not follow the API. 您仍然可以对其进行变异,但是您必须显式使用const_cast来表明您打算不遵循API。

A good portion of Objective-C API's are comprised of property declarations. Objective-C API的很大一部分由属性声明组成。 How is a user of an API supposed to interpret : @property (readonly) T foo ? API的用户应如何解释@property (readonly) T foo (Assume T isn't an immutable type) (假设T不是一成不变的类型)

  • Since the setter isn't synthesized, clearly foo isn't mean to be replaced . 由于二传手不是合成的,因此显然foo并不意味着被替换
  • However, the getter still gives me a pointer to foo . 但是,getter仍然给我一个指向foo的指针。 Is it safe to mutate foo? 变异foo是否安全? (Clearly I can ) (显然我可以

NOTE: I'm not asking about the language specs. 注意:我不是在询问语言规格。 I'm asking about what the conventional interpretation of an API like this is within the Objective-C community. 我问的是,Objective-C社区中对此类API的常规解释是什么。

As matt said , the fact that you've got a pointer to the object does not mean that the object itself is mutable. 正如matt所说的,您拥有指向该对象的指针的事实并不意味着该对象本身是可变的。 Objective-C uses the behavior of the class, not the pointer, to enforce immutability. Objective-C使用类的行为而不是指针来实施不变性。 So in general you should be seeing read-only properties that return, eg, NSString rather than NSMutableString . 因此,通常您应该看到返回的只读属性,例如NSString而不是NSMutableString

I took a look through Apple's iOS framework headers to verify this: 我浏览了Apple的iOS框架标头以验证这一点:

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

(The pattern for class names in Cocoa is to call the mutable "version" of the class $PREFIXMutable$CLASSNAME : NSString / NSMutableString , NSDictionary / NSMutableDictionary .) (可可中类名称的模式是调用$PREFIXMutable$CLASSNAME类的可变“版本”: NSString / NSMutableStringNSDictionary / NSMutableDictionary 。)

./System/Library/Frameworks/AVFoundation.framework/Headers/AVComposition.h:133:@property (nonatomic, readonly) NSArray<AVMutableCompositionTrack *> *tracks; ./System/Library/Frameworks/AVFoundation.framework/Headers/AVComposition.h:133:@property(非原子,只读)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/CoreData.framework/Headers/NSManagedObjectContext.h:149:@property(非原子,只读,强)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/NSAttributedString.h:54:@property(只读,保留)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/NSExpression.h:127:@property(只读,复制)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/Foundation.framework/Headers/NSThread.h:24:@property(只读,保留)NSMutableDictionary * threadDictionary; ./System/Library/Frameworks/GameplayKit.framework/Headers/GKRuleSystem.h:54:@property (nonatomic, retain, readonly) NSMutableDictionary *state; ./System/Library/Frameworks/GameplayKit.framework/Headers/GKRuleSystem.h:54:@property(非原子,保留,只读)NSMutableDictionary * state; ./System/Library/Frameworks/ModelIO.framework/Headers/MDLMesh.h:137:@property (nonatomic, readonly, retain) NSMutableArray *submeshes; ./System/Library/Frameworks/ModelIO.framework/Headers/MDLMesh.h:137:@property(非原子,只读,保留)NSMutableArray *子网格;

Only seven results, and the one in NSExpression doesn't count because the "Mutable" that the search found is an argument to the Block that is actually the property's value. 只有七个结果, NSExpression中的一个不计算在内,因为搜索找到的“ Mutable”是Block的一个参数,实际上是该属性的值。

For the others, I think you'll find that the appropriate class reference doc tells you what you can and can't do with the values. 对于其他人,我认为您会发现适当的类引用文档会告诉您可以使用和不能使用值的内容。

For example, the documentation for threadDictionary has this to say: 例如, threadDictionary文档说明如下:

You can use the returned dictionary to store thread-specific data.[...]You may define your own keys for the dictionary. 您可以使用返回的字典来存储特定于线程的数据。[...]您可以为字典定义自己的键。

A mutable dictionary is returned precisely so that you can mutate it. 可变字典会精确返回,以便您对其进行突变。 The thread object doesn't let you set it, however, so that it can also store things there. 但是,线程对象不允许您对其进行设置,因此它也可以在其中存储内容。

The hit in NSAttributedString.h is actually in the NSMutableAttributedString class, and those docs note : NSAttributedString.h中的命中实际上在NSMutableAttributedString类中, 这些文档指出

The receiver tracks changes to this string and keeps its attribute mappings up to date. 接收者跟踪对此字符串的更改,并保持其属性映射为最新。

Since NSAttributedString is pretty explicitly* just an NSString packaged up with a bunch of attributes, the design of the class exposes the wrapped string directly; 由于NSAttributedString非常明显*,只是一个NSString打包了许多属性,因此类的设计直接公开了包装的字符串; the mutable version follows suit. 可变版本也是如此。

UIButton was mentioned in the comments, because there you have a read-only label whose own properties are modifiable. 注释中提到了UIButton ,因为那里有一个只读标签,其自身属性可以修改。 And there again, the docs are explicit : 同样, 文档是显式的

Although this property is read-only, its own properties are read/write. 尽管此属性是只读的,但它自己的属性是读/写。 Use these properties primarily to configure the text of the button. 主要使用这些属性来配置按钮的文本。

and

Do not use the label object to set the text color or the shadow color. 不要使用标签对象设置文本颜色或阴影颜色。

In summary, there's no way in Objective-C at the language level to create or enforce mutability restrictions. 总而言之,Objective-C在语言级别上无法创建或强制实施可变性限制。 As you've noted, a property marked readonly simply means there's no way for you to set the value to something else.** And there's no equivalent of const_cast ing the value to be mutable so that you can change it: you will end up with a new value that the vendor object knows nothing about. 正如您已经指出的,标记为readonly的属性只是意味着您无法将值设置为其他值。**而且没有const_cast将该值设置为可变的以便您可以更改它:您将最终得到具有供应商对象一无所知的新值。

The Cocoa convention, then, is to secondarily enforce the property's status by using immutable classes. 因此, 可可约定是通过使用不可变类来第二次增强财产的地位。 (In some cases you might even be getting an immutable copy of data that the class internally retains as mutable.) If the API gives you a mutable object, you can assume that you may mutate it, but the documentation should tell you exactly how you can use it . (在某些情况下,您甚至可能会获得该类在内部保留为可变的数据的不可变副本。)如果API为您提供了可变的对象,则可以假定您可以对其进行突变,但是文档应该确切地告诉您如何可以使用它


*Its class description says : "An NSAttributedString object manages character strings and associated sets of attributes (for example, font and kerning) that apply to individual characters or ranges of characters in the string." *其类描述说 :“ NSAttributedString对象管理字符串以及应用于字符串中各个字符或字符范围的关联属性集(例如,字体和字距调整)。”

**There is KVC, but that's again at the framework level, and framework convention would indicate that you're asking for trouble doing that. **有KVC,但这又是在框架级别,框架约定将表明您在这样做时遇到麻烦。

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

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