Cách sao chép một đối tượng trong Objective-C


112

Tôi cần sao chép sâu một đối tượng tùy chỉnh có các đối tượng của riêng nó. Tôi đã đọc xung quanh và hơi bối rối về cách kế thừa NSCopying và cách sử dụng NSCopyObject.


1
Vĩ đại TUTORIAL cho bản sao sự hiểu biết, mutableCopy và copyWithZone
horkavlna

Câu trả lời:


192

Như mọi khi với các loại tham chiếu, có hai khái niệm về "bản sao". Tôi chắc rằng bạn biết chúng, nhưng để hoàn thiện.

  1. Một bản sao bitwise. Trong điều này, chúng tôi chỉ sao chép bit bộ nhớ - đây là những gì NSCopyObject thực hiện. Gần như luôn luôn, đó không phải là điều bạn muốn. Các đối tượng có trạng thái bên trong, các đối tượng khác, v.v. và thường đưa ra giả định rằng chúng là đối tượng duy nhất giữ tham chiếu đến dữ liệu đó. Bản sao bitwise phá vỡ giả định này.
  2. Một bản sao sâu sắc, hợp lý. Trong điều này, chúng tôi tạo một bản sao của đối tượng, nhưng không thực sự làm điều đó từng chút một - chúng tôi muốn một đối tượng hoạt động giống nhau cho tất cả các ý định và mục đích, nhưng không (nhất thiết) phải là bản sao giống bộ nhớ của bản gốc - Sổ tay hướng dẫn Objective C gọi một đối tượng như vậy là "độc lập về mặt chức năng" với nó ban đầu. Vì cơ chế tạo các bản sao "thông minh" này khác nhau giữa các lớp, chúng tôi yêu cầu các đối tượng tự thực hiện chúng. Đây là giao thức NSCopying.

Bạn muốn cái sau. Nếu đây là một trong những đối tượng của riêng bạn, bạn chỉ cần sử dụng giao thức NSCopying và thực hiện - (id) copyWithZone: (NSZone *) zone. Bạn tự do làm bất cứ điều gì bạn muốn; mặc dù ý tưởng là bạn tạo một bản sao thật của chính mình và gửi lại. Bạn gọi copyWithZone trên tất cả các trường của mình, để tạo một bản sao sâu. Một ví dụ đơn giản là

@interface YourClass : NSObject <NSCopying> 
{
   SomeOtherObject *obj;
}

// In the implementation
-(id)copyWithZone:(NSZone *)zone
{
  // We'll ignore the zone for now
  YourClass *another = [[YourClass alloc] init];
  another.obj = [obj copyWithZone: zone];

  return another;
}

Nhưng bạn đang khiến người nhận đối tượng được sao chép có trách nhiệm giải phóng nó! Bạn không nên autoreleasenó, hay tôi đang thiếu một cái gì đó ở đây?
bobobobo

30
@bobobobo: Không, quy tắc cơ bản của quản lý bộ nhớ Objective-C là: Bạn có quyền sở hữu một đối tượng nếu bạn tạo nó bằng phương pháp có tên bắt đầu bằng “phân bổ” hoặc “mới” hoặc chứa “bản sao”. copyWithZone:đáp ứng tiêu chí này, do đó nó phải trả về một đối tượng có số lượng giữ lại là +1.
Steve Madsen

1
@Adam Có lý do gì để sử dụng allocthay vì allocWithZone:kể từ khi khu vực được chuyển vào không?
Richard

3
Chà, các vùng được sử dụng một cách hiệu quả trong thời gian chạy dựa trên OS X hiện đại (tức là tôi nghĩ rằng chúng thực sự không bao giờ được sử dụng). Nhưng có, bạn có thể gọi allocWithZone.
Adam Wright


25

Tài liệu của Apple cho biết

Phiên bản lớp con của phương thức copyWithZone: nên gửi thông báo tới super trước, để kết hợp việc triển khai nó, trừ khi lớp con xuống trực tiếp từ NSObject.

để thêm vào câu trả lời hiện có

@interface YourClass : NSObject <NSCopying> 
{
   SomeOtherObject *obj;
}

// In the implementation
-(id)copyWithZone:(NSZone *)zone
{
  YourClass *another = [super copyWithZone:zone];
  another.obj = [obj copyWithZone: zone];

  return another;
}

