Copy的那点事儿~

Copy的简单使用

copy 的效果

对源对象进行拷贝,建立出新的副本,彼此修改互不干扰!

###OC中copy的方法

copy–>建立对象的副本

  • 如果对象有可变/不可变版本的区别,copy方法,只能拷贝出不可变的版本
  • 如果对象没有可变/不可变的区别,copy方法就是建立一个副本

mutableCopy
* 建立对象的可变副本(如果对象有”可变/不可变”版本的区别,才需要使用此方法)

###副本的特点
彼此的内容一样,具有相同的方法

可变版本对象的copy

/** 可变版本对象的copy
    对于可变对象的copy/mutableCopy都是深拷贝
 */
- (void)copyDemo1 {
    // arrayM本身为可变对象
    NSMutableArray *arrayM = [NSMutableArray array];
    NSLog(@"源对象\t\t%@\t内存地址%p", arrayM.class, arrayM);

    // copy ---> 不可变 地址变化 新的对象
    // 无论是可变对象,还是不可以变对象,copy之后都会编程 不可变
    id a = [arrayM copy];
    NSLog(@"copy后\t%@\t内存地址%p", [a class], a);

    // mutableCopy  => 可变 地址变化 新的对象,mutableCopy可以保持可变的特性
    id aM = [arrayM mutableCopy];
    NSLog(@"mutableCopy后%@\t内存地址%p", [aM class], aM);
}

运行结果:

源对象     __NSArrayM  内存地址0x7f91c2c1d940
copy__NSArrayI  内存地址0x7f91c2e09a00
mutableCopy后__NSArrayM  内存地址0x7f91c2d23660

不可变版本对象的copy

/** 不可变版本对象的copy */
- (void)copyDemo2 {
    NSArray *array = [NSArray array];
    NSLog(@"源对象%@ 内存地址%p", array.class, array);

    // copy ---> 不可变 地址没有变化 引用计数+1
    // 对于不可变对象的copy操作,进行的浅拷贝,系统并不会为之分配内存空间,仅仅是retainCount+1
    id a = [array copy];
    NSLog(@"copy后%@ 内存地址%p", [a class], a);

    // mutableCopy ---> 可变 地址变化 新的对象
    // 不可变的mutaleCopy的操作会变为可变对象
    id aM = [array mutableCopy];
    NSLog(@"mutableCopy%@ 内存地址%p", [aM class], aM);
}

执行结果:

 源对象__NSArrayI 内存地址0x7fb4e07010d0
 copy__NSArrayI 内存地址0x7fb4e07010d0
 mutableCopy__NSArrayM 内存地址0x7fb4e063daf0

小结:

  • 不要随随变变给可变对象做 copy 操作

  • 都会建立新的副本,深拷贝(只要有一个可以修改,就是深拷贝)

    可变  ----> 可变
    可变  ----> 不可变
    不可变 ----> 可变
  • 不会建立新的副本,只是引用计数+1,浅拷贝,指针拷贝(两个对象前后都不需要修改)

    不可变 =》 不可变

Copy属性

在面向对象程序开发中,有一个非常重要的原则

####开闭原则

-开:对内开放,向怎么改,就怎么改
-闭:对外封闭,只能用,不能改

  • 定义成 copy 属性,在设置数值的时候,会默认做一次 copy 操作

    • 如果设置的数值是可变的,做一次copy,会新建副本
    • 如果设置的数值是不可变的,做一次copy,只是引用计数+1,不会建立新的副本!跟strong类型一致的!

建议:如果属性是 NSString,建议使用 copy 属性

注意:可变字符串一定不要使用 copy 属性

// 头衔,如果区分可变和不可变版本,做一次copy操作得到的就是不可变的字符串!
@property (nonatomic, copy) NSMutableString *title;

对象的类型

  • 1> 一个对象的准确类型,是在给该对象”分配内存空间”的时候指定的类型
  • 2> 对象的”类型”,是程序员指定该对象的类型,指定类型之后,就可以具有该对象的方法!
  • 3> 能否使用对象的方法,取决于运行时,这个对象的类型是否真的正确!
  • 4> 如果类型不正确会出现 -[NSObject length]: unrecognized selector
NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"];

    Person *p = [[Person alloc] init];
    p.name = strM;

    NSLog(@"%@", p.name);  // zhangsan

    [strM setString:@"lisi"];
    NSLog(@"===> %@", p.name);  // zhangsan

    // 问题:p.name的类型 NSString & NSMutableString
    // 答案:NSCFString --->  NSString
    // NSMutableString类型的数据做了一次copy后,会变为不可变的NSString类型
    id obj = p.name;
    NSLog(@"%@", [p.name class]);  // NSCFString
