ObjC 基础-copy与mutableCopy的区别与用法(Foundation框架自带有区分可变/不可变能力类与其他没有区分能力类在拷贝本质上的区别)

76 阅读6分钟

Objc 基础-copy与mutableCopy的区别

用过文本拷贝的小伙办都知道吧,假定A文本的内容是一封情书,拷贝生成B文本那也是一封情书,和A文本的内容是一致的,修改B文本对A文本是没有任何影响的,同样修改A文本对B文本也没有影响,这两个文件是独立存在互不影响的。开发中的拷贝也是一样的道理。

拷贝的特点是什么?产生一个副本对象,副本对象与源对象互不影响。

  • 修改副本对象不影响源对象。
  • 修改源对象不影响副本对象。
  • 拷贝只适用于那些遵循NSCopying或NSMutableCopying协议的对象类型,继承自NSObject的子类都满足

拷贝方法分两种:copymutableCopy

严格来讲mutableCopy操作是只给Foundation框架自带的一些类去做事情的,只有他们才有区分产生可变或不可变的权利,如:字符串、字典、数组等;其他类是没有的,如:自定义类、UIKit中的类等。 只要是遵循NSCopying或NSMutableCopying协议的对象类型都具备拷贝能力,所以,只要是继承自NSObject的类都可以调用mutableCopy。

拷贝在底层怎么进行内存管理的?

  • MRC环境下 当调用allocnewcopymutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它;
  • ARC环境下 编译器会自动进行内存管理,了解MRC环境下怎么进行内存管理,有助于开发理解。

是否具备具备区分可变/不可变的能力?

  • 一般只有Foundation框架自带的一些类才具备区分可变/不可变的能力,如:字符串、数组、字典等。
    • copy和mutableCopy,使用有区别,返回可能是可变/不可变的对象。
  • 其他类是不具备区分可变/不可变的能力,如:自定义类、UIKit框架下的类。
    • 自定义Person类,Person对象的属性都是可以修改的,不区分可变/不可变,只有可变特性
    • copy和mutableCopy,使用没区别,返回的都是可变的对象。

区分可变/不可变的拷贝

Foundation框架自带的一些类才具备区分可变/不可变的能力,如:字符串、数组、字典等。

区分可变/不可变的类有哪些?

字符串:区分可变或不可变 NSString NSMutableString 
数组:区分可变或不可变 NSArray NSMutableArray 
字典:区分可变或不可变 NSDictionary NSMutableDictionary 
数据:区分可变或不可变NSData NSMutableData 
集合:区分可变或不可变 NSSet NSMutableSet 
...

拷贝类型分两种:

  • 浅拷贝:不拷贝源对象,不会生成副本对象,仅仅拷贝指向源对象的指针。
  • 深拷贝:拷贝源对象生成副本对象,两者在不同的内存

对拷贝存在的疑问?

  • 什么情况下是浅拷贝呢?

    只有一种情况,会是浅拷贝,那就是对不可变对象调用 copy 方法。

  • 那浅拷贝为什么是指针拷贝?不是说源对象拷贝会生成新的副本对象么?

    因为,源对象是不可变的对象,本身就是不可变的对象,编译器认为没必要浪费内存,再开辟新的内存来存储同样不可变的副本对象,干脆就不生成副本对象,直接拷贝指向源对象的指针,变相达到了拷贝的目的,拷贝之后,也能通过指针拿到内容,只是这个内容不能修改而已。

copy与mutableCopy 区别图

  
源对象类型 拷贝方法 副本对象类型 是否产生新对象 拷贝类型
不可变类型 copy 不可变类型 浅拷贝:指针拷贝
mutableCopy 可变类型 深拷贝:内容拷贝
可变类型 copy 不可变类型 深拷贝:内容拷贝
mutableCopy 可变对象 深拷贝:内容拷贝

copy与mutableCopy 案例分析

copy

copy是不可变拷贝,产生的是不可变副本。不可变副本意味着内容是不可变。

调用copy拷贝不可变对象例子:

