Властивості для читання лише в Objective-C?


93

Я оголосив властивість лише для читання у своєму інтерфейсі як такий:

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

Можливо, я неправильно розумію властивості, але я думав, що коли ви оголосите це як readonly, ви можете використовувати згенерований сеттер усередині .mфайлу реализации ( ), але зовнішні сутності не можуть змінити значення. Це ТО запитання говорить, що саме так і має статися. Ось така поведінка, за якою я пішов. Однак при спробі використати стандартний синтаксис сеттера або крапки для встановлення eventDomainвсередині мого методу init це видає мені unrecognized selector sent to instance.помилку. Звичайно, я маю @synthesizeвласність. Спроба використовувати його так:

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

То я не розумію readonlyдекларацію про майно? Або щось інше відбувається?

Відповіді:


116

Вам потрібно сказати компілятору, що ви також хочете встановити. Поширений спосіб - помістити його у розширення класу у файл .m:

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end

22
Це не категорія. Це розширення класу (як сказав Ейко). Вони виразно відрізняються, хоча і використовуються для подібних цілей. Наприклад, ви не могли зробити вищезазначене у справжній категорії.
bbum

2
Я помітив, що клас, який успадковує від класу, що має властивості розширення класу, не побачить їх. тобто успадкування бачити лише зовнішній інтерфейс. підтвердити?
Йогев Шеллі

5
Це не зовсім справедливий bbum. Це може бути інакше, але це відоме як "анонімна категорія"
MaxGabriel

3
Чи є це додатково до первинної декларації ОП у загальнодоступному інтерфейсі? Значення, чи ця декларація розширення класу замінює початкову декларацію в .h? В іншому випадку я не розумію, як це могло б викрити публічного установника. Спасибі
Madbreaks

4
@Madbreaks Це додатково, але це у файлі .m. Отже, компілятор знає, як зробити це readwrite для цього класу, але зовнішнє використання все одно обмежується лише для читання, як очікувалося.
Ейко

38

Ейко та інші дали правильні відповіді.

Ось простіший спосіб: безпосередній доступ до змінної private member.

Приклад

У заголовку .h-файлу:

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

У файлі реалізації .m:

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

Ось і все, це все, що вам потрібно. Ні мусу, ні метушні.

Деталі

Що стосується Xcode 4.4 та LLVM Compiler 4.0 ( нові функції Xcode 4.4 ), вам не потрібно псувати з справами, обговореними в інших відповідях:

  • synthesizeключове слово
  • Декларування змінної
  • Повторне оголошення властивості у файлі реалізації .m.

Після оголошення властивості foo, можна припустити , Xcode додала закриту змінну з ім'ям з префіксом підкреслення: _foo.

Якщо властивість було оголошено readwrite, Xcode генерує метод getter з іменем fooта setter з іменем setFoo. Ці методи неявно викликаються, коли ви використовуєте позначення крапками (мій Object.myMethod). Якщо властивість оголошено readonly, сеттер не генерується. Це означає, що змінна підтримки, названа символом підкреслення, не є самою для читання. У readonlyпросто означає , що ні один метод не був сетера синтезований, і , отже , з допомогою точкової нотації , щоб встановити значення зазнає невдачі з помилкою компілятора. Позначення крапок не вдається, оскільки компілятор зупиняє вас від виклику методу (сеттера), який не існує.

Найпростіший спосіб цього - безпосередньо отримати доступ до змінної члена, названої підкресленням. Ви можете зробити це, навіть не оголошуючи змінну з підкресленим іменем! Xcode вставляє цю декларацію як частину процесу побудови / компіляції, тому ваш скомпільований код дійсно матиме декларацію змінної. Але ви ніколи не бачите цієї декларації у своєму вихідному файлі вихідного коду. Не магія, а просто синтаксичний цукор .