//    [obj setString:@"wangwu"];  // 报错,不应该对NSString进行修改
    NSLog(@"===> %@", p.name);  // wangwu
  NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"];
    NSString *str = @"haha";
    NSLog(@"%p", str);  // 0x10a182138

    Person *p = [[Person alloc] init];
//        p.name = strM;
    p.name = str;
    NSLog(@"%p", p.name); //0x10a182138  跟 str地址是一样滴~,p.name指向了str的空间

    NSLog(@"%@", p.name); // haha

    [strM setString:@"lisi"];
    NSLog(@"===> %@ %@", p.name, strM);  // p.name 和 strM不可能一样,strM的改变不会影响到p.name
  // 从网络获取到一个字符串
    NSMutableString *strM = [NSMutableString stringWithString:@"BOSS"];

    Person *p = [[Person alloc] init];
    p.title = strM;

    [strM setString:@"经理"];
    NSLog(@"===> %@ %@", p.title, strM);

    // Attempt to mutate immutable object with setString:
    // 试图使用 setString: 方法修改"不可变对象"?
    // setString方法,是title存在之后,修改title的内容!
    [p.title setString:@"jingli"];  // 程序会崩掉,p.title为不可变对象
    NSLog(@"!!!!> %@ %@", p.title, strM);

Copy的自定义对象

  • 在很多商业级应用程序或者第三方框架,在开发时的模型通常会支持 copy

    NSCache & NSMutableDictionary

    -NSCache 的 key strong 的
    -Dict 的 key 是 copy 的

    // NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
    // dictM setObject:<#(id)#> forKey:<#(id<NSCopying>)#>

  • 如果自定义对象要当作字典的 key,需要支持 copy!

Person.h

/**
 要让自定义对象支持 copy,需要做两件事情

 1. 遵守 NSCopying 协议
 2. 实现 copyWithZone: 方法
 */
@interface Person : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
Person.m
/**
 有返回值 -》 copy 出来的新对象
 是一个对象方法 -> 将 self 建立一个副本

 zone: 空间,分配对象是需要内存空间的,如果指定了zone,就可以指定新建对象对应的内存空间
 但是:zone是一个非常古老的技术,为了避免在堆中出现内存碎片而使用的

 在今天的开发中,zone几乎可以忽略

 如果对象没有 可变/不可变 的版本区别,只要实现 copyWithZone 方法即可
 */
- (id)copyWithZone:(NSZone *)zone {
    // copy 是要建立一个新的副本,和当前的对象具有相同的内容
    // 1. 实例化 person 对象
    Person *p = [[Person alloc] init];

    p.name = self.name;
    p.age = self.age;

    return p;
}

// 只需要写模型的description就可以了,返回对象的描述信息,便于调试使用,类似于 java 中的 toString()
- (NSString *)description {
    // JSON的格式和字典非常像
//    @{@"name": @"zhangsan", @"age": @(19)}
    return [NSString stringWithFormat:@"<%@: %p> {name: %@, age: %d}", self.class, self, self.name,self.age];
}

// 也可以输出调试信息的字符串,专门用来调试使用的
// 有的网站上的培训资料会提到这个方法,跟 description 方法非常类似!
// 但是:如果在应用程序中,使用了这个方法,应用程序无法上架!苹果会认为使用了私有API
//- (NSString *)debugDescription {
//
//}
- (void)viewDidLoad {
    [super viewDidLoad];

    Person *p = [[Person alloc] init];
    p.name = @"zhangsan";
    p.age = 19;
    NSLog(@"%@--- %p", p.name, p); // zhangsan--- 0x7fbacbf17120

    Person *p1 = [p copy];
    p1.name = @"xiaofang";

    // 注意,地址不同,说明实现了自定义对象的copy
    NSLog(@"%@--- %p", p1.name, p1); // xiaofang--- 0x7fbacbe13900


}

子类对象的Copy

#import "Person.h"

@interface Student : Person
// 学号
@property (nonatomic, copy) NSString *no;
@end

