The basic concept

Here are a few concepts of copy for iOS development:

  1. Copy: replication, the purpose is to produce a copy, so that the original object and the copy are independent of each other, do not affect each other;
  2. Immutable copy: The copy method produces an immutable copy regardless of whether the original object is mutable.
  3. MutableCopy: the mutableCopy method produces a mutableCopy regardless of whether the original object is mutable.
  4. Deep copy: content is copied to create new objects;
  5. Shallow copy: a pointer copy that does not create new objects.

Copy and deep copy are two different concepts.

  • If the source object is immutable, the copy method is a shallow copy.
  • When the source object is variable, the copy method is called a deep copy.
  • The mutableCopy method is deep copy in any case;

The code analysis

With ARC off, look at two pieces of code:

Case 1: NSString

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *str1 = [[NSString alloc] initWithFormat:@"123abcdefghijklmn"];
        NSString *str2 = [str1 copy];
        NSMutableString *str3 = [str1 mutableCopy];
        
        NSLog(@"%zd %zd %zd",str1.retainCount, str2.retainCount, str3.retainCount);
        NSLog(@"%p %p %p",str1, str2, str3);
    }
    return 0;
}
Copy the code

Guess what the print was? Here are the results:

2019-12-26 17:23:23.020793+0800 XKCopyTest[1862:60872367] 2019-12-26 17:23:23.021176+0800 XKCopyTest[1862:60872367] 2019-12-26 17:23:23.021176+0800 XKCopyTest[1862:60872367] 2019-12-26 17:23:23.021176+0800 XKCopyTest[1862:60872367] 0x100610460 0x100610460 0x1006103b0 Program ended withexit code: 0
Copy the code

Instead of looking at the reference counter, look at the memory address, and you can see from the print:

  1. Str1 wasNSStringType, belonging to immutable objects;
  2. Str2copyMethod representation is immutable copy, and an immutable copy needs to be obtained;
  3. Str2 has the same address as STR1, and no new object is created.

This leads to the first conclusion:

  • The copy in STR2 is shallow;

Since str2 has the same address, it still points to the same object as str1, and no new object is created, the copy is shallow.

  • In shallow copycopyThe method is equivalent toretain

Since this is a shallow copy, no new objects are created, and the pointer str2 still points to the source object, the copy method executes the logical equivalent of retain, which simply increments the reference counter of the source object by 1, so the final str1.retainCount result is 2. Since str2 points to the source object, it is natural that str2. RetainCount will also print 2.

The reason for this is that, just like when we use PC files for copying, the essence of copying is to create a separate and non-interfering copy of the source file. Specifically, two files can be modified without affecting the other file. Because the str1 is immutable objects, the copy method generated is an immutable object, source object is immutable, so don’t worry about the condition of the source object is modified, so the str2 pointing directly to the source object, can not only realize copy are independent of each other, the purpose of each other noninterference, also don’t have to generate a new memory, save memory space, kill two birds with one stone.


Looking at str3 again, from the printed results we can get:

  1. Str3mutableCopyA mutable copy is required;
  2. str3The address andstr1If not, a new object is generated;

Because the new object is created, the copy operation in STR3 is a deep copy. Str3 also points to the memory address of the newly generated object, so the reference counter is 1. The str1 and str2 Pointers point to the same object and are referred to by str1 and str2, so the final reference counter prints 2.


Case two: NSMutableString

Change str1 to a mutable type, i.e. NSMutableString, as follows:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableString *str1 = [[NSMutableString alloc] initWithFormat:@"123abcdefghijklmn"];
        NSString *str2 = [str1 copy];
        NSMutableString *str3 = [str1 mutableCopy];
        
        NSLog(@"%zd %zd %zd",str1.retainCount, str2.retainCount, str3.retainCount);
        NSLog(@"%p %p %p",str1, str2, str3);
    }
    return 0;
}
Copy the code

What about the printout? Here are the results:

[197:60876834] 2019-12-26 17:29:07.661591+0800 XKCopyTest[197:60876834] 2019-12-26 17:29:07.661965+0800 XKCopyTest[197:60876834] 2019-12-26 17:29:07.661965+0800 XKCopyTest[1937:60876834] 0x10057c600 0x1005086d0 0x100508700 Program ended withexit code: 0
Copy the code