Використання self->- це спосіб отримати доступ до змінної члена об'єкта / екземпляра. Можливо, ви можете це опустити, і просто використовуйте ім'я var. Але я волію використовувати стрілку self +, оскільки це робить мій код самодокументованим. Коли ви бачите, що self->_fooви знаєте без неоднозначності, що _fooє змінною-членом у цьому екземплярі.


До речі, обговорення плюсів і мінусів власників прав доступу проти прямого доступу ivar - це саме той продуманий спосіб, який ви прочитаєте в книзі доктора Метта Нойберга з програмування iOS . Мені було дуже корисно читати та перечитувати.


1
Можливо, вам слід додати, що ніколи не слід отримувати доступ до змінної члена безпосередньо (з-за її класу)! Це є порушенням ООП (приховування інформації).
ВІД

2
@HAS Правильно, тут сценарій приватно встановлює змінну-член, яку не видно поза цим класом. Ось і вся суть питання про оригінальний плакат: Оголошення властивості readonlyтакою, що жоден інший клас не може її встановити.
Василь Бурк

Я розглядав цю публікацію, як створити власність лише для читання. Це не працює для мене. Це дозволяє мені призначити значення в init, але я також можу змінити _variable пізніше за допомогою призначення. це не викликає помилки чи попередження компілятора.
netskink

@BasilBourque Щиро дякую за корисну редакцію цього питання "наполегливості"!
GhostCat

36

Ще один спосіб роботи зі властивостями readonly - це використання @synthesize для визначення сховища. Наприклад

@interface MyClass

@property (readonly) int whatever;

@end

Потім у реалізації

@implementation MyClass

@synthesize whatever = m_whatever;

@end

Тоді ваші методи можуть встановлюватися m_whatever, оскільки це змінна член.


Ще одна цікава річ, яку я зрозумів протягом останніх кількох днів, - це те, що ви можете створювати властивості лише для читання, які можна записувати в такі підкласи:

(у файлі заголовка)

@interface MyClass
{
    @protected
    int m_propertyBackingStore;
}

@property (readonly) int myProperty;

@end

Потім, у реалізації

@synthesize myProperty = m_propertyBackingStore;

Він використовуватиме декларацію у файлі заголовка, тому підкласи можуть оновлювати значення властивості, зберігаючи при цьому лише її читання.

Хоча трохи жаль з приводу приховування даних та інкапсуляції.


1
Перший спосіб, який ви описали, легше мати справу з прийнятою відповіддю. Просто "@synthesize foo1, foo2, foo3;" в .m, і все добре.
sudo

20

Див. Налаштування існуючих класів у Документах iOS.

readonly Позначає, що властивість лише для читання. Якщо ви вказуєте лише для читання, у @implementation потрібен лише метод отримання. Якщо ви використовуєте @synthesize в блоці реалізації, синтезується лише метод отримання. Більше того, якщо ви намагаєтеся призначити значення за допомогою синтаксису крапок, ви отримуєте помилку компілятора.

Властивості Readonly мають лише метод геттера. Ви все ще можете встановити резервний ivar безпосередньо в класі властивості або використовуючи кодування ключових значень.


9

Ви неправильно розумієте інше питання. У цьому питанні є розширення класу, заявлене таким чином:

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

Це те, що генерує сетер, видимий лише в межах реалізації класу. Отже, як каже Eiko, вам потрібно оголосити розширення класу та перевизначити декларацію властивості, щоб сказати компілятору генерувати сеттер лише в класі.


5

Найкоротшим рішенням є:

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end

2

Якщо властивість визначено як лише для читання, це означає, що фактично не буде сеттера, який може бути використаний як для внутрішнього класу, так і для інших класів. (тобто: "геть" буде мати лише якщо це має сенс.)

З його звуків ви хочете отримати звичайну властивість читання / запису, позначену як приватну, чого можна досягти, встановивши змінну класу як приватну у вашому файлі інтерфейсу як таку:

@private
    NSString* eventDomain;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.