Thuộc tính chỉ đọc trong Objective-C?


93

Tôi đã khai báo thuộc tính chỉ đọc trong giao diện của mình như sau:

 @property (readonly, nonatomic, copy) NSString* eventDomain;

Có thể tôi đang hiểu sai về thuộc tính, nhưng tôi nghĩ rằng khi bạn khai báo nó là readonly, bạn có thể sử dụng setter được tạo bên trong .mtệp implement ( ), nhưng các thực thể bên ngoài không thể thay đổi giá trị. Câu hỏi SO này nói rằng đó là điều nên xảy ra. Đó là hành vi mà tôi đang theo đuổi. Tuy nhiên, khi cố gắng sử dụng bộ setter hoặc cú pháp dấu chấm tiêu chuẩn để đặt eventDomainbên trong phương thức init của tôi, nó gây ra unrecognized selector sent to instance.lỗi cho tôi . Tất nhiên tôi đang @synthesizesử dụng tài sản. Đang cố gắng sử dụng nó như thế này:

 // inside one of my init methods
 [self setEventDomain:@"someString"]; // unrecognized selector sent to instance error

Như vậy có phải tôi đang hiểu sai về việc kê readonlykhai tài sản không? Hoặc là thứ gì đó đang diễn ra?

Câu trả lời:


116

Bạn cần phải nói với trình biên dịch rằng bạn cũng muốn có một trình cài đặt. Một cách phổ biến là đặt nó trong phần mở rộng lớp trong tệp .m:

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end

22
Nó không phải là một danh mục. Nó là một phần mở rộng của lớp học (như Eiko đã nói). Chúng khác nhau rõ ràng, mặc dù được sử dụng cho các mục đích tương tự. Ví dụ: bạn không thể làm như trên trong danh mục true.
bbum

2
Tôi nhận thấy rằng một lớp kế thừa từ một lớp có các thuộc tính mở rộng lớp, sẽ không thấy chúng. tức là kế thừa chỉ nhìn thấy giao diện bên ngoài. xác nhận?
Yogev Shelly

5
Đó không phải là một bbum công bằng. Nó có thể khác nhau, nhưng nó được biết đến như là "loại Anonymous"
MaxGabriel

3
Điều này có bổ sung cho khai báo ban đầu của op trong giao diện công khai không? Có nghĩa là, khai báo phần mở rộng lớp này có ghi đè lên khai báo ban đầu trong .h? Nếu không, tôi không hiểu điều này sẽ làm lộ ra một người dàn dựng công khai như thế nào. Cảm ơn
Madbreaks 19/12/12

4
@Madbreaks Nó bổ sung, nhưng nó nằm trong tệp .m. Vì vậy, trình biên dịch biết làm cho nó đọc ghi cho lớp đó, nhưng việc sử dụng bên ngoài vẫn bị giới hạn ở mức chỉ đọc như mong đợi.
Eiko

38

Eiko và những người khác đã đưa ra câu trả lời chính xác.

Đây là một cách đơn giản hơn: Truy cập trực tiếp vào biến thành viên riêng.

Thí dụ

Trong tệp .h tiêu đề:

@property (strong, nonatomic, readonly) NSString* foo;

Trong tệp .m triển khai:

// inside one of my init methods
self->_foo = @"someString"; // Notice the underscore prefix of var name.

Vậy đó, đó là tất cả những gì bạn cần. Không có rối rắm, không ồn ào.

Chi tiết

Kể từ Xcode 4.4 và LLVM Compiler 4.0 ( Các tính năng mới trong Xcode 4.4 ), bạn không cần phải làm việc nhà được thảo luận trong các câu trả lời khác:

  • Các synthesize từ khóa
  • Khai báo một biến
  • Khai báo lại thuộc tính trong tệp .m.

Sau khi khai báo một thuộc tính foo, bạn có thể giả sử Xcode đã thêm một biến thành viên riêng được đặt tên với tiền tố là dấu gạch dưới:_foo .

Nếu thuộc tính được khai báo readwrite, Xcode tạo ra một phương thức getter có tên foovà một setter có tên setFoo. Các phương thức này được gọi ngầm khi bạn sử dụng ký hiệu dấu chấm (Object.myMethod của tôi). Nếu thuộc tính đã được khai báo readonly, không có bộ cài đặt nào được tạo. Điều đó có nghĩa là biến ủng hộ, được đặt tên với dấu gạch dưới, không phải là bản thân nó chỉ đọc. Cácreadonly có nghĩa đơn giản là không có phương thức setter nào được tổng hợp và do đó việc sử dụng ký hiệu dấu chấm để đặt giá trị không thành công với lỗi trình biên dịch. Ký hiệu dấu chấm không thành công vì trình biên dịch ngăn bạn gọi một phương thức (bộ thiết lập) không tồn tại.

Cách đơn giản nhất để giải quyết vấn đề này là truy cập trực tiếp vào biến thành viên, được đặt tên bằng dấu gạch dưới. Bạn có thể làm như vậy ngay cả khi không khai báo biến có tên gạch dưới đó! Xcode đang chèn khai báo đó như một phần của quá trình xây dựng / biên dịch, vì vậy mã đã biên dịch của bạn thực sự sẽ có khai báo biến. Nhưng bạn không bao giờ thấy phần khai báo đó trong tệp mã nguồn ban đầu của mình. Không phải phép thuật, chỉ là đường cú pháp .

