Właściwości tylko do odczytu w Objective-C?

94

W moim interfejsie zadeklarowałem właściwość tylko do odczytu:

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

Może nie rozumiem właściwości, ale pomyślałem, że kiedy deklarujesz je jako readonly, możesz użyć wygenerowanego setera w .mpliku implementacji ( ), ale zewnętrzne jednostki nie mogą zmienić wartości. To pytanie SO mówi, że tak powinno się stać. To jest zachowanie, którego szukam. Jednak przy próbie użycia metody ustawiającej standard lub składni kropki do ustawienia eventDomainwewnątrz mojej metody init powoduje to unrecognized selector sent to instance.błąd. Oczywiście, że jestem @synthesizewłaścicielem. Próbuję tego użyć w ten sposób:

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

Czy więc źle rozumiem readonlyoświadczenie dotyczące nieruchomości? A może dzieje się coś innego?

Alex
źródło

Odpowiedzi:

118

Musisz powiedzieć kompilatorowi, że chcesz również ustawiacza. Typowym sposobem jest umieszczenie go w rozszerzeniu klasy w pliku .m:

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end
Eiko
źródło
22
To nie jest kategoria. Jest to rozszerzenie klasy (jak powiedział Eiko). Są wyraźnie różne, chociaż są używane do podobnych celów. Nie możesz na przykład zrobić tego w prawdziwej kategorii.
bbum
2
Zauważyłem, że klasa, która dziedziczy po klasie, która ma właściwości rozszerzenia klasy, nie zobaczy ich. tzn. dziedziczenie widzi tylko interfejs zewnętrzny. potwierdzać?
Yogev Shelly
5
To nie całkiem sprawiedliwe bbum. Może być inaczej, ale jest znany jako „kategoria anonimowa”
MaxGabriel
3
Czy jest to dodatek do początkowej deklaracji operacji w interfejsie publicznym? Znaczenie, czy ta deklaracja rozszerzenia klasy zastępuje początkową deklarację w .h? W przeciwnym razie nie widzę, jak mogłoby to ujawnić publicznego ustawiacza. Dzięki
Madbreaks
4
@Madbreaks To dodatkowe, ale znajduje się w pliku .m. Kompilator wie więc, że musi wykonać odczyt dla tej klasy, ale użycie zewnętrzne jest nadal ograniczone do odczytu tylko zgodnie z oczekiwaniami.
Eiko
38

Eiko i inni udzielili poprawnych odpowiedzi.

Oto prostszy sposób: bezpośredni dostęp do prywatnej zmiennej składowej.

Przykład

W pliku nagłówkowym .h:

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

W pliku .m implementacji:

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

To wszystko, to wszystko, czego potrzebujesz. Bez musów, bez zamieszania.

Detale

Począwszy od Xcode 4.4 i LLVM Compiler 4.0 ( nowe funkcje w Xcode 4.4 ), nie musisz zadzierać z obowiązkami omówionymi w innych odpowiedziach:

  • Słowo synthesizekluczowe
  • Deklarowanie zmiennej
  • Ponowne zadeklarowanie właściwości w pliku .m implementacji.

Po zadeklarowaniu właściwość foo, można założyć, Xcode dodała prywatną zmienną składową o nazwie z prefiksem podkreślenia: _foo.

Jeśli właściwość została zadeklarowana readwrite, Xcode generuje metodę pobierającą o nazwie fooi metodę ustawiającą o nazwie setFoo. Te metody są wywoływane niejawnie, gdy używasz notacji z kropką (my Object.myMethod). Jeśli właściwość została zadeklarowana readonly, nie jest generowany żaden ustawiający. Oznacza to, że zmienna zapasowa, nazwana podkreśleniem, sama nie jest tylko do odczytu. Te readonlyśrodki, które po prostu nie sposób seter został zsyntetyzowany, a więc za pomocą notacji kropki ustawić wartość nie z błędu kompilatora. Notacja z kropką kończy się niepowodzeniem, ponieważ kompilator powstrzymuje Cię przed wywołaniem metody (ustawiającej), która nie istnieje.

Najprostszym sposobem obejścia tego jest bezpośredni dostęp do zmiennej składowej, nazwanej podkreśleniem. Możesz to zrobić nawet bez deklarowania zmiennej o nazwie podkreślonej! Xcode wstawia tę deklarację jako część procesu kompilacji / kompilacji, więc skompilowany kod rzeczywiście będzie zawierał deklarację zmiennej. Ale nigdy nie widzisz tej deklaracji w swoim oryginalnym pliku kodu źródłowego. Nie magia, tylko cukier syntaktyczny .