Once you understand case one, it’s easy to look at this. From the print and the code, you can conclude:

  • The copy in STR2 is a deep copy;

The copy in STR2 is still an immutable copy, but the source object is a mutable object, so a new object must be generated. If a new object is generated, it is a deep copy.

  • MutableCopy must be a deep copy;

MutableCopy needs to make mutable copies, so whether the source object is mutable or immutable, the mutableCopy method generates a new object, so it must be a deep copy.

For array, Dictionary, data, the same, this article will not repeat.


Copy modifier

We know that there are deep copies and shallow copies, so what does the copy keyword in the @property do? Is there a mutablecopy keyword?

First the conclusion:

  • Properties of thecopyThe function of the keyword is to call the object assigned to the propertycopyWithZoneMethod and assign the return value to the property;

To look at the source code, first look at a common property declaration code:

@interface XKPerson()
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;

@end

@implementation XKPerson

@end
Copy the code

Generate a CPP file using the compile directive:

xcrun  -sdk  iphoneos  clang  -arch  arm64  -rewrite-objc XKPerson.m -o XKPerson.cpp
Copy the code

Then let’s find out what the resulting code for the property looks like. The implementation code for the property in the CPP file looks like this:

// @interface XKPerson()
// @property (copy, nonatomic) NSString *name;
// @property (assign, nonatomic) NSInteger age;
/* @end */

// @implementation XKPerson

static NSString * _I_XKPerson_name(XKPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_XKPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_XKPerson_setName_(XKPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct XKPerson, _name), (id)name, 0, 1); }

static NSInteger _I_XKPerson_age(XKPerson * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_XKPerson$_age)); }
static void _I_XKPerson_setAge_(XKPerson * self, SEL _cmd, NSInteger age) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_XKPerson$_age)) = age; }
// @end
Copy the code

In other words, at sign property just tells the compiler to generate the setter and getter methods for me, which declares and implements four methods:

  1. static NSString * _I_XKPerson_name
  2. static void I_XKPerson_setName
  3. static NSInteger _I_XKPerson_age
  4. static void I_XKPerson_setAge

Here, because we’re talking about copy in the property, and copy only works when we set the property, we just need to focus on the _I_XKPerson_setName_ method, and the core thing is that we call objc_setProperty(), So let’s go to the source code for ObjC4, download the source code and see what the objc_setProperty function does. The code looks like this:

#define MUTABLE_COPY 2void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) { bool copy = (shouldCopy && shouldCopy ! = MUTABLE_COPY); bool mutableCopy = (shouldCopy == MUTABLE_COPY); (1) reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy); }Copy the code

The rest of the code is not pasted, the MUTABLE_COPY value is 2, and the setter is 1, and it ends up in this reallySetProperty method:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if(offset == 0) {// change isa to point to object_setClass(self, newValue);return; } id oldValue; id *slot = (id*) ((char*)self + offset); // Copy logicif(copy) {// attribute modifier keyword only copy newValue = [newValue copyWithZone:nil]; }else ifNewValue = [newValue mutableCopyWithZone:nil]; newValue = [newValue mutableCopyWithZone:nil]; }else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if(! atomic) { oldValue = *slot; *slot = newValue; }else{ spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); oldValue = *slot; *slot = newValue; slotlock.unlock(); } // Release objc_release(oldValue); }Copy the code

In fact, this code is pretty classic, but let’s just look at the copy part:

// Copy logicifNewValue = [newValue copyWithZone:nil]; }else ifNewValue = [newValue mutableCopyWithZone:nil]; newValue = [newValue mutableCopyWithZone:nil]; }else {
        if (*slot == newValue) return; // If you call copy on an immutable object such as NSString, the internal code logic will go to this point. NewValue = objc_retain(newValue); }Copy the code

It’s clear from the source code:

  1. If it iscopyMethod, the object’scopyWithZoneMethods;
  2. If it ismutablecopy, the object’smutableCopyWithZoneMethods;
  3. ifcopy = 0.mutablecopy = 0, will eventually callobjc_retainMethods;

When using the retain attribute, which is the third case, the code can also be verified by changing the copy to the compiled result after retain:

Static void _I_XKPerson_setName_(XKPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct XKPerson, _name), (id)name, 0, 0); }Copy the code