/*
 不可变对象
 */
NSString *str1 = [NSString stringWithFormat:@"test"];
/*
 copy:拷贝不可变对象,返回的是指向不可变源对象的指针,
 两者的地址相同,指向同一个对象,为浅拷贝。
 */
NSString *str2 = [str1 copy];
// 结果:str1:0xf2b3163347d64815 str2:0xf2b3163347d64815
NSLog(@"str1:%p str2:%p",str1,str2);

// 修改str2 程序崩溃 error:unrecognized selector sent to instance 0x66c820d963b9ed45'
NSMutableString *str2Mut = (NSMutableString *)str2;
[str2Mut appendString:@"123"];

调用copy拷贝可变对象例子:

/**
 可变对象
 */
NSMutableString *mutStr1 = [NSMutableString stringWithFormat:@"test"];
/*
 copy:拷贝不可变对象,返回的是不可变的副本对象。
 两者地址不同,内容相同都是test,为深拷贝。
 */
NSString *str2 = [mutStr1 copy];
// 结果不同:mutStr1:0x101326ea0 str2:0x86b4b755183513b5
NSLog(@"mutStr1:%p str2:%p",mutStr1,str2);

// 修改str2 程序崩溃 error:unrecognized selector sent to instance 0x66c820d963b9ed45'
NSMutableString *mutStr2 = (NSMutableString *)str2;
[mutStr2 appendString:@"123"];

mutableCopy

mutableCopy是可变拷贝,产生的是可变副本。可变副本意味着内容是可变。

调用mutableCopy拷贝不可变对象例子:

/*
 不可变对象
 */
NSString *str1 = [NSString stringWithFormat:@"test"];
/*
 mutableCopy:拷贝不可变对象,返回的是可变副本对象,
 两者地址不同,内容相同都为test,为深拷贝。
 */
NSString *mutStr1 = [str1 mutableCopy];
// 结果:str1:0xb0d12937cf389f53 str2:0x100716f90
NSLog(@"str1:%p mutStr1:%p",str1,mutStr1);

// 可以成功修改
NSMutableString *mutStr2 = (NSMutableString *)mutStr1;
[mutStr2 appendString:@"123"];

调用mutableCopy拷贝可变对象例子:

/*
 可变对象
 */
NSMutableString *mutStr1 = [NSMutableString stringWithFormat:@"test"];
/*
 mutableCopy:拷贝可变对象,返回的是可变副本对象,
 两者地址不同,内容相同都为test
 */
NSMutableString *mutStr2 = [mutStr1 mutableCopy];
// 结果:mutStr1:0x10122d290 mutStr2:0x10122d800
NSLog(@"mutStr1:%p mutStr2:%p",mutStr1,mutStr2);
// 可以修改成
[mutStr2 appendString:@"123"];

不区分可变/不可变的拷贝

其他类是不具备区分可变/不可变的能力,如:自定义类、UIKit框架下的类。

  • 拷贝类型只有深拷贝,没有浅拷贝。
  • copymutableCopy 使用没区别,返回的都是可变的对象。
  • 只用copy完全能满足拷贝的要求啦,没必要用mutableCopy。
  • 使用时,要先遵循NSCopying或NSMutableCopy协议。

示例代码:

@interface FSLPerson : NSObject<NSCopying,NSMutableCopying>
@property (copy,nonatomic) NSString *name;
@property (assign,nonatomic) int age;
@property (strong,nonatomic) FSLCat *cat;
@end

@implementation FSLPerson

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

- (id)mutableCopyWithZone:(nullable NSZone *)zone{
    FSLPerson *person = [FSLPerson allocWithZone:zone];
    person.name = self.name;
    person.age = self.age;
    return person;
}


int main(int argc, char * argv[]) {
    FSLPerson *person = [[FSLPerson alloc] init];
    person.name = @"123";
    person.age = 30;
    
    FSLPerson *person1 = [person copy];
    person.name = @"qaz";
    
    FSLPerson *person2 = [person mutableCopy];
    person2.age = 20;
    
    return 0;
}