iOS Copy解析以及源码分析


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结论:

  1. Copy 得到的对象是 immutable 类,比如NSMutableDictionary 对象 的Copy会返回一个NSDictionary对象。
  2. mutableCopy 得到的对象是 mutable 类,比如NSString执行mutableCopy 会返回NSMutableString,一定是深拷贝。
  3. mutable 对象执行 copy / mutableCopy ,都是深拷贝,因为存在数据同步的问题(比如mutable可以修改了数据),那么就需要新建一块内存复制内容。
  4. immutable 对象执行copy,属于浅拷贝; 执行 mutableCopy 则是深拷贝。(浅拷贝即 retain)

注意的深拷贝也仅仅是新建一个Mutable对象,而原对象如果保存有其他对 象(比如数组),那么里面的对象则是 retain 操作,Apple文档将这个称为集合的单层深拷贝。

定义:

  • 浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指针复制,即retain操作。
  • 深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有一层是深复制,即单层深拷贝。(比如Array只深复制Array的内存地址,里面数组元素浅拷贝)
  • 完全复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都是对象复制。

集合的完全复制有两种方法:

  1. 调用对象本身写好的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释放。

你可能感兴趣的:(iOS Copy解析以及源码分析)