In addition, if the strong and assign modifier is used, the final setter does not call the objc_setProperty method but instead assigns to the pointer by the offset or directly, which is not discussed here


Here’s a question:

If you use copy, then the value of the objc_setProperty method passed in the setter of the property is always going to be 1 as the last parameter. And objc_setProperty calls the reallySetProperty method internally. When it’s 1, the argument in the call to the reallySetProperty method is always bool Copy = 1, bool mutablecopy = 0, so it goes to copyWithZone. Bool copy = 0, bool mutablecopy = 0, obje_retain

The keyword Value of the copy parameter Parameter mutablecopy Code logic
retain 0 0 obje_retain()
copy 1 0 copyWithZone()

MutablecopyWithZone code in the reallySetProperty method is called when the last argument of objc_setProperty is 2, so when is the mutablecopyWithZone code called??


Use copy to modify the string meaning

To conclude, the meaning of the copy attribute is:

  • Make it clear to the user that the designer does not want and cannot directly modify the value of the memory address to which the property object points;

You don’t want to see this reflected in using NSString to declare properties so that if you use appendString:, a compilation error will be reported.

However, if you use strong to modify the string attribute, and cast it, you can still modify the value in the memory address directly:

@property (strong, nonatomic) NSString *name1; XKPerson *p = [XKPerson new]; NSMutableString *name = [[NSMutableString alloc] initWithFormat:@"% @"The @"Jack"]; // In this case, if name is declared as NSString, the actual type is NSMutableString. Name = name; [(NSMutableString *)p.name appendFormat:@"1"];
Copy the code

So this code is modifying the value in the memory address that the property is pointing to, and it’s going to use copy, because copyWithZone is going to return an immutable type, so even if it’s an NSMutableString, it’s still going to be an NSString, This had the desired effect;

So, NSString is best decorated with copy ~~~

In a scenario where we want the string property to change along with the value of a string object, we use strong + NSMutableString:

@property (strong, nonatomic) NSMutableString *name; // Use XKPerson *p = [XKPerson new]; NSMutableString *name = [NSMutableString stringWithFormat:@"The king"];
    p.name = name;
    NSLog(@"% @",p.name);
    [name appendString:@"Small 2"];
    NSLog(@"% @",p.name);
Copy the code

Results:

2019-12-27 17:53:51.730 XKStringTest[10172:63201310] 2019-12-27 17:53:51.731 XKStringTest[10172:63201310Copy the code

[self.name appendString: @” XXX “] crashes when copy is used, just like when copy is used with NSMutableArray.

Once again: copy is to get an immutable copy, mutableCopy is to get a mutableCopy, and the only keyword that decorates the property is copy.

In this scenario, we should actually use mutableCopy to create a mutable independent copy of the string, but there is no mutableCopy keyword modifier attribute.

So remember 😯 :

  • Do not use copy in attributes to modify types such as NSMutableArray and NSMutableDictionary.

The copy agreement

The previous chapter explained that the essence of the copy keyword in a property modifier is to call the copyWithZone method. We can use copy to decorate strings, arrays, and so on because these system objects implement the protocol associated with copy.

So here’s the problem: customize the copyWithZone method. The most common thing we use copy for is strings

Therefore, we are provided with two protocols in OC:

@protocol NSCopying
- (id)copyWithZone:(nullable NSZone *)zone;
@end

@protocol NSMutableCopying
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
@end
Copy the code

The specific implementation is as follows:

- (id)copyWithZone:(NSZone *)zone {
    XKPerson *newP = [[XKPerson allocWithZone:zone] init];
    newP.name = self.name;
    newP.age = self.age;
    return newP;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
    XKPerson *newP = [[XKPerson allocWithZone:zone] init];
    newP.name = self.name;
    newP.age = 10;
    return newP;
}
Copy the code

Use:

XKPerson *p = [XKPerson new];
    p.name = @"Jack";
    p.age = 18;
    XKPerson *p1 = [p copy];
    XKPerson *p2 = [p mutableCopy];
    NSLog(@"%p %p %p", p, p1, p2);
    
    p1.name = @"Lucy";
    NSLog(@"% @",p1.name);
Copy the code

Results:

2019-12-27 17:42:11.326 XKStringTest[9991:63192515] 0x7ae30B10 0x7ae31340 0x7ae313E0 2019-12-27 17:42:11.327 XKStringTest[9991:63192515] 0x7ae30B10 0x7ae31340 0x7ae313e0 XKStringTest[9991:63192515] LucyCopy the code

Copy In the data model model, the copy protocol may be used more. In this case, the copy protocol can be implemented. However, in normal times, the copy protocol is not used much by custom objects

Why do blocks use copy

This will be covered in the block progression, but to get to the core here, look at a piece of code to see the essence. Non-arc mode code is as follows:

Int main(int argc, const char * argv[]) {@autoreleasepool {// look at the copy method int age = 10; void(^block)(void) = ^(void){ NSLog(@"%d",age);
        };
        
        NSLog(@"%p",block);
        NSLog(@"% @",[block class]);
        block = [block copy];
        NSLog(@"%p",block);
        NSLog(@"% @",[block class]);

    }
    return 0;
}
Copy the code

