我期望一个对象上的copy会生成一个新对象.但它似乎只会产生一个不同的版本,并可能在另外copy个版本中重复使用.

void (^block1)(void) = ^ {
    // ...
};
void (^block2)(void) = ^ {
    // ...
};
typeof(block1) block3 = [block1 copy];
typeof(block1) block4 = [block1 copy];
typeof(block2) block5 = [block2 copy];

XCTAssertNotEqual(block1, block2);
XCTAssertNotEqual(block2, block3);
XCTAssertNotEqual(block3, block4); // Fail, [block1 copy] returns the same result for two requests
XCTAssertNotEqual(block4, block5);

但为什么会是这样呢?

推荐答案

-copy-copyWithZone:NSCopying协议描述,其内容如下:

"复制"的确切含义可以因类而异,但复制必须是一个功能独立的对象,其值必须与创建复制时的原始对象相同.用NSCopying制作的副本由发送者隐含保留,发送者负责发布它.

-copy/-copyWithZone:返回的对象可以是被认为与原始对象相等但在功能上独立的任何对象.关键的是,这意味着不变对象可以通过返回self来实现-copy,因为它们不能在复制之前或之后被修改,这意味着具有相同值的两个实例将是不可区分的(允许它们跳过制作实际副本,因为这将是不必要的工作).

这是一个非常常见的优化,很多类型都实现了它,包括NSString(特别是.使用常量字符串)、某些集合(如NSArrayNSDictionary)以及其他集合:

void printEqual(id obj) {
    id copy = [obj copy];
    NSLog(@"%p == %p: %d", obj, copy, obj == copy);
}

printEqual(@"Hello, world!"); // => 0x1046d8048 == 0x1046d8048: 1
printEqual(@[@1, @2, @3]); // => 0x1046dc078 == 0x1046dc078: 1
printEqual(@{@"greeting": @"Hello, world!"}); // => 0x1046dc090 == 0x1046dc090: 1

通常,如果类型是不可变的,则不应该依赖于-copy从原始对象返回different对象,因为它可以包括这种优化.

(可变对象的不可变副本以及一般的可变副本不能以这种方式运行,因为原始对象或新对象的Mutations 不允许彼此更改;这些副本是真正不同的.)


在您的特定情况下:您声明的块是不可变的对象,并且允许从-copy返回self,因为它们不能被更改,并且没有理由执行块的完整复制.

不仅仅是那block3 == block4,还有那block1 == block3 == block4,因为-copy返回的是原始块:

NSLog(@"%p == %p: %d", block1, block3, block1 == block3); // => 0x600001dc0b70 == 0x600001dc0b70: 1
NSLog(@"%p == %p: %d", block1, block4, block1 == block4); // => 0x600001dc0b70 == 0x600001dc0b70: 1

这并不适用于all种类型的块;如果您很好奇,下面的关于"全局"块、"堆栈"块和"Malloc"块的答案也描述了复制行为:https://stackoverflow.com/a/29160233/169394

Objective-c相关问答推荐

覆盖@property setter 和无限循环

日期更改时的 UIDatePicker

如何通过仅更改高度而不更改宽度来调整 UILabel 的大小?

iOS performSelectorOnMainThread 有多个参数

在 Objective-C 类中混合 C 函数

在 NSData 和 base64 字符串之间转换

如何获取 iPhone 的当前方向?

Container View 好像有 UINavigationBar 一样被下推?

iOS 6 Facebook 发布过程以remote_app_id 与存储的 id 不匹配结束

在 Objective-C/cocoa 中创建文件夹/目录

如何测试生产推送通知?

更改后退导航栏按钮的字体

xcode storyboard Container View - 如何访问视图控制器

UITableView configureCellForDisplay:forIndexPath 中的断言失败:

如何推送两个视图控制器但只为第二个设置动画过渡?

UITextField:键盘出现 timeshift 动视图

writeToFile:atomically: atomically 是什么意思?

当 UIView 框架更改时,视图内的 AVPlayer 层不会调整大小

如何配置我的应用程序 - 菜单项被禁用?

Xcode 的不完整实现警告