Readonly Properties в Objective-C?

Я объявил свойство readonly в моем интерфейсе как таковом:

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

Возможно, я неправильно понимаю свойства, но я думал, что когда вы объявляете его как readonly, вы можете использовать сгенерированный сеттер внутри файла реализации (.m), но внешние объекты не могут изменить значение. Этот вопрос SO говорит, что должно произойти. Это поведение, которое я переживаю. Однако при попытке использовать стандартный синтаксис сеттера или точки для установки eventDomain внутри моего метода init он дает мне ошибку unrecognized selector sent to instance.. Конечно, я @synthesize свойство. Попытка использовать его следующим образом:

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

Так я не понимаю объявление readonly для свойства? Или что-то еще происходит?

Ответ 1

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

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end

Ответ 2

Другим способом, который я нашел для работы с свойствами readonly, является использование @synthesize для указания хранилища резервных копий. Например

@interface MyClass

@property (readonly) int whatever;

@end

Тогда в реализации

@implementation MyClass

@synthesize whatever = _whatever;

@end

Затем ваши методы могут установить _whatever, поскольку это переменная-член.


Еще одна интересная вещь, которую я осознал в последние несколько дней, - вы можете сделать свойства readonly доступными для записи подклассами вроде:

(в файле заголовка)

@interface MyClass
{
    @protected
    int _propertyBackingStore;
}

@property (readonly) int myProperty;

@end

Затем в реализации

@synthesize myProperty = _propertyBackingStore;

Он будет использовать объявление в файле заголовка, поэтому подклассы могут обновлять значение свойства, сохраняя при этом свою readonlyness.

К сожалению, с точки зрения скрытия данных и инкапсуляции, хотя.

Ответ 3

Эйко и другие дали правильные ответы.

Вот более простой способ: Непосредственный доступ к частной переменной-члену.

Пример

В файле заголовка .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 и установщик с именем setFoo. Эти методы неявно называются при использовании точечной нотации (мой Object.myMethod). Если свойство было объявлено readonly, сеттер не генерируется. Это означает, что переменная поддержки, названная с подчеркиванием, сама по себе не является readonly. readonly означает просто, что метод setter не был синтезирован, и поэтому использование точечной нотации для установки значения не выполняется с ошибкой компилятора. Точечное обозначение терпит неудачу, потому что компилятор не позволяет вам вызвать метод (сеттер), который не существует.

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

Использование self-> - это способ доступа к переменной-члену объекта/экземпляра. Вы можете опустить это и просто использовать имя var. Но я предпочитаю использовать стрелку self +, потому что это делает мой код самодокументированным. Когда вы видите self->_foo, вы знаете без двусмысленности, что _foo является переменной-членом в этом экземпляре.


Кстати, обсуждение плюсов и минусов доступа к аксессуарам по сравнению с прямым доступом к ivar - это именно то, что вдумчивое лечение вы прочитаете в Dr. Matt Neuberg Программирование iOS. Мне было очень полезно читать и перечитывать.

Ответ 4

См. Настройка существующих классов в Документах iOS.

только для чтения Указывает, что свойство доступно только для чтения. Если вы укажете только для чтения, в @implementation требуется только метод getter. Если вы используете @synthesize в блоке реализации, синтезируется только метод getter. Более того, если вы попытаетесь присвоить значение с помощью синтаксиса точек, вы получите ошибку компилятора.

Свойства Readonly имеют только метод getter. Вы все равно можете установить поддержку ivar непосредственно в классе свойств или с помощью кодирования значения ключа.

Ответ 5

Вы недопонимаете другой вопрос. В этом вопросе есть расширение класса, объявленное таким образом:

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

Это то, что генерирует сеттер, только видимый внутри реализации класса. Так, как говорит Эйко, вам нужно объявить расширение класса и переопределить объявление свойства, чтобы сообщить компилятору сгенерировать сеттер только внутри класса.

Ответ 6

Самое короткое решение:

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end

Ответ 7

Если свойство определено как readonly, это означает, что фактически не будет установщиком, который может использоваться либо внутри класса, либо извне из других классов. (т.е. у вас будет только "getter", если это имеет смысл.)

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

@private
    NSString* eventDomain;
}