【iOS】对象复制与属性关键字

发布于:2025-09-05 ⋅ 阅读:(22) ⋅ 点赞:(0)

目录

对象复制

一、copy与mutableCopy方法

二、NSCopying和NSmutableCopying协议

三、深复制与浅复制

按照类型说明:

非容器类对象的深拷贝与浅拷贝

不可变字符串

可变类型字符串 

容器类对象的深浅拷贝

 自定义类型的拷贝

容器类对象的深拷贝

归档与解档代码实现

属性关键字

1 原子性

2 读写权限

3. 内存管理

assign

weak

unsafe_unretained

retain

strong

copy

4. 修饰变量关键字

常量(const)和宏定义(define)的区别:

Q&A:

copy和strong的区别:(深拷贝 浅拷贝)

Q:以下代码会出现什么问题?

Q:assign 和 weak 关键字的区别有哪些?

Q:atomic 修饰的属性是怎么样保存线程安全的?

Q:weak和assign的区别?

对象复制

一、copy与mutableCopy方法

copy方法用于复制对象的副本,复制下来的该副本是不可修改的,哪怕是调用NSMutableString的copy方法也不可修改。

而mutableCopy方法复制下来的副本是可修改的,即使被复制的对象原本是不可修改的。例如调用mutableCopy方法复制NSString的,返回的是一个NSMutableString对象。

 以下用代码演示copy和mutableCopy方法的功能:

#import <Foundation/Foundation.h>
 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //copy与mutableCopy
        NSMutableString *book = [NSMutableString stringWithString: @"疯狂iOS讲义"];
        
        NSMutableString *bookCopy = [book mutableCopy]
        
        [bookCopy replaceCharactersInRange: NSMakeRange(2, 3) withString: @"Android"];//复制后的bookCopy副本是可以修改的,这里做个修改,对原字符串的值也没有影响
        
        NSLog(@"book的值为:%@",book);//原值
        
        NSLog(@"bookCopy的值为:%@",bookCopy);//副本修改后的值

        NSString *str = @"fkit";//定义一个str字符串
        NSMutableString *strCopy = [str mutableCopy];//用mutableCopy给str复制一个副本
        
        [strCopy appendString:@".org"];//向可变字符串后面追加字符串
        NSLog(@"%@",strCopy);
        
        NSMutableString *bookCopy2 = [book copy];//用copy方法复制一个book的副本(这个副本不可变)
        [bookCopy2 appendString:@"aa"];//这里会报错,因为copy创建的副本不可变,修改了就崩了
        
    }
    return 0;
}

二、NSCopying和NSmutableCopying协议

当我们想将自定义类用上一节的两个方法复制副本时,我们可能会直接创建完对象后用”类名* 对象2 = [对象1 copy];“这样的格式来复制副本,但实际上直接这样复制是不对的,会报错说找不到copyWithZone:方法,mutableCopy也是一样。因此我们可以看出,自定义类是不能直接调用这两个方法来复制自身的。

        这是为什么呢?是因为当程序调用copy/mutableCopy方法复制时,程序底层需要调用copyWithZone:/mutableCopyWithZone:方法来完成复制的工作,并返回这两个方法的值。因此为了保证可以复制,需要在自定义类的接口部分声明NSCopying/NSMutableCopying协议,然后再类的实现部分增加copyWithZone:/mutableCopyWithZone:方法,因此,对自定义对象的复制应该如下所示:

接下来,我们以一个自定义的Person类为例,支持copy和mutablecopy:

#import <Foundation/Foundation.h>

@interface Person : NSObject <NSCopying, NSMutableCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end

@implementation Person

// 实现 copy(返回不可变副本)
- (id)copyWithZone:(NSZone *)zone {
    Person *copy = [[[self class] allocWithZone:zone] init];
    copy.name = [self.name copy]; 
    copy.age = self.age;
    return copy;
}

