【问题标题】:How can I make a deep copy in Objective-C?如何在 Objective-C 中进行深度复制?
【发布时间】:2013-12-03 01:48:46
【问题描述】:

我正在学习 ios 开发,但我对 Objective-C 中的深度复制感到困惑。 例如,我有以下三个班级。现在我想深拷贝ClassA,有谁能教我完成拷贝方法吗?

答:

@interface ClassA : NSObject <NSCopying>

@property (nonatomic, assign) int aInt;
@property (nonatomic, retain) ClassB *bClass;

@end

乙:

@interface ClassB : NSObject <NSCopying>

@property (nonatomic, assign) int bInt;
@property (nonatomic, retain) ClassC *cClass;

@end

C:

@interface ClassC : NSObject <NSCopying>

@property (nonatomic, assign) int cInt;
@property (nonatomic, copy) NSString *str;

@end

【问题讨论】:

  • 您还没有显示copy 方法的开始...

标签: objective-c deep-copy


【解决方案1】:

iOS 上的 Objective-C 不提供任何直接的语言或库结构来在浅拷贝和深拷贝之间切换。每个类都定义了“获取其副本”的含义:

@implementation ClassA

- (id) copyWithZone: (NSZone*) zone
{
    ClassA *copy = [super copyWithZone:zone];
    [copy setBClass:bClass]; // this would be a shallow copy
    [copy setBClass:[bClass copy]]; // this would be a deep copy
    return copy;
}

@end

当然,您必须在 ClassB 和 ClassC 中做同样的决定。如果我没记错的话,Objective-C 中副本的通常语义是返回浅副本。有关该主题的更多讨论,另请参阅this question about copying arrays。

【讨论】:

  • 确实没有语言说明符或用于复制或深度复制的内置方法,这是一种耻辱。从理论上讲,不会复制不可变对象,而只会增加引用计数,因为内容无法更改,因此您最好只复制引用并仅对可变对象进行深度复制。不幸的是,没有定义 NSMutableObject 并且 Mutable 对象的命名只是由自己的判断来完成。
  • @isgoed Objective-C 中的很多很多东西都不是由特定的语言特性强制执行的——我们现在期望的许多现代功能是由 Foundation 建立的标准和最佳实践,而不是编译器强制执行的规则。它是一种古老的语言,更倾向于在运行时由库/框架/应用程序代码(如 Smalltalk)处理事情,这使得它在某些事情上更加笨拙,但也使得在更严格的现代中几乎不可能完成的事情变得容易语言。它是一个美妙的语言,它是什么,而不是它不是什么。
【解决方案2】:

您应该在每个要复制的类中添加copyWithZone: 方法。

注意:这是我手写的,请注意拼写错误。

-(id) copyWithZone:(NSZone *) zone
{
    ClassA *object = [super copyWithZone:zone];
    object.aInt = self.aInt;
    object.bClass = [self.bClass copyWithZone:zone];
    return object;
}

-(id) copyWithZone:(NSZone *) zone
{
    ClassB *object = [super copyWithZone:zone];
    object.bInt = self.bInt;
    object.cClass = [self.cClass copyWithZone:zone];
    return object;
}

-(id) copyWithZone:(NSZone *) zone
{
    ClassC *object = [super copyWithZone:zone];
    object.cInt = self.cInt;
    object.str = [self.str copy];
    return object;
}

【讨论】:

  • 你不想在你的实现中调用super吗?
  • 我不确定 :P 但是一些研究告诉我,编辑是一种更安全的方式。见this answer
  • 这会有所帮助。但是如果超类有任何你目前缺少的属性。
  • 它们是不同的实例,所以那里没有递归。每个都只是建立类层次结构,确保复制实例中的所有实例变量。
  • 我承认。点得很好。 :)
【解决方案3】:

按照http://www.techotopia.com/index.php/Copying_Objects_in_Objective-C的解释

“这可以通过将对象及其组成元素写入存档然后读回新对象来实现。”

@implementation ClassA

- (id)copyWithZone:(NSZone*)zone{
    NSData *buffer;
    buffer = [NSKeyedArchiver archivedDataWithRootObject:self];
    ClassA *copy = [NSKeyedUnarchiver unarchiveObjectWithData: buffer];
    return copy;
}
@end

