Свойства только для чтения в Objective-C?

94

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

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

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

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

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

Alex
источник

Ответы:

118

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

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end
Эйко
источник
22
Это не категория. Это расширение класса (как сказал Эйко). Они совершенно разные, хотя используются для схожих целей. Вы, например, не смогли бы сделать это в настоящей категории.
bbum 03
2
Я заметил, что класс, наследующий от класса, у которого есть свойства расширения класса, их не увидит. т.е. наследование видит только внешний интерфейс. подтвердить?
Йогев Шелли
5
Это не совсем честный бомж. Он может быть другим, но он известен как «категория
анонимов
3
Это дополнение к первоначальному объявлению оператора в общедоступном интерфейсе? Это означает, отменяет ли это объявление расширения класса первоначальное объявление в .h? В противном случае я не понимаю, как это выставит публичный сеттер. Спасибо
Madbreaks
4
@Madbreaks Это дополнительный элемент, но он находится в файле .m. Таким образом, компилятор знает, что для этого класса нужно выполнять чтение и запись, но внешнее использование по-прежнему ограничено только чтением, как и ожидалось.
Eiko
38

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

Вот более простой способ: получить прямой доступ к частной переменной-члену.

пример

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

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

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


Между прочим, обсуждение преимуществ и недостатков средств доступа к свойствам по сравнению с прямым доступом к ivar - это именно та продуманная трактовка, которую вы прочтете в книге доктора Мэтта Нойберга « Программирование iOS» . Мне было очень полезно читать и перечитывать.

Василий Бурк
источник
1
Возможно, вам следует добавить, что никогда не следует обращаться к переменной-члену напрямую (без ее класса)! Это является нарушением ООП (сокрытие информации).
ИМЕЕТ
2
@HAS Верно, здесь сценарий устанавливает частную переменную-член, невидимую извне этого класса. В этом вся суть вопроса исходного плаката: объявление свойства таким readonlyобразом, чтобы никакой другой класс не мог его установить.
Basil Bourque
Я смотрел этот пост о том, как создать свойство только для чтения. У меня это не работает. Это позволяет мне присвоить значение в инициализации, но я также могу изменить _variable позже с назначением. он не вызывает ошибки или предупреждения компилятора.
netskink 09
@BasilBourque Большое спасибо за полезное редактирование этого вопроса о "настойчивости"!
GhostCat
36

Другой способ, который я нашел для работы со свойствами только для чтения, - это использовать @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 в блоке реализации, синтезируется только метод получения. Более того, если вы попытаетесь присвоить значение, используя синтаксис с точкой, вы получите ошибку компилятора.

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

Иона
источник
9

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

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

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

BoltClock
источник
5

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

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end
user2159978
источник
2

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

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

@private
    NSString* eventDomain;
}
Джон Паркер
источник