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

目录

对象复制

一、copy与mutableCopy方法

二、NSCopying和NSmutableCopying协议

三、深复制与浅复制

按照类型说明:

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

不可变字符串

可变类型字符串

容器类对象的深浅拷贝

自定义类型的拷贝

容器类对象的深拷贝

归档与解档代码实现

属性关键字

[1 原子性](#1 原子性)

[2 读写权限](#2 读写权限)

[3. 内存管理](#3. 内存管理)

assign

weak

unsafe_unretained

retain

strong

copy

[4. 修饰变量关键字](#4. 修饰变量关键字)

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

Q&A:

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

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

[Q:assign 和 weak 关键字的区别有哪些?](#Q:assign 和 weak 关键字的区别有哪些?)

[Q:atomic 修饰的属性是怎么样保存线程安全的?](#Q:atomic 修饰的属性是怎么样保存线程安全的?)

Q:weak和assign的区别?

对象复制

一、copy与mutableCopy方法

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

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

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

objectivec 复制代码
#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:

objectivec 复制代码
#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
objectivec 复制代码
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;
}

三、深复制与浅复制

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

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

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

举个例子:

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

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

按照类型说明:

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

不可变字符串
objectivec 复制代码
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 方式创建的是一个常量区字符串。

可变类型字符串
objectivec 复制代码
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 中的 不可变数组类 ,它在创建之后 元素数量和顺序都不能更改

objectivec 复制代码
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 中的可变数组类,可以动态添加、删除、替换数组中的元素。

objectivec 复制代码
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 是容器级别的拷贝,里面的元素对象不会被复制,而是指针指向原对象。

自定义类型的拷贝

objectivec 复制代码
#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
objectivec 复制代码
#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
objectivec 复制代码
#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操作,然后将拷贝后的元素添加到新数组中。
objectivec 复制代码
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;
}

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

归档与解档代码实现

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

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

objectivec 复制代码
#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。

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

相关推荐
Someone_sky11 小时前
Loopback for Mac:一键打造虚拟音频矩阵,实现跨应用音频自由流转
macos·音视频
Pocker_Spades_A12 小时前
Python快速入门专业版(一):Windows/macOS/Linux 系统环境搭建(附常见报错解决)
windows·python·macos
在下历飞雨14 小时前
AI+Kuikly自研DSL初体验:让UI开发更简洁优雅
ios·vibecoding
zzywxc78715 小时前
深入探讨AI三大领域的核心技术、实践方法以及未来发展趋势,结合具体代码示例、流程图和Prompt工程实践,全面展示AI编程的强大能力。
人工智能·spring·机器学习·ios·prompt·流程图·ai编程
CocoaKier15 小时前
推荐一个网站,一句话生成网站应用和小程序
前端·ios·ai编程
他们都不看好你,偏偏你最不争气16 小时前
【iOS】 懒加载
ios
FOWng_lp1 天前
66Mac电脑Tomcat+Java项目中 代码更新但8080端口内容没有更新
java·开发语言·macos·tomcat
i建模1 天前
移动开发如何给不同手机屏幕做适配
macos·智能手机·cocoa
MoSheng菜鸟1 天前
React Native开发
android·react.js·ios