【讨论】:

  • 真正的深拷贝方法:)
  • ClassA中的所有自定义对象,都必须实现NSCoding协议吗?
  • 但是这不是将所有数据复制两次吗?如果它是一个大对象,会相当昂贵?
  • @GreenAsJade 这是个好问题 - 我不确定,但如果你发现了,请告诉我
  • 这是一个绝妙的解决方案,值得更多赞赏。
【解决方案4】:

这可能会有所帮助。该链接显示了如何使用NSKeyedArchiver进行深层复制

http://iphonecodecenter.wordpress.com/2013/08/26/difference-between-shallow-copy-and-deep-copy/

【讨论】:

    【解决方案5】:

    我有很长的属性列表的自定义类,所以我对它们进行了迭代:

    @interface MyClass : NSObject <NSCopying>
    
    #import <objc/runtime.h>
    
    -(id) copyWithZone: (NSZone *) zone {
    
        MyClass *myCopy = [[MyClass alloc] init];
    
        //deepCopy
        unsigned int numOfProperties;
        objc_property_t *properties = class_copyPropertyList([self class], &numOfProperties);
    
        for (int i = 0; i < numOfProperties; i++) {
    
           objc_property_t property = properties[i];
           NSString *propertyName = [[NSString alloc]initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
           [adressCopy setValue:[[self valueForKey:propertyName] copy] forKey:propertyName];
        }
        return myCopy;
    }
    

    所有 customClassProperties 也需要实现这一点。

    【讨论】:

    • 这将只复制属性(使用@property 定义的所有内容)而不是成员值({} 之间的所有内容)。这是对 initAsCopy 例程的有用修改(我不使用 copy(WithZone) 因为它是伪造的,请参阅我的答案)。如果它是一个类别,那就更好了。但由于这不是我对它投反对票的问题的答案(它不会制作完整的副本)。此外: - adressCopy 未声明。你的意思是myCopy。 - 你不释放属性 - 你应该在释放之前检查属性是否不为零。 - #import 不能放在那里。
    【解决方案6】:

    Objective-C 的 copy 和 copyWithZone 规范是虚假且危险的,不应使用。 --!-- 至少与 ARC(自动引用计数)一起使用时不会(2016-08-23)--!-- 该代码将导致写入超出内存/缓冲区溢出的界限。 相反,我提出了一种安全复制对象 initAsShallowCopy 和 deepCopy 的方法。

    在下面的代码中查看我的测试结果:

    #import <Foundation/Foundation.h>
    
    @interface ClassA : NSObject
    {
        @public
        NSMutableString*    A_Name;
        NSInteger           A_NSInteger;
        long int            A_int;
        float               A_float;
    }
        -(id)init;
        -(id)copyWithZone:(NSZone *) zone;      // DON'T USE copy OR copyWithZone, unless you ignore Apple's guidelines and always make shallow copies in line with the correct example code here for initAsShallowCopy (but you return a copy instead of being a copy)
        -(id)initAsShallowCopy:(ClassA *)original;  // Correct way to make a shallow copy
        -(void)deepCopy;                            // Correct way to make a deep copy (Call initAsShallowCopy first)
    @end
    
    @interface ClassB : ClassA
    {
        @public
        NSMutableString*    B_Name;
        NSInteger           B_NSInteger;
        long int            B_int;
        float               B_float;
    }
        -(id)init;
        -(id)copyWithZone:(NSZone *) zone;      // DON'T USE copy OR copyWithZone, unless you ignore Apple's guidelines and always make shallow copies in line with the correct example code here for initAsShallowCopy (but you return a copy instead of being a copy)
        -(id)initAsShallowCopy:(ClassB *)original;  // Correct way to make a shallow copy
        -(void)deepCopy;                            // Correct way to make a deep copy (Call initAsShallowCopy first)
        -(void)print;
    @end
    
    @interface ClassCWithoutCopy : NSObject
    {
        @public
        NSMutableString*    C_Name;
        NSInteger           C_NSInteger;
        long int            C_int;
        float               C_float;
    }
    -(id)init;
    -(void)print;
    
    @end
    
    @implementation ClassA
    
    
        -(id)init
        {
            if ( self = [super init] ) {    // initialize NSObject
                //A_Name        = [[NSMutableString alloc] init];
                //[A_Name setString:@"I am inited to A"];
                A_Name      = [NSMutableString stringWithString:@"I am inited to A"];
                A_NSInteger = 1;
                A_int       = 1;
                A_float     = 1.0;
    
                return self;
            }
            return nil;
        }
    
        /*
        FROM https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/#//apple_ref/occ/instm/NSObject/copy
    
        -- NSObject Class Reference --
    
    
        - (id)copy
    
        Discussion
        This is a convenience method for classes that adopt the NSCopying protocol. An exception is raised if there is
        no implementation for copyWithZone:.
    
        NSObject does not itself support the NSCopying protocol. Subclasses must support the protocol and
        implement the copyWithZone: method. A subclass version of the copyWithZone: method should send the message to super first,
        to incorporate its implementation, unless the subclass descends directly from NSObject.
    
    
        + copyWithZone:
    
        Discussion
        This method exists so class objects can be used in situations where you need an object that conforms to the NSCopying protocol.
        For example, this method lets you use a class object as a key to an NSDictionary object.
        You should not override this method.
    
        CONCLUSION
    
        copy says we should incorporate the implementation of copyWithZone, while copyWithZone says we should not override it.. So what is it?
        Looking at copyWithZone, we see that it is a class method (+), meaning it has not access to its instantiated members.
        So maybe they mean, we should not override the class method (+), but we should implement its instance method -copyWithZone:
        !!In any case we should not implement copy, because it is just made for convenience by Apple!!
    
        FROM: https://developer.apple.com/library/tvos/documentation/Cocoa/Reference/Foundation/Protocols/NSCopying_Protocol/index.html
    
        -- NSCopying --
    
        Your options for implementing this protocol are as follows:
    
        1) Implement NSCopying using alloc and init... in classes that don’t inherit copyWithZone:.
    
        2) Implement NSCopying by invoking the superclass’s copyWithZone: when NSCopying behavior is inherited.
        If the superclass implementation might use the NSCopyObject function, make explicit assignments to
        pointer instance variables for retained objects.
    
        3) Implement NSCopying by retaining the original instead of creating a new copy when the class and its contents are immutable.
    
        CONCLUSION:
    
        From 1) NSObject does not implement copyWithZone so any class that you make that should support copying should call [[Class alloc] init].
    
        From 2) Any subclass of a copyable object should call [super copyWithZone:zone], but NOT [[Class alloc] init] !!!!!!
        */
    
        -(id) copyWithZone:(NSZone *) zone
        {
            ClassA *CopiedObject = [[ClassA alloc] init];
    
            if(CopiedObject){
                CopiedObject->A_Name        = [A_Name copy];
                CopiedObject->A_NSInteger   = A_NSInteger;
                CopiedObject->A_int         = A_int;
                CopiedObject->A_float       = A_float;
                return CopiedObject;
            }
            return nil;
        }
    
        -(id)initAsShallowCopy:(ClassA *)original   // Correct way to make a shallow copy
        {
            /* Why this has to be done like this:
                It is very annoying to assign every variable explicitely.
                However this has to be done, in order for ARC (Automatic Reference Counting) (2016-08-23) to work.
                The compiler needs to be aware of any reference made to an object or reference cleared to an object in order to keep track of the
                reference counts.
                The danger is that when you add a variable to you class later on, you must not forget to update your initAsShallowCopy function and 
                possibly your DeepCopy function.
                It would be much nicer if you could just do:
                *self = *original;
                But that gives compiler error:
                /DeepCopyTest/main.m:135:9: Cannot assign to class object ('ClassA' invalid)
                So therefore there is also no raw memory copy between objects,
                so we are stuck with writing out each member variable explicitely.
            */
            if ( self = [super init] ) {    // initialize NSObject
                A_Name      = original->A_Name;
                A_NSInteger = original->A_NSInteger;
                A_int       = original->A_int;
                A_float     = original->A_float;
                return self;
            }
            return nil;
        }
    
        -(void)deepCopy;                            // Correct way to make a deep copy (Call initAsShallowCopy first)
        {
            /*  Luckily now, we only have to duplicate the objects that require a deep copy.
                So we don't have to write out all the floats, ints and NSIntegers, etcetera. Thus only the pointers (*) to objects.
                */
            A_Name  = [A_Name copy];
        }
    
    @end
    
    @implementation ClassB
    
    
        -(id)init
        {
            if ( self = [super init] ) {    // initialize ClassA
                B_Name      = [NSMutableString stringWithString:@"I am inited to B"];
                B_NSInteger = 2;
                B_int       = 2;
                B_float     = 2.0;
    
                return self;
            }
            return nil;
        }
    
        -(id) copyWithZone:(NSZone *) zone
        {
            //ClassA *CopiedObject = [[ClassA alloc] init]; We are not a direct descendant from NSObject, so don't call alloc-init
            // instead call the super copyWithZone
            ClassB *CopiedObject = [super copyWithZone:zone];   /* Using ARC (Automatic Reference Counting) 2016-08-23:
            THIS IS A MASSIVE BUFFER OVERFLOW/WRITING OUT OF BOUNDS RISK:
            Since super now allocates the object, it will now only allocate an object of size ClassA
            and effectively allocate too little memory for the ClassB. Unless memory allocation is upgraded to work with magic for
            Objective-C, DON'T USE copy or copyWithZone!!!!
            */
    
            if(CopiedObject){
                CopiedObject->B_Name        = [B_Name copy];
                CopiedObject->B_NSInteger   = B_NSInteger;
                CopiedObject->B_int         = B_int;
                CopiedObject->B_float       = B_float;
                return CopiedObject;
            }
            return nil;
        }
    
        -(id)initAsShallowCopy:(ClassB *)original   // Correct way to make a shallow copy
        {
            if ( self = [super initAsShallowCopy:original] ) {  // initialize ClassA
                B_Name      = original->B_Name;
                B_NSInteger = original->B_NSInteger;
                B_int       = original->B_int;
                B_float     = original->B_float;
                return self;
            }
            return nil;
        }
    
        -(void)deepCopy;                            // Correct way to make a deep copy (Call initAsShallowCopy first)
        {
            /*  Luckily now, we only have to duplicate the objects that require a deep copy.
                So we don't have to write out all the floats, ints and NSIntegers, etcetera. Thus only the pointers (*) to objects.
                */
            [super deepCopy];
            B_Name  = [B_Name copy];
        }
    
        -(void)print
        {
            NSLog(@"A_Name=\"%@\", A_NSInteger=%ld,A_int=%ld,A_float=%f",A_Name,A_NSInteger,A_int,A_float);
            NSLog(@"B_Name=\"%@\", B_NSInteger=%ld,B_int=%ld,B_float=%f",B_Name,B_NSInteger,B_int,B_float);
        }
    
    @end
    
    @implementation ClassCWithoutCopy
    
    
        -(id)init
        {
            if ( self = [super init] ) {    // initialize NSObject
                C_Name      = [NSMutableString stringWithString:@"I am inited to C"];
                C_NSInteger = 3;
                C_int       = 3;
                C_float     = 3.0;
    
                return self;
            }
            return nil;
        }
    
        -(void)print
        {
            NSLog(@"C_Name=\"%@\", C_NSInteger=%ld,C_int=%ld,C_float=%f",C_Name,C_NSInteger,C_int,C_float);
        }
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            ClassB      *OriginalB;
            ClassB      *CopiedB;
    
        #define USE_CORRECT_DEEP_COPY_AND_SHALLOW_COPY  1
        #define USE_CLASSC_WITHOUT_COPY_TEST    0
    
        #if(USE_CLASSC_WITHOUT_COPY_TEST)
    
            ClassCWithoutCopy   *OriginalC;
            ClassCWithoutCopy   *CopiedC;
    
            OriginalC   = [[ClassCWithoutCopy alloc] init];
            CopiedC     = [OriginalC copy]; /* Thread 1: signal SIGABRT: libc++abi.dylib: terminating with uncaught exception of type NSException
            *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ClassCWithoutCopy copyWithZone:]: unrecognized selector sent to instance 0x100100450' */
            //CopiedC       = [ClassCWithoutCopy copyWithZone:nil]; /* DeepCopyTest/main.m:283:33: 'copyWithZone:' is unavailable: not available in automatic reference counting mode
            */
    
            NSLog(@"OriginalC print:1");
            [OriginalC print];
            NSLog(@"CopiedC print:1");
            [CopiedC print];
    
            [OriginalC->C_Name appendString:@" and Appended as the original"];
            OriginalC->C_NSInteger = 30;
            OriginalC->C_int = 30;
            OriginalC->C_float = 30.0;
    
            NSLog(@"OriginalC print:2");
            [OriginalC print];
            NSLog(@"CopiedC print:2");
            [CopiedC print];
        #endif
    
        #if(USE_CORRECT_DEEP_COPY_AND_SHALLOW_COPY)
            OriginalB   = [[ClassB alloc] init];
            CopiedB     = [[ClassB alloc] initAsShallowCopy:OriginalB];
    
            NSLog(@"OriginalB print:1");
            [OriginalB print];
            NSLog(@"CopiedB print:1");
            [CopiedB print];
    
            [OriginalB->A_Name appendString:@" and Appended as the original"];
            OriginalB->A_NSInteger = 10;
            OriginalB->A_int = 10;
            OriginalB->A_float = 10.0;
            [OriginalB->B_Name appendString:@" and Appended as the original"];
            OriginalB->B_NSInteger = 20;
            OriginalB->B_int = 20;
            OriginalB->B_float = 20.0;
    
    
    
            NSLog(@"OriginalB print:2");
            [OriginalB print];
            NSLog(@"CopiedB print:2");
            [CopiedB print];
            // This works as expected: The values of OriginalB and CopiedB differ, but the shallow copied strings are the same.
    
            // Now make a deep copy of CopiedB
            [CopiedB deepCopy];
    
            [OriginalB->A_Name appendString:@" and Appended twice as the original"];
            OriginalB->A_NSInteger = 100;
            OriginalB->A_int = 100;
            OriginalB->A_float = 100.0;
            [OriginalB->B_Name appendString:@" and Appended twice as the original"];
            OriginalB->B_NSInteger = 200;
            OriginalB->B_int = 200;
            OriginalB->B_float = 200.0;
    
            NSLog(@"OriginalB print:3");
            [OriginalB print];
            NSLog(@"CopiedB print:3");
            [CopiedB print];
            // This works as expected: The values of OriginalB and CopiedB differ and als the deep copied strings are different.
    
        #else
            OriginalB   = [[ClassB alloc] init];
            CopiedB     = [OriginalB copy];             // Undefined behaviour. You will write unallocated memory
    
            NSLog(@"OriginalB print:1");
            [OriginalB print];
            NSLog(@"CopiedB print:1");
            /*[CopiedB print];  / * Thread 1: signal SIGABRT: libc++abi.dylib: terminating with uncaught exception of type NSException
            *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ClassA print]: unrecognized selector sent to instance 0x10010ad60' */
            NSLog(@"A_Name=\"%@\", A_NSInteger=%ld,A_int=%ld,A_float=%f",CopiedB->A_Name,CopiedB->A_NSInteger,CopiedB->A_int,CopiedB->A_float);
            NSLog(@"B_Name=\"%@\", B_NSInteger=%ld,B_int=%ld,B_float=%f",CopiedB->B_Name,CopiedB->B_NSInteger,CopiedB->B_int,CopiedB->B_float); // Undefined behaviour. You will read unallocated memory
    
    
            [OriginalB->A_Name appendString:@" and Appended as the original"];
            OriginalB->A_NSInteger = 10;
            OriginalB->A_int = 10;
            OriginalB->A_float = 10.0;
            [OriginalB->B_Name appendString:@" and Appended as the original"];
            OriginalB->B_NSInteger = 20;
            OriginalB->B_int = 20;
            OriginalB->B_float = 20.0;
            // This at least works: Changing Original, does not alter the values of Copy.
    
    
            NSLog(@"OriginalB print:2");
            [OriginalB print];
            NSLog(@"CopiedB print:2");
            NSLog(@"A_Name=\"%@\", A_NSInteger=%ld,A_int=%ld,A_float=%f",CopiedB->A_Name,CopiedB->A_NSInteger,CopiedB->A_int,CopiedB->A_float);
            //NSLog(@"B_Name=\"%@\", B_NSInteger=%ld,B_int=%ld,B_float=%f",CopiedB->B_Name,CopiedB->B_NSInteger,CopiedB->B_int,CopiedB->B_float);   // Undefined behaviour. You will read unallocated memory
    
            /*[CopiedB->A_Name appendString:@" and Appended as the copy"];  / * Thread 1: signal SIGABRT: libc++abi.dylib: terminating with uncaught exception of type NSException
            *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendString:' */
            CopiedB->A_NSInteger = 100;
            CopiedB->A_int = 100;
            CopiedB->A_float = 100.0;
            /*[CopiedB->B_Name appendString:@" and Appended as the copy"];  / * Thread 1: signal SIGABRT: libc++abi.dylib: terminating with uncaught exception of type NSException
             *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendString:'*/
            CopiedB->B_NSInteger = 200;                                     // Undefined behaviour. You will write unallocated memory
            CopiedB->B_int = 200;                                           // Undefined behaviour. You will write unallocated memory
            CopiedB->B_float = 200.0;                                       // Undefined behaviour. You will write unallocated memory
    
            /* Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
            DeepCopyTest(2376,0x7fff7edda310) malloc: *** error for object 0x10010ad98: incorrect checksum for freed object - object was probably modified after being freed.
    *** set a breakpoint in malloc_error_break to debug */
    
            NSLog(@"OriginalB print after modification of CopiedB:");
            [OriginalB print];
            NSLog(@"CopiedB print after modification of CopiedB:");
            /*[CopiedB print];; / * Thread 1: signal SIGABRT: libc++abi.dylib: terminating with uncaught exception of type NSException
            *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ClassA print]: unrecognized selector sent to instance 0x10010ad60' */
    
        #endif
        }
        return 0;
    }
    

    PS-1:来自:

    https://developer.apple.com/library/mac/documentation/General/Conceptual/DevPedia-CocoaCore/ObjectCopying.html

    -- 对象复制--

    深拷贝复制引用的对象,而浅拷贝只复制对这些对象的引用。因此,如果对象 A 被浅复制到对象 B,则对象 B 指的是对象 A 所指的同一个实例变量(或属性)。深拷贝对象优于浅拷贝,尤其是值对象。

    注意:

    这个表述不清楚,尤其是附带的插图,这表明解释是错误的。

    这个公式使得对同一个对象的两个引用似乎算作浅拷贝。这不是真的。这根本不是副本。

    明确的表述是: - 对象的浅拷贝具有从其父对象复制的所有值和引用,但它本身是内存中的唯一对象。 - 对象的深层副本具有从其父对象复制的所有值,并且它本身是内存中的唯一对象,但所有引用现在都引用了 -deep 自身 - 原始引用对象的副本。

    虽然深度复制的确切实现可能不会 100% 提供深度复制。 指向外部引用的对象(例如硬件项或图形驱动程序不能复制,而只会增加引用计数) 一些深拷贝没有功能意义。一个对象可能会引用它所在的窗口,但复制该窗口是没有意义的。 一个对象也可能引用被认为是不可变的数据,因此复制它是没有效率的。

    PS-2:在我尝试手动格式化所有代码之前,您可以给我 ctrl-K 的提示。

    PS-3:Apple-Z(撤消)撤消了我的所有格式而不是最后一个格式,我无法重做。

    【讨论】:

    • 甚至改进 deepcopy:-(void)deepCopy; // 正确的深拷贝方法(先调用 initAsShallowCopy) { // A_Name = [A_Name copy]; // 这甚至没有必要,因为 NSString 是不可变的,所以复制对象并没有额外的目的 }
    猜你喜欢
    • 2018-10-08
    • 2014-09-05
    • 1970-01-01
    • 2018-07-30
    • 2011-09-25
    • 2017-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多