// 实现 mutableCopy(返回可变副本)
- (id)mutableCopyWithZone:(NSZone *)zone {
    Person *copy = [[[self class] allocWithZone:zone] init];
    copy.name = [self.name mutableCopy];  // 注意生成可变副本
    copy.age = self.age;
    return copy;
}

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p1 = [[Person alloc] init];
        p1.name = @"Tom";
        p1.age = 18;
        
        Person *p2 = [p1 copy];         // 调用 copyWithZone
        Person *p3 = [p1 mutableCopy];  // 调用 mutableCopyWithZone
        
        NSLog(@"原始:%@ %ld", p1.name, p1.age);
        NSLog(@"copy:%@ %ld", p2.name, p2.age);
        NSLog(@"mutableCopy:%@ %ld", p3.name, p3.age);
    }
    return 0;
}

三、深复制与浅复制

深复制和浅复制是面向对象编程中非常重要的概念。

浅复制:仅复制对象的指针地址,多个变量共享同一个对象。

深复制: 不仅复制指针,还会复制整个对象内容,使得原对象和副本完全独立。

举个例子:

NSMutableString *str1 = [NSMutableString stringWithString:@"Hello"];
NSMutableString *str2 = str1;            // 浅复制(赋值)
// 修改 str1,str2 也变了
[str1 appendString:@" World"];

因此总而言之,浅拷贝就是创建一个副本,对内存地址的复制。深拷贝就是创建一个副本,对内容完全复制。原始对象与副本对象内存地址不同。

按照类型说明:

非容器类对象的深拷贝与浅拷贝

不可变字符串
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString* str1 = @"dddddd";
        NSString* str2 = [str1 copy];
        NSString* str3 = [str1 mutableCopy];
        NSMutableString* str4 = [str1 copy];
        NSMutableString* str5 = [str1 mutableCopy];
        NSLog(@"str1:%p", str1);
        NSLog(@"str2:%p", str2);
        NSLog(@"str3:%p", str3);
        NSLog(@"str4:%p", str4);
        NSLog(@"str5:%p", str5);
    }
    return 0;
}

得出结论: 不可变字符串,只要是copy就是浅拷贝,mutableCopy是深拷贝。tips:我们用NSString stringWithstring 方式创建的是一个常量区字符串。

可变类型字符串 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableString* str1 = [NSMutableString stringWithString:@"helloworld"];
        NSMutableString* str2 = [str1 copy];
        NSMutableString* str3 = [str1 mutableCopy];
        NSString* str4 = [str1 copy];
        NSString* str5 = [str1 mutableCopy];
        NSLog(@"str1:%p", str1);
        NSLog(@"str2:%p", str2);
        NSLog(@"str3:%p", str3);
        NSLog(@"str4:%p", str4);
        NSLog(@"str5:%p", str5);
    }
    return 0;
}

 打印结果:

 这里我们发现这里的两种拷贝都是深拷贝,它都重新创建了一块新的区域储存这一部分内容

因此:

可变对象copy后的对象是不可变的,mutableCopy后的对象是可变的

对于可变对象的复制都是深拷贝

容器类对象的深浅拷贝

NSArray:NSArray 是 Objective-C 中的 不可变数组类,它在创建之后 元素数量和顺序都不能更改

NSArray *array01 = [NSArray arrayWithObjects:@"a",@"b",@"c", nil];
        NSArray *copyArray01 = [array01 copy];
        NSMutableArray *mutableCopyArray01 = [array01 mutableCopy];
        NSLog(@"array01 = %p,copyArray01 = %p",array01,copyArray01);
        NSLog(@"array01 = %p,mutableCopyArray01 = %p",array01,mutableCopyArray01);
//-----------------------------------------------------
        NSLog(@"array01[0] = %p,array01[1] = %p,array01[2] = %p",array01[0],array01[1],array01[2]);
        NSLog(@"copyArray01[0] = %p,copyArray01[1] = %p,copyArray01[2] = %p",copyArray01[0],copyArray01[1],copyArray01[2]);
        NSLog(@"mutableCopyArray01[0] = %p,mutableCopyArray01[1] = %p,mutableCopyArray01[2] = %p",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]);

可以得出下列结论:

copyArray01和array01指向的是同一个对象,包括里面的元素也是指向相同的地址。
mutableCopyArray01和array01指向的是不同的对象,但是里面的元素指向相同的对象,mutableCopyArray01可以修改自己的对象。
copyArray01是对array01的指针复制(浅复制),而mutableCopyArray01是内容复制。
所以这就是不可变的容器对象 copy是浅拷贝 mutablecopy是深拷贝

NSMutableArray:NSMutableArray 是 Objective-C 中的可变数组类,可以动态添加、删除、替换数组中的元素。

NSMutableArray *array01 = [NSMutableArray arrayWithObjects:@"a",@"b",@"c", nil];
        NSArray *copyArray01 = [array01 copy];
        NSMutableArray *mutableCopyArray01 = [array01 mutableCopy];
        NSLog(@"array01 = %p,copyArray01 = %p",array01,copyArray01);
        NSLog(@"array01 = %p,mutableCopyArray01 = %p",array01,mutableCopyArray01);
    
    
        NSLog(@"array01[0] = %p,array01[1] = %p,array01[2] = %p",array01[0],array01[1],array01[2]);
        NSLog(@"copyArray01[0] = %p,copyArray01[1] = %p,copyArray01[2] = %p",copyArray01[0],copyArray01[1],copyArray01[2]);
        NSLog(@"mutableCopyArray01[0] = %p,mutableCopyArray01[1] = %p,mutableCopyArray01[2] = %p",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]);

 我们不难发现,对于可变对象的拷贝都是一个新对象,深拷贝。

 但是,集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制

用更为严谨的表达来说:

集合的 copy/mutableCopy 是容器级别的拷贝,里面的元素对象不会被复制,而是指针指向原对象。

 自定义类型的拷贝

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end

NS_ASSUME_NONNULL_END
#import "Person.h"
@interface Person()<NSCopying,NSMutableCopying>

@end

@implementation Person

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


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

@end
#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.name = @"Jack";
        person.age = 23;
     
        Person *copyPerson = [person copy]; // 深拷贝
        Person *mutableCopyPerson = [person mutableCopy]; // 深拷贝
        NSLog(@"person = %p;copyPerson = %p",person,copyPerson);
        NSLog(@"person = %p;mutableCopyPerson = %p",person,mutableCopyPerson);
   }
    return 0;
}

 我们可以看到无论是copy还是mutableCopy,都是深拷贝,这是因为我们本来就是使用copyWithZone和mutableCopyWithZone进行拷贝的。

容器类对象的深拷贝

1. copyItems 方法是NSArray和NSMutableArray的一个方法,用于创建一个新的数组,并对数组中的元素进行拷贝。该方法遍历原数组的每个元素,并对每个元素执行copy操作,然后将拷贝后的元素添加到新数组中。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableString* str1;
        FKUser* p1 = [[FKUser alloc] initWithName:@"krystal" pass:@"12"];
        FKUser* p2 = [p1 copy];
        NSArray* ary = [NSArray arrayWithObjects:p1, p2, nil];
        NSArray* ary1 = [[NSArray alloc] initWithArray:ary copyItems:YES];
        NSLog(@"%p, %p", ary, ary1);
        NSLog(@"%p, %p", ary1[0], ary[0]);
      	FKUser* p3 = ary1[0];
        FKUser* p4 = ary[0];
        NSLog(@"%p, %p", p3.name, p4.name);
    }
    return 0;
}

 我们对于数组内部的元素也进行一了一次深拷贝。这个方法实现了我们的对于容器内部的一个深拷贝。但是,要注意一个点,就是我们容器类对象中的元素是非容器类才可以实现一个复制,但是注意这里我们没有对对象进行更深一层的拷贝。

归档与解档代码实现

实现解档与归档:之前有了解过,这里再回顾一遍加深印象

关键在与为自定义类实现两个方法,如下:

