【问题标题】:Why can't I mutate an NSObject allocated as mutable, referenced as immutable, then cast back to mutable?为什么我不能改变分配为可变、引用为不可变的 NSObject,然后再转换回可变的 NSObject?
【发布时间】:2022-01-04 17:35:31
【问题描述】:

我正在分配一个 NSMutableAttributedString,然后将它分配给一个 SKLabelNode 的属性字符串属性。该属性是(NSAttributedString *),但我认为我可以将其转换为(NSMutableAttributedString *),因为它是这样分配的。然后访问它的 mutableString 属性,更新它,而不必每次我想更改字符串时都进行另一次分配。

但是在转换之后,对象是不可变的,当我尝试对其进行变异时会引发异常。

我不能改变一个被分配为可变的 NSObject 只是因为它被引用为不可变,这是真的吗?

【问题讨论】:

  • 但我认为我可以将其转换为 (NSMutableAttributedString *)。不,你不能。将不可变对象分配给声明为可变的属性会将类型更改为不可变。
  • @vadian:感谢您的回答。除非我误解,否则我假设您的意思是“将 mutable 对象分配给声明为 immutable 的属性会将类型更改为不可变。”这仍然是一个正确的说法吗?
  • 哦,我的错,是的。

标签: objective-c nsattributedstring nsobject nsmutableattributedstring


【解决方案1】:

我不能改变一个被分配为可变的 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 变量(如上面的 fs)与分配给 SKLabelNode 的 @987654322 之间的主要区别@property 位于属性的定义中:

@property(nonatomic, copy, nullable) NSAttributedString *attributedText;

具体来说,SKLabelNode 对其attributedText 属性的赋值执行复制,而对NSMutableAttributedString 执行复制会产生不可变 变体:

NSAttributedString *s = [[[NSMutableAttributedString alloc] initWithString:@"Hello"] copy];
NSLog(@"%@", [s class]); // => NSConcreteAttributedString

因此,当您以这种方式分配给您的 SKLabelNode 时,它不会存储您的原始实例,而是它自己的副本——而且这个副本恰好是不可变的。


请注意,这种行为是两件事的汇合:

  1. SKLabelNode选择-copy分配的变量;如果它改为-retained (例如@property(nonatomic, strong, nullable)),这将按您的预期工作
  2. 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>

【讨论】:

  • 这是一个了不起的答案。谢谢!
  • @eglynum 很高兴为您提供帮助! :)
猜你喜欢
  • 2017-05-02
  • 2019-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多