```objc





<div class="se-preview-section-delimiter"></div>

#import "Student.h"

@implementation Student

- (id)copyWithZone:(NSZone *)zone {
    // 执行父类的 copy 方法,会把父类中的属性完全 copy
    Student *s = [super copyWithZone:zone];

    // 在子类的copy方法中,只需要给子类特有的属性进行赋值即可!
    s.no = self.no;

    return s;
}

@end




<div class="se-preview-section-delimiter"></div>

在父类的copyWithZone方法中:
要写:

    Person *p = [[self.class alloc] init];

    p.name = self.name;
    p.age = self.age;




<div class="se-preview-section-delimiter"></div>

不能写 Person * p = [[Person alloc] init];这么干了,只能copy出Person对象,不对子类起作用。

测试Demo
// 一个对象的准确类型,取决分配内存空间指定的类型
    Person *p = [[Student alloc] init];
    p.name = @"zhangsan";
    p.age = 19;
    // 给对象指定的类型,决定了能够使用对象的哪些属性和方法
//    p.no = @"001";
//    NSLog(@"%@ %@", p, p.no);
    NSLog(@"%@", p); // {name: zhangsan, age: 19}

    // copy会执行父类的copy方法
    Student *p1 = [p copy];  // 虽然是父类的引用,但实际上copy的是子类
    p1.name = @"xiaofang";

    NSLog(@"%@ %@", p1, p1.no); // <Student: 0x7fb0f9443150> {name: xiaofang, age: 19} (null)
### 关于 `SettingWithCopyWarning` 的警告解析及解决方案 --- #### **一、背景知识** 当你在使用 Pandas 数据框 (`DataFrame`) 进行操作时,如果尝试对一个切片副本设置值,可能会触发 `SettingWithCopyWarning` 警告。这是因为 Pandas 无法明确你是想修改原数据框还是仅对其某个视图(slice/view)进行更改。 例如,在下面的代码片段中: ```python df[df['column'] == some_value]['new_column'] = new_value ``` Pandas 并不清楚这里的 `df[df['column'] == some_value]` 是返回了一个独立的副本copy),还是指向原始数据的一个引用(view)。因此它会发出这个警告提示潜在风险。 --- #### **二、为什么会出现这种问题?** 根本原因是 Pandas 对象的行为存在一定的不确定性——某些情况下返回的是 view(共享母体数组存储空间的一块区域),而另一些时候又返回 copy(完全复制出来的新对象)。具体表现为以下几个方面: 1. **链式索引**: 如果你连续进行了多次索引访问,那么中间某一步就有可能创建出了新的拷贝。 ```python df.iloc[0:3].iloc[:,2:] = 999 # 链式索引容易引发 SettingWithCopyWarning ``` 2. **布尔掩码过滤**: 应用布尔表达式的筛选同样可能导致类似的问题,尤其是进一步对该结果再做赋值的时候。 ```python filtered_df = df[df['age']>30] filtered_df['income'] *= 1.1 # 此处也可能抛出 warning ``` 为了提高程序稳定性并避免意外行为的发生,推荐尽量显式指定到底是在操弄哪部分的数据结构。 --- #### **三、解决办法** 下面是几种消除该警告的有效策略: ##### **1. 明确告诉 Pandas 我们希望的操作形式** 可以使用 `.loc[]`,`.iloc[]` 或者其他精确定位工具代替普通的方括号语法,从而减少歧义性。 假设我们有一个简单的例子想要修正员工收入列的所有数值都增加百分之十: ```python # 错误做法 - 可能导致 SettingWithCopyWarning filtered_df = df[df['gender']=='male'] filtered_df['salary'] += 10% # 改进版本 - 直接作用到源表上 df.loc[df['gender']=='male', 'salary'] *= 1.1 ``` 注意上面改写后的语句不会引起任何警告信息了! 另外,如果你确实需要先获取子集然后再单独处理的话,则应该主动制造一份真正的深拷贝(copy): ```python sub_df = df[df['city']=="Beijing"].copy() sub_df['population_density'] /= area_in_km_squared[sub_df.index] ``` 这里的关键就是添加上了那个小小的 `.copy()` 方法调用。 ##### **2. 把所有改动集中起来完成后再统一提交回去** 有时我们可以考虑先把所有的变更累积保存在一个临时变量里边,等到全部准备完毕之后再一次性送回目标字段之中去更新。 比如说统计学作业当中经常要用到标准化转换的过程: ```python scaler = StandardScaler() scaled_values = scaler.fit_transform(df[['height','weight']]) df[['norm_height','norm_weight']] = scaled_values ``` 这样就不必担心因为反复局部调整而导致复杂的内部状态管理难题啦~ ##### **3. 忽略掉无害的小警报(不建议长期如此)** 当然了,实在觉得那些小提醒干扰太大的话,也可以选择关闭它们;不过请注意这种方式并不能真正解决问题哦~ ```python pd.options.mode.chained_assignment=None # 设定为 None 即可禁用 warnings ... # 恢复默认配置 pd.reset_option('mode.chained_assignment') ``` --- #### **四、总结** 通过对上述内容的学习了解到,“SettingWithCopyWarning”虽然看起来烦人但实际上是个非常有用的功能特性,帮助开发者及时发现潜在隐患所在之处。只要按照规范的方式编写干净利落且意图清晰的 pandas 代码即可轻松规避此类麻烦事儿发生。同时也要记住每当我们面临复杂业务逻辑场景下总是优先选用更高效可靠的批量运算模式而非逐条记录逐一处理的传统思维习惯哟~ ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值