Używanie self->to sposób na dostęp do zmiennej składowej obiektu / instancji. Możesz to pominąć i po prostu użyć nazwy var. Ale wolę używać self + arrow, ponieważ sprawia, że ​​mój kod jest samodokumentujący. Gdy zobaczysz, self->_fooże wiesz bez niejasności, _foojest to zmienna składowa w tej instancji.


Nawiasem mówiąc, omówienie zalet i wad udostępniające właściwości w porównaniu bezpośrednim dostępem Ivar jest dokładnie ten rodzaj przemyślany leczenia będziesz czytać w dr Matt Neuberg „s Programowanie iOS książki. Przeczytanie i ponowne przeczytanie okazało się bardzo pomocne.

Basil Bourque
źródło
1
Być może powinieneś dodać, że nigdy nie należy uzyskiwać bezpośredniego dostępu do zmiennej składowej (spoza jej klasy)! Takie postępowanie stanowi naruszenie OOP (ukrywanie informacji).
MA
2
@ MA Prawidłowo, scenariusz tutaj polega na prywatnym ustawieniu zmiennej składowej niewidocznej spoza tej klasy. To jest cały punkt pytania autora oryginalnego: zadeklarowanie właściwości jako readonlytakiej, aby żadna inna klasa nie mogła jej ustawić.
Basil Bourque
Patrzyłem na ten post, aby dowiedzieć się, jak utworzyć właściwość tylko do odczytu. To nie działa dla mnie. To pozwala mi przypisać wartość w init, ale mogę też później zmienić _variable przy pomocy przypisania. nie zgłasza błędu ani ostrzeżenia kompilatora.
netskink
@BasilBourque Bardzo dziękuję za pomocną edycję tego pytania „wytrwałości”!
GhostCat
36

Innym sposobem pracy z właściwościami tylko do odczytu jest użycie @synthesize do określenia magazynu zapasowego. Na przykład

@interface MyClass

@property (readonly) int whatever;

@end

Następnie w realizacji

@implementation MyClass

@synthesize whatever = m_whatever;

@end

Twoje metody mogą następnie ustawić m_whatever, ponieważ jest to zmienna składowa.


Inną interesującą rzeczą, z której zdałem sobie sprawę w ciągu ostatnich kilku dni, jest to, że możesz tworzyć właściwości tylko do odczytu, które są zapisywalne przez podklasy, takie jak:

(w pliku nagłówkowym)

@interface MyClass
{
    @protected
    int m_propertyBackingStore;
}

@property (readonly) int myProperty;

@end

Następnie w trakcie realizacji

@synthesize myProperty = m_propertyBackingStore;

Użyje deklaracji w pliku nagłówkowym, więc podklasy mogą aktualizować wartość właściwości, zachowując jej tylko do odczytu.

Nieco niestety, jeśli chodzi o ukrywanie i hermetyzację danych.

yano
źródło
1
Pierwszy sposób, który opisałeś, jest łatwiejszy do rozwiązania niż zaakceptowana odpowiedź. Po prostu „@synthesize foo1, foo2, foo3;” w .m i wszystko jest w porządku.
sudo
20

Zobacz Dostosowywanie istniejących klas w dokumentach iOS.

readonly Wskazuje, że właściwość jest tylko do odczytu. Jeśli określisz tylko do odczytu, w @implementation wymagana jest tylko metoda pobierająca. Jeśli używasz @synthesize w bloku implementacji, syntetyzowana jest tylko metoda pobierająca. Co więcej, jeśli spróbujesz przypisać wartość za pomocą składni z kropką, pojawi się błąd kompilatora.

Właściwości tylko do odczytu mają tylko metodę pobierającą. Nadal można ustawić zapasowy ivar bezpośrednio w klasie właściwości lub za pomocą kodowania wartości klucza.

Jonasza
źródło
9

Nie rozumiesz drugiego pytania. W tym pytaniu istnieje rozszerzenie klasy, zadeklarowane w ten sposób:

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

To właśnie generuje ustawiacz widoczny tylko w implementacji klasy. Tak więc, jak mówi Eiko, musisz zadeklarować rozszerzenie klasy i przesłonić deklarację właściwości, aby poinstruować kompilator, aby generował metodę ustawiającą tylko w klasie.

BoltClock
źródło
5

Najkrótszym rozwiązaniem jest:

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end
user2159978
źródło
2

Jeśli właściwość jest zdefiniowana jako tylko do odczytu, oznacza to, że faktycznie nie będzie metody ustawiającej, której można by użyć wewnętrznie do klasy lub zewnętrznie z innych klas. (tj. będziesz mieć „getter” tylko wtedy, gdy ma to sens).

Z dźwięków tego wynika, że ​​chcesz mieć normalną właściwość odczytu / zapisu, która jest oznaczona jako prywatna, co można osiągnąć, ustawiając zmienną klasy jako prywatną w pliku interfejsu jako taką:

@private
    NSString* eventDomain;
}
John Parker
źródło