Sử dụng self->là một cách để truy cập một biến thành viên của đối tượng / cá thể. Bạn có thể bỏ qua điều đó và chỉ sử dụng tên var. Nhưng tôi thích sử dụng self + arrow hơn vì nó làm cho mã của tôi tự ghi lại. Khi bạn nhìn thấy self->_foobạn biết mà không mơ hồ rằng đó _foolà một biến thành viên trên trường hợp này.


Bằng cách này, thảo luận về ưu và nhược điểm của accessors tài sản so với truy cập Ivar trực tiếp chính là loại điều trị chu đáo bạn sẽ đọc trong Tiến sĩ Matt Neuberg 's Lập trình iOS cuốn sách. Tôi thấy rất hữu ích khi đọc và đọc lại.


1
Có lẽ bạn nên thêm rằng không bao giờ được truy cập trực tiếp vào biến thành viên (từ mà không có lớp của nó)! Làm như vậy là vi phạm OOP (che giấu thông tin).
ĐÃ

2
@HAS Đúng, kịch bản ở đây là cài đặt riêng biến thành viên không được nhìn thấy từ bên ngoài lớp này. Đó là toàn bộ điểm của câu hỏi của người đăng ban đầu: Khai báo một thuộc tính readonlyđể không lớp nào khác có thể thiết lập nó.
Basil Bourque

Tôi đang xem bài đăng này để biết cách tạo thuộc tính chỉ đọc. Điều này không làm việc cho tôi. Nó cho phép tôi gán giá trị trong init, nhưng tôi cũng có thể thay đổi _variable sau này bằng một phép gán. nó không gây ra lỗi hoặc cảnh báo trình biên dịch.
netskink

@BasilBourque Cảm ơn bạn rất nhiều về chỉnh sửa hữu ích cho câu hỏi "dai dẳng" đó!
GhostCat

36

Một cách khác mà tôi đã tìm thấy để làm việc với các thuộc tính chỉ đọc là sử dụng @synthesize để chỉ định kho sao lưu. Ví dụ

@interface MyClass

@property (readonly) int whatever;

@end

Sau đó, trong việc thực hiện

@implementation MyClass

@synthesize whatever = m_whatever;

@end

Các phương thức của bạn sau đó có thể thiết lập m_whatever, vì nó là một biến thành viên.


Một điều thú vị khác mà tôi đã nhận ra trong vài ngày qua là bạn có thể tạo các thuộc tính chỉ đọc có thể ghi được bởi các lớp con như vậy:

(trong tệp tiêu đề)

@interface MyClass
{
    @protected
    int m_propertyBackingStore;
}

@property (readonly) int myProperty;

@end

Sau đó, trong việc triển khai

@synthesize myProperty = m_propertyBackingStore;

Nó sẽ sử dụng khai báo trong tệp tiêu đề, vì vậy các lớp con có thể cập nhật giá trị của thuộc tính, trong khi vẫn giữ được tính độc lập của nó.

Tuy nhiên, hơi đáng tiếc về việc ẩn và đóng gói dữ liệu.


1
Cách đầu tiên bạn mô tả dễ giải quyết hơn cách trả lời được chấp nhận. Chỉ là "@synthesize foo1, foo2, foo3;" trong .m, và tất cả đều tốt.
sudo

20

Xem Tùy chỉnh các lớp hiện có trong Tài liệu iOS.

readonly Cho biết thuộc tính ở chế độ chỉ đọc. Nếu bạn chỉ định chỉ đọc, chỉ cần có phương thức getter trong @implementation. Nếu bạn sử dụng @synthesize trong khối thực thi, chỉ có phương thức getter được tổng hợp. Hơn nữa, nếu bạn cố gắng gán một giá trị bằng cú pháp dấu chấm, bạn sẽ gặp lỗi trình biên dịch.

Thuộc tính chỉ đọc chỉ có một phương thức getter. Bạn vẫn có thể đặt ivar hỗ trợ trực tiếp trong lớp của thuộc tính hoặc sử dụng mã hóa giá trị khóa.


8

Bạn đang hiểu sai câu hỏi kia. Trong câu hỏi đó có một phần mở rộng lớp, được khai báo như vậy:

@interface MYShapeEditorDocument ()
@property (readwrite, copy) NSArray *shapesInOrderBackToFront;
@end

Đó là những gì tạo ra setter chỉ hiển thị trong quá trình triển khai của lớp. Vì vậy, như Eiko nói, bạn cần phải khai báo một phần mở rộng của lớp và ghi đè khai báo thuộc tính để yêu cầu trình biên dịch chỉ tạo một bộ thiết lập trong lớp.


5

Giải pháp ngắn nhất là:

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end

2

Nếu một thuộc tính được định nghĩa là chỉ đọc, điều đó có nghĩa là thực sự sẽ không có một bộ cài đặt có thể được sử dụng nội bộ cho lớp hoặc bên ngoài từ các lớp khác. (nghĩa là: Bạn sẽ chỉ có "getter" nếu điều đó hợp lý.)

Từ âm thanh của nó, bạn muốn một thuộc tính đọc / ghi bình thường được đánh dấu là riêng tư, bạn có thể đạt được điều này bằng cách đặt biến lớp là riêng tư trong tệp giao diện của bạn như sau:

@private
    NSString* eventDomain;
}
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.