2
Kể từ YourClass xuống trực tiếp từ NSObject Tôi không nghĩ rằng đó là cần thiết ở đây
Mike

2
Điểm tốt, nhưng nó là một quy tắc chung, trong trường hợp nó là một hệ thống phân cấp lớp dài.
Saqib Saud

8
Tôi đã nhận ra lỗi: No visible @interface for 'NSObject' declares the selector 'copyWithZone:'. Tôi đoán đây chỉ được yêu cầu khi chúng ta đang kế thừa từ một số lớp tùy chỉnh khác mà cụcopyWithZone
Sam

1
another.obj = [[obj copyWithZone: zone] autorelease]; cho tất cả các lớp con của NSObject. Và đối với các kiểu dữ liệu nguyên thủy, bạn chỉ cần gán chúng -> another.someBOOL = self.someBOOL;
hariszaman

@Sam "Bản thân NSObject không hỗ trợ giao thức NSCopying. Các lớp con phải hỗ trợ giao thức và triển khai phương thức copyWithZone:. Một phiên bản lớp con của phương thức copyWithZone: phải gửi thông báo tới super trước, để kết hợp việc triển khai nó, trừ khi lớp con giảm xuống trực tiếp từ NSObject. " developer.apple.com/documentation/objectivec/nsobject/…
s4mt6

21

Tôi không biết sự khác biệt giữa mã đó và mã của tôi, nhưng tôi gặp vấn đề với giải pháp đó, vì vậy tôi đã đọc thêm một chút và thấy rằng chúng tôi phải thiết lập đối tượng trước khi trả lại. Ý tôi là:

#import <Foundation/Foundation.h>

@interface YourObject : NSObject <NSCopying>

@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *line;
@property (strong, nonatomic) NSMutableString *tags;
@property (strong, nonatomic) NSString *htmlSource;
@property (strong, nonatomic) NSMutableString *obj;

-(id) copyWithZone: (NSZone *) zone;

@end


@implementation YourObject


-(id) copyWithZone: (NSZone *) zone
{
    YourObject *copy = [[YourObject allocWithZone: zone] init];

    [copy setNombre: self.name];
    [copy setLinea: self.line];
    [copy setTags: self.tags];
    [copy setHtmlSource: self.htmlSource];

    return copy;
}

Tôi đã thêm câu trả lời này bởi vì tôi có rất nhiều vấn đề với vấn đề này và tôi không có manh mối về lý do tại sao nó xảy ra. Tôi không biết sự khác biệt, nhưng nó hiệu quả với tôi và có thể nó cũng có ích cho những người khác:)


3
another.obj = [obj copyWithZone: zone];

Tôi nghĩ rằng dòng này gây ra rò rỉ bộ nhớ, bởi vì bạn truy cập objthông qua thuộc tính mà (tôi giả sử) được khai báo là retain. Vì vậy, số lượng giữ lại sẽ được tăng lên theo thuộc tính và copyWithZone.

Tôi tin rằng nó phải là:

another.obj = [[obj copyWithZone: zone] autorelease];

hoặc là:

SomeOtherObject *temp = [obj copyWithZone: zone];
another.obj = temp;
[temp release]; 

Không, các phương thức phân bổ, sao chép, mutableCopy, new sẽ trả về các đối tượng không được phân bổ tự động.
kovpas

@kovpas, Bạn có chắc rằng Bạn hiểu tôi đúng không? Tôi không nói về đối tượng được trả về, tôi đang nói về nó là các trường dữ liệu.
Szuwar_Jr

vâng, tệ của tôi, xin lỗi. bạn có thể vui lòng chỉnh sửa câu trả lời của bạn bằng cách nào đó để tôi có thể xóa dấu trừ được không? :))
kovpas

0

Ngoài ra còn có việc sử dụng toán tử -> để sao chép. Ví dụ:

-(id)copyWithZone:(NSZone*)zone
{
    MYClass* copy = [MYClass new];
    copy->_property1 = self->_property1;
    ...
    copy->_propertyN = self->_propertyN;
    return copy;
}

Lý do ở đây là đối tượng sao chép kết quả nên phản ánh trạng thái của đối tượng ban đầu. Các "." nhà điều hành có thể đưa ra các hiệu ứng phụ vì điều này gọi các getters đến lượt nó có thể chứa logic.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.