#import "Coffee.h"
​
@implementation Coffee
​
+ (BOOL)supportsSecureCoding {
    return YES;
}
​
- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.name forKey:@"name"];
}
​
- (instancetype)initWithCoder:(NSCoder *)coder {
    if (self = [super init]) {
        _name = [coder decodeObjectOfClass:[NSString class] forKey:@"name"];
    }
    return self;
}
​
- (id)copyWithZone:(NSZone *)zone {
    Coffee* coffee = [[[self class] allocWithZone:zone] init];
    coffee.name = [self.name copy];
    return coffee;;
}
@end
​
  //使用
NSData* data = [NSKeyedArchiver archivedDataWithRootObject:coffee requiringSecureCoding:YES error:nil];
​
Coffee* copyCoffee = [NSKeyedUnarchiver unarchivedObjectOfClass:[Coffee class] fromData:data error:nil];

总而言之:

可变对象 -> copy :深拷贝 -> 不可变对象

可变对象 -> mutableCopy : 深拷贝 -> 可变对象

不可变对象 -> copy : 浅拷贝 -> 不可变对象

不可变对象 -> mutableCopy : 深拷贝 -> 可变对象

属性关键字

分类

属性关键字

原子性 atomic、nonatomic
读写 readwrite.  readonly. setter\getter
内存管理 assign、weak、unsafe_unretained、retain、strong、copy
可空性

(nullable、_Nullable 、__nullable)、
(nonnull、_Nonnull、__nonnull)、
(null_unspecified、_Null_unspecified 、__null_unspecified)、
null_resettable

1 原子性

atomic原子性(默认)。

编译器会自动生成互斥锁,对 setter 和 getter 方法进行加锁,可以保证属性的赋值和取值的原子性操作是线程安全的,但不包括操作和访问。
比如说 atomic 修饰的是一个数组的话,那么我们对数组进行赋值和取值是可以保证线程安全的。但是如果我们对数组进行操作,比如说给数组添加对象或者移除对象,是不在 atomic 的负责范围之内的,所以给被 atomic 修饰的数组添加对象或者移除对象是没办法保证线程安全的。

nonatomic  非原子性,一般属性都用 nonatomic 进行修饰,因为 atomic 非常耗时。

2 读写权限

readwrite可读可写(默认),同时生成 setter 方法和 getter 方法的声明和实现。

readonly只读,只生成 getter 方法的声明和实现。

setter可以指定生成的 setter 方法名,如 setter = setName。

getter可以指定生成的 getter 方法名,如 getter = getName。

3. 内存管理

属性关键字用法

assign

1. 既可以修饰基本数据类型,也可以修饰对象类型;
2. setter 方法的实现是直接赋值,一般用于基本数据类型 ;
3. 修饰基本数据类型,如 NSInteger、BOOL、int、float 等;
4. 修饰对象类型时,不增加其引用计数;
5. 会产生悬垂指针(悬垂指针:assign 修饰的对象在被释放之后,指针仍然指向原对象地址,该指针变为悬垂指针。这时候如果继续通过该指针访问原对象的话,就可能导致程序崩溃)。

weak

1. 只能修饰对象类型;
2. ARC 下才能使用;
3. 修饰弱引用,不增加对象引用计数,主要可以用于避免循环引用;
4. weak 修饰的对象在被释放之后,会自动将指针置为 nil,不会产生悬垂指针。

unsafe_unretained

1. 既可以修饰基本数据类型,也可以修饰对象类型;
2. MRC 下经常使用,ARC 下基本不用;
3. 修饰弱引用,同 weak;但性能更好,同时使用要求高,因为会产生悬垂指针。

retain

1. MRC 下使用,ARC 下基本使用 strong;
2. 修饰强引用,将指针原来指向的旧对象释放掉,然后指向新对象,同时将新对象的引用计数加1;
3. setter 方法的实现是 release 旧值,retain 新值,用于OC对象类型。

strong

1. ARC 下才能使用;
2. 原理同 retain;
3. 但是在修饰 block 时,strong 相当于 copy,而 retain 相当于 assign。

4. 浅拷贝

copy

setter 方法的实现是 release 旧值,copy 新值,用于 NSString、block 等类型。是深拷贝

4. 修饰变量关键字

