Copy解析
测试代码:
NSMutableDictionary *dic1 = [NSMutableDictionary new];
NSMutableDictionary *dic2 = [dic1 copy];
NSLog(@"dic1:%p ---- dic2:%p:", dic1, dic2);
[dic2 setObject:@"test"forKey:@"test"];//会报错No Selector,因为该dic2指向的是NSDictionary对象,没有setObject这个方法
NSDictionary *dic3 = [NSDictionary dictionaryWithObjectsAndKeys:@"dfd",@"dsfds",nil];
NSDictionary *dic4 = [dic3 copy];
NSMutableDictionary *dic5 = [dic3 mutableCopy];
NSLog(@"dic3:%p ---- dic4:%p: ---- dic5:%p", dic3, dic4, dic5);
输出结果:
dic1:0x618000244260 ---- dic2:0x60800000dbf0:
dic3:0x60000002ff60 ---- dic4:0x60000002ff60: ---- dic5:0x60800005d490
Copy结论:
- Copy 得到的对象是 immutable 类,比如NSMutableDictionary 对象 的Copy会返回一个NSDictionary对象。
- mutableCopy 得到的对象是 mutable 类,比如NSString执行mutableCopy 会返回NSMutableString,一定是深拷贝。
- mutable 对象执行 copy / mutableCopy ,都是深拷贝,因为存在数据同步的问题(比如mutable可以修改了数据),那么就需要新建一块内存复制内容。
- immutable 对象执行copy,属于浅拷贝; 执行 mutableCopy 则是深拷贝。(浅拷贝即 retain)
注意的深拷贝也仅仅是新建一个Mutable对象,而原对象如果保存有其他对 象(比如数组),那么里面的对象则是 retain 操作,Apple文档将这个称为集合的单层深拷贝。
定义:
- 浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指针复制,即retain操作。
- 深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有一层是深复制,即单层深拷贝。(比如Array只深复制Array的内存地址,里面数组元素浅拷贝)
- 完全复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都是对象复制。
集合的完全复制有两种方法:
- 调用对象本身写好的API(如initWithArray: copyItems: 和 initWithDictionary: copyItems:,最后的参数copyItems设置为YES)
执行个这种方法,集合里的每个对象都会收到 copyWithZone: 消息。这个方法要求集合里的所有对象都实现 NSCopying 协议(copy操作),如果对象没有实现 NSCopying 协议,而尝试用这种方法进行深复制,会在运行时出错。
- 将集合进行归档(archive),然后解档(unarchive),就可以实现完全深复制,mutable对象会进行mutableCopy,而immutable对象会新建一块内存复制内容。(同样需要所有对象都实现 NSCopying、NSMutableCopying 协议)
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
源码分析
我们看下源码,copy和mutableCopy是干什么的
// NSObject.mm
+ (id)copy {
return (id)self;
}
+ (id)copyWithZone:(struct _NSZone *)zone {
return (id)self;
}
- (id)copy {
return [(id)self copyWithZone:nil];
}
+ (id)mutableCopy {
return (id)self;
}
+ (id)mutableCopyWithZone:(struct _NSZone *)zone {
return (id)self;
}
- (id)mutableCopy {
return [(id)self mutableCopyWithZone:nil];
}
从上面的源码看,是否理解我们继承NSObject的类对象在没有实现NSCopying、NSMutableCopying协议的情况下,执行copy的时候会Crash的原因了么?
因为[(id)self copyWithZone:nil] 是需要我们自定义的类里面实现的
NSCopying、NSMutableCopying协议
@protocol NSCopying
- (id)copyWithZone:(nullableNSZone *)zone;
@end
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(nullableNSZone *)zone;
@end
写个copy的示例代码:
@interface MyObject : NSObject
- (id)copyWithZone:(NSZone*)zone
{
NSLog(@"zone:%p"); // 这里输出nil,因为zone已经被废弃了
MyObject obj = [[[self class] allocWithZone:zone] init];
obj.str = [self.str copy];
return obj;
}
这段源码的意思是新建一个对象,然后把需要拷贝的源对象的成员变量赋值过来。
那么我们看看OC源码是怎么实现Copy的
OC Foundation框架 功能类 已实现的深拷贝 Copy 源码了。(未验证)
// runtime.h
OBJC_EXPORT id object_copyFromZone(id anObject, size_t nBytes, void *z)
__OSX_DEPRECATED(10.0, 10.5, "use object_copy instead")
/**
* Returns a copy of a given object.
*
* @param obj An Objective-C object.
* @param size The size of the object \e obj.
*
* @return A copy of \e obj.
*/
OBJC_EXPORT id object_copy(id obj, size_t size)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0)
OBJC_ARC_UNAVAILABLE;
// objc-runtime-new.m
/***********************************************************************
* object_copy
* fixme
* Locking: none
**********************************************************************/
id object_copy(id oldObj, size_t extraBytes)
{
return _object_copyFromZone(oldObj, extraBytes, malloc_default_zone());
}
/***********************************************************************
* object_copyFromZone
* fixme
* Locking: none
**********************************************************************/
static id _object_copyFromZone(id oldObj, size_t extraBytes, void *zone)
{
if (!oldObj) return nil;
if (oldObj->isTaggedPointer()) return oldObj;
// fixme this doesn't handle C++ ivars correctly (#4619414)
Class cls = oldObj->ISA();
size_t size;
id obj = _class_createInstanceFromZone(cls, extraBytes, zone, false, &size);
if (!obj) return nil;
// Copy everything except the isa, which was already set above.
uint8_t *copyDst = (uint8_t *)obj + sizeof(Class);
uint8_t *copySrc = (uint8_t *)oldObj + sizeof(Class);
size_t copySize = size - sizeof(Class);
memmove(copyDst, copySrc, copySize); // 拷贝对象的内存数据
/* fixupCopiedIvars
* Fix up ARC strong and ARC-style weak variables
* after oldObject was memcpy'd to newObject.*/
fixupCopiedIvars(obj, oldObj); // 处理对象的ARC
return obj;
}
看得出源码也是新建了一个对象,但是这里是在新建对象之后使用内存拷贝的方法 memmove 把源对象的成员变量直接拷贝到新建对象。
之前我在写CopyWithZone的示例代码,就是仿造_object_copyFromZone这个方法内部实现写的。
NSZone
有人可能注意到 NSCopying、NSMutableCopying两个协议的方法参数 NSZone,这个是干什么的?
可以这么说,NSZone是OC旧版本遗留下来的,在OC2.0开始已经被废弃了。
既然已经废弃了,那为什么还留着呢?
额,有可能是为了兼容老版本,也有可能是嫌麻烦…
接下来还是用源码说明NSZone还有没有在使用
// objc-runtime-new.mm
+ (id)allocWithZone:(struct _NSZone *)zone {
return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
id obj;
#if __OBJC2__
// allocWithZone under __OBJC2__ ignores the zone parameter
(void)zone;
obj = class_createInstance(cls, 0);
#else
if (!zone) {
obj = class_createInstance(cls, 0);
}
else {
obj = class_createInstanceFromZone(cls, 0, zone);
}
#endif
if (slowpath(!obj)) obj = callBadAllocHandler(cls);
return obj;
}
从 id_objc_rootAllocWithZone方法看出,zone在__ OBJC2 __ (即objc2.0)开始已经不用啦。
继续看看class_createInstance源码里关于Zone的(有个细节:OC源码私有方法,方法名前面都带有“_”前缀,现在看很多第三方SDK也开始用这种代码风格)
// objc-runtime-new.mm
/* class_createInstance和class_createInstanceFromZone调用的都是同一个方法 */
id class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
id class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone)
{
return _class_createInstanceFromZone(cls, extraBytes, zone);
}
id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
assert(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) {
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
}
else {
if (zone) { // 使用Zone的地方
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (!obj) return nil;
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
继续!!!
// objc-object.h
inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
从上面的源码看出,Zone是用来初始化内存时使用的,最后初始化对象值的都是initIsa这个方法。
那还有malloc_zone_calloc这个方法了呢?
继续看一下源码吧
// objc-os.h
typedef void * malloc_zone_t;
static __inline malloc_zone_t malloc_default_zone(void) { return (malloc_zone_t)-1; }
static __inline void *malloc_zone_malloc(malloc_zone_t z, size_t size) { return malloc(size); }
static __inline void *malloc_zone_calloc(malloc_zone_t z, size_t size, size_t count) { return calloc(size, count); }
static __inline void *malloc_zone_realloc(malloc_zone_t z, void *p, size_t size) { return realloc(p, size); }
static __inline void malloc_zone_free(malloc_zone_t z, void *p) { free(p); }
static __inline malloc_zone_t malloc_zone_from_ptr(const void *p) { return (malloc_zone_t)-1; }
static __inline size_t malloc_size(const void *p) { return _msize((void*)p); /* fixme invalid pointer check? */ }
从源码可以看出,就算zone != nil,malloc_zone_calloc函数里面也没有使用z来分配内存
好的,从源码看,即使我们使用 allocWithZone 时传入了非空的NSZone,并且假设#if __ OBJC2 __不成立,那么我们看下接下来调用的流程:
allocWithZone
->
class_createInstanceFromZone
->
_class_createInstanceFromZone
->
malloc_zone_calloc
->
initIsa
// 注意
void * malloc_zone_calloc(malloc_zone_t z, size_t size, size_t count) {
return calloc(size, count);
}
最后分配内存的代码也没有再使用Zone了!
差不多啦,再看看C函数内存分配的方法说明吧
_alloc
原型:void *_alloc(size_t size);
本函数与上述的两个函数不同,因为它是在栈上分配了size大小的内存,因此使用此函数分配的内存不用再担心内存释放的情况了。但是使用此函数需要注意的是:在函数内部使用此函数分配的内存随着函数的终结不复存在,因此不能将此函数分配的内存供函数外部使用。
malloc
原型:void * malloc(size_t size);
该函数将在堆上分配一个size byte大小的内存。它分配的单原完全按字节大小计算,因此如此分配N个单原的student_t,那么要这样实现:(stdent_t *)malloc(N * sizeof (student_t));
calloc
原型:void* calloc(size_t size, int count);
该函数解决了上面的函数的不足,它将分配count个size大小的单原,因此在便用此函数的时候就会很方便,比如对上面的例子就可以:(student_t *)calloc(sizeof(t_student), N)就可以了。这样使用就会很清晰的知道分配的内存是一种什么样的逻辑方式。
malloc与calloc没有本质区别,malloc之后的未初始化内存可以使用memset进行初始化。
realloc是在malloc的基础上增加内存分配,free函数用来对分配在堆的内存进行释放以防内存泄漏的产生。
sbrk函数用来向os申请数据段,供malloc,calloc及realloc申请使用。
New和delete是c++中的运算符,调用malloc申请内存后初始化为对象,用delete释放。