深浅拷贝问题想必每一个iOS开发都接触过,也是面试过程中最常考的点。网上帖子很多,说法却都不一,很容易把人绕晕,还列了各种表格,我认为记那些结论意义不大,要想彻底搞明白深浅拷贝,不犯晕,核心要记住五个概念:
深拷贝、浅拷贝、copy方法、mutableCopy方法、copy修饰符
-
深拷贝:内存中生成一个新的对象,新旧指针指向两块不同的内存地址。拷贝的是对象本身。
-
浅拷贝:新旧指针指向一块相同的内存地址。拷贝的是指针。
-
copy和mutableCopy:这是NSCopying和NSMutableCopying协议中的两个方法,系统的一些容器类已经实现。自定义类如果想实现copy或者mutableCopy功能,也需要遵守这两个协议,对应实现copyWithZone、mutableCopyWithZone方法。需要注意的是,这两个方法和深浅拷贝没有必然联系,深浅拷贝是一种泛指的概念,任何语言都可以实现对象的深浅拷贝方法。iOS中的copy和mutableCopy是两个返回类型不同的拷贝方法,copy方法返回一个不可变对象,mutableCopy方法返回一个可变对象,这是他们之间的主要区别。至于其实现到底是深拷贝还是浅拷贝,需要根据不同的类独立实现。
-
copy修饰符:用copy修饰的对象,在调用setter方法时会调用传入的对象的copy方法复制一份。从语意上讲是复制一份数据,旧数据修改之后,不会影响新数据。但语意和实现是分离的,具体实现上,是不是真的复制一份,是不固定的,是由各个类独立实现。
根据上述内容,回答以下面试常问的问题:
1.用copy修饰符修饰以后,一定会拷贝一份新的内存地址吗?原对象的引用计数一定不会变吗?
不一定。用copy修饰的属性,在setter时,会先调用传入对象的copy方法得到一份拷贝后再retain,但不同的对象调用copy方法,可能是深拷贝也可能是浅拷贝,如果是浅拷贝,则两个指针指向一个内存,旧对象引用计数会加1。如果是深拷贝,则retain只会是新的对象引用计数加1,不影响旧对象。
2.不可变对象用strong修饰会有什么问题?
如果赋值的对象是不可变对象,那和copy一个效果,都是两个指针指向一个内存,引用计数加1。如果赋值的对象是可变对象,两个指针指向同一个内存则会出现问题。指针类型和实际对象类型不匹配,明明是不可变对象,但是内容却可能会被篡改。这样不符合语义,数据可能出现污染。
3.可变对象用copy修饰会有什么问题?
copy方法会返回一个不可变对象,指针类型和实际对象类型不匹配,调用可变对象的一些方法时则会报错。
总结:2、3两个问题根本原因是因为iOS的消息机制,方法的绑定不在编译期而是运行时确定。为了避免可变对象找不到方法crash,或者不可变对象被篡改,iOS中的不可变对象、容器一般都使用copy修饰符,保证setter方法中对传入的新对象调用copy方法。iOS中的可变对象、容器一般使用strong修饰符,避免setter方法中对传入的对象调用copy方法。
网友评论