这部分还没仔细了解,以后再来补全。

const

常量修饰符,表示不可变,可以用来修饰右边的基本变量和指针变量(放在谁的前面修饰谁(基本数据变量p,指针变量*p))。

常量(const)和宏定义(define)的区别:

使用宏和常量所占用的内存差别不大,宏定义的是常量,常量都放在常量区,只会生成一份内存 缺点:

编译时刻:宏是预编译(编译之前处理),const是编译阶段。导致使用宏定义过多的话,随着工程越来越大,编译速度会越来越慢

宏不做检查,不会报编译错误,只是替换,const会编译检查,会报编译错误。

优点:

宏能定义一些函数,方法。 const不能。

static

extern

Q&A:

copy和strong的区别:(深拷贝 浅拷贝)

由上面可以看出,strong修饰的对象,在引用一个对象的时候,内存地址都是一样的,只有指针地址不同,copy修饰的对象也是如此。为什么呢?不是说copy修饰的对象是生成一个新的内存地址嘛?这里为什么内存地址还是原来的呢?

因为,对不可变对象赋值,无论是strong还是copy,都是一样的,原内存地址不变,生成了新的指针地址。

那我们用可变对象对属性赋值。

strong修饰的属性内存地址依旧没有改变,但是copy修饰的属性内存子产生了变化。

对可变对象赋值 strong 是原地址不变,引用计数+1(浅拷贝)。 copy是生成一个新的地址和对象,生成一个新指针指向新的内存地址(深拷贝)

我们来修改OriginalStr的值:

看到 strong 修饰的属性,跟着进行了改变 当改变了原有值的时候,由于OriginalStr是可变类型,是在原有内存地址上进行修改,无论是指针地址和内存地址都没有b改变,只是当前内存地址所存放的数据进行改变。由于 strong 修饰的属性虽然指针地址不同,但是指针是指向原内存地址的,所以会跟着 OriginalMutableStr 的改变而改变。

不同于strong,copy修饰的类型不仅指针地址不同,而且指向的内存地址也和OriginalMutableStr 不一样,所以不会跟着 OriginalMutableStr 的改变而改变。

Q:以下代码会出现什么问题?

@property (copy) NSMutableArray *array;

答: 不论赋值过来的是NSMutableArray还是NSArray对象,进行copy操作后都是NSArray对象(深拷贝)。由于属性被声明为NSMutableArray类型,就不可避免的会有调用方去调用它的添加对象、移除对象等一些方法,此时由于copy的结果是NSArray不可变对象,对NSArray对象调用添加对象、移除对象等方法,就会产生程序异常。

Q:assign 和 weak 关键字的区别有哪些?

weak只能修饰对象,而assign既可以修饰对象也可以修饰基本数据类型;

assign修饰的对象在被释放后,指针仍然指向原对象地址;而weak修饰的对象在被释放之后会自动置指针为 nil;

 相同点:在修饰对象的时候,assignweak都不改变对象的引用计数。

Q:atomic 修饰的属性是怎么样保存线程安全的?

答: 编译器会自动生成互斥锁,对 setter 和 getter 方法进行加锁,可以保证属性的赋值和取值原子性操作是线程安全的,但不包括操作和访问。
比如说atomic修饰的是一个数组的话,那么我们对数组进行赋值和取值是可以保证线程安全的。但是如果我们对数组进行操作,比如说给数组添加对象或者移除对象,是不在atomic的负责范围之内的,所以给被atomic修饰的数组添加对象或者移除对象是没办法保证线程安全的。

Q:weak和assign的区别?

答:修饰的对象:weak修饰oc对象类型的数据,assign用来修饰是非指针变量。

引用计数:weak 和 assign 都不会增加引用计数。

释放:weak 修饰的对象释放后,指针地址自动设置为 nil,assign修饰的对象释放后指针地址依然在,成为野指针。

修饰delegate 在MRC使用assign,在ARC使用weak。

属性关键字的其他知识还有很多,笔者会在接下来的学习中不断丰富本篇博客。


网站公告

今日签到

点亮在社区的每一天
去签到