我不能改变一个被分配为可变的 NSObject 只是因为它被引用为不可变,这是真的吗?
不,您的一般直觉是正确的。暂时忽略一般的“可变”和“不可变”的概念,而是关注NS<SomeType> 和NSMutable<SomeType> 之间的子类化关系:通常,Apple 框架具有可变/不可变对应物的对象具有 mutable 变体作为不可变变体的 子类型。将 mutable 变量分配给 immutable 变量不会改变存储变量的任何内容,如下所示:
@interface Foo: NSObject @end
@implementation Foo @end
@interface Bar: Foo @end
@implementation Bar @end
Foo *f = [[Bar alloc] init];
NSLog(@"%@", f); // => <Bar: 0x6000014b0040>
你可以看到与NSMutableAttributedString 类似的东西(虽然它有点复杂,因为NSAttributedString 和子类型形成了class cluster:
NSAttributedString *s = [[NSMutableAttributedString alloc] initWithString:@"Hello"];
NSLog(@"%@", [s class]); // => NSConcreteMutableAttributedString
然而:分配给 local 变量(如上面的 f 和 s)与分配给 SKLabelNode 的 @987654322 之间的主要区别@property 位于属性的定义中:
@property(nonatomic, copy, nullable) NSAttributedString *attributedText;
具体来说,SKLabelNode 对其attributedText 属性的赋值执行复制,而对NSMutableAttributedString 执行复制会产生不可变 变体:
NSAttributedString *s = [[[NSMutableAttributedString alloc] initWithString:@"Hello"] copy];
NSLog(@"%@", [s class]); // => NSConcreteAttributedString
因此,当您以这种方式分配给您的 SKLabelNode 时,它不会存储您的原始实例,而是它自己的副本——而且这个副本恰好是不可变的。
请注意,这种行为是两件事的汇合:
-
SKLabelNode选择到-copy分配的变量;如果它改为-retained (例如@property(nonatomic, strong, nullable)),这将按您的预期工作
-
NSMutableAttributedString 从其 -copy 方法返回一个 NSAttributedString,但它必须这样做。事实上,大多数类型从 -copy 返回 instancetype,但 NSMutableAttributedString 选择 从其 -copy 方法返回 NSAttributedString。 (嗯,这就是类簇的重点:-copy → 不可变,-mutableCopy → 可变)
因此,通常情况下不必如此,但您会看到使用这些规则实现的可变/不可变类集群的这种行为。
为了比较,与上面的Foo 示例:
@interface Foo: NSObject @end
@implementation Foo
- (instancetype)copyWithZone:(NSZone *)zone {
// Expects to return a new Foo:
return [[[self class] alloc] init];
// OR:
// Not all types allow copying:
return self;
}
@end
@interface Bar: Foo @end
@implementation Bar @end
Foo *f = [[[Bar alloc] init] copy];
NSLog(@"%@", f); // => <Bar: 0x600001e7c1a0>