Print result:

2020-01-03 09:02:36.888792+0800 XKBlockTest[84409:72373799] 0x7ffeefBff488 2020-01-03 09:02:36.889375+0800 XKBlockTest[84409:72373799] __NSStackBlock__ 2020-01-03 09:02:36.889904+0800 XKBlockTest[84409:72373799] 0x1006444b0 The 2020-01-03 09:02:36. 889961 + 0800 XKBlockTest __NSMallocBlock__ [84409-72373799]Copy the code

Analysis:

  1. The code is just for testing, so it’s omittedreleaseOperations such as;
  2. The essence of copy is similar to the implementation of string, etc., which returns an immutable object;
  3. Because of the accessautoThe Block of the variable as__NSStackBlock__Store on the stack, otherwise as__NSGlobalBlock__Stored in the data area (global area), the stack Block will be destroyed at any time, so the system implementation of the copy operation in the heap to create a new Block and return, this is the two print memory results are different;
  4. Blocks are marked as__NSMallocBlock__Type, non-ARC requires manualrelease;
  5. When a Block is decorated with copy, just like a string, it calls the copy method of the new value, which is the copy method of the Block;
  6. The Block attribute usesweakNeedless to say, it does not call the copy method or increment the reference counter;

Let’s get straight to the conclusion:

ARC will proactively call the copy method of a Block in certain situations, such as when it is modified by strong, the GCD Block, the system UsingBlock, etc.

  • ARC blocks can be modified with copy or strong
  1. When copy is invoked, the StackBlock is copied to the heap as a MallocBlock.
  2. When using strong, the copy method is automatically called in ARC mode, and the StackBlock is copied to the heap as a MallocBlock.
  • The Block attribute in MRC must be decorated with copy
  1. When copy is invoked, the StackBlock is copied to the heap as a MallocBlock.
  2. In MRC mode, the strong clause only retains the Block and does not automatically call the copy method of the Block.

conclusion

Above, summarize the key points of copy, convenient memory:

  1. The purpose of copy is to create a non-interfering, independent copy;
  2. Copy, whether a direct call or a decorated property, is essentially a callcopyWithZoneandmutableCopyWithZoneMethods;
  3. The difference between deep and shallow copy is whether the return value is a newly created object, regardless of which method of copy is called;
  4. The key purpose of using the copy attribute is to tell the user that this is not to directly modify the value that the attribute points to in memory;
  5. Do not use copy to decorate objects of mutable type, such as mutable arrays.
  6. The essence of copy is to call the two methods of copy protocol, but the system implements the two methods of the protocol on strings, arrays, dictionaries, NSNumbers and blocks, two of which implement the same logic.
  7. The essence of the copy attribute is to automatically call the copy method of the new value to get an immutable object. The attribute is not mutable, because it is not necessary;
  8. Copy refers to the copy method, but its internal implementation is to move the Block on the stack to the heap. Otherwise, after the Block on the stack is destroyed, the pointer to the Block will be accessed and there will be bad memory access problems;