I would expect copy
on an object to generate a new object. But it seems it will only generate a different one and may reuse it for another 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);
But why does it act like this?
-copy
and -copyWithZone:
are described by the NSCopying
protocol, which says the following:
The exact meaning of “copy” can vary from class to class, but a copy must be a functionally independent object with values identical to the original at the time the copy was made. A copy produced with
NSCopying
is implicitly retained by the sender, who is responsible for releasing it.
The object returned by -copy
/-copyWithZone:
can be any object which is considered equal to the original object, while being functionally independent. Critically, this means that immutable objects are allowed to implement -copy
by returning self
, since they cannot be modified before or after the copy, which means that two instances with the same value would be indistinguishable (allowing them to skip making an actual copy, since it would be needless work).
This is a pretty common optimization, and plenty of types implement it, including NSString
(esp. with constant strings), certain collections like NSArray
and NSDictionary
, and others:
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
In general, you should not rely on -copy
returning a different object from the original if the type is immutable, since it can include this optimization.
(Immutable copies of mutable objects, and mutable copies in general, cannot behave this way, because mutations of either the original or the new object are not allowed to change one another; those copies are truly distinct.)
In your specific case: the blocks you declared are immutable objects, and are allowed to return self
from -copy
, since they cannot be changed, and there's no reason to perform a full copy of the block.
It's not just that block3 == block4
, but that block1 == block3 == block4
, since -copy
is returning the original block:
NSLog(@"%p == %p: %d", block1, block3, block1 == block3); // => 0x600001dc0b70 == 0x600001dc0b70: 1
NSLog(@"%p == %p: %d", block1, block4, block1 == block4); // => 0x600001dc0b70 == 0x600001dc0b70: 1
This is not true for all types of blocks; if you're curious, this SO answer about "global" blocks, "stack" blocks, and "malloc" blocks also describes copying behavior: https://stackoverflow.com/a/29160233/169394