2013-09-07 13 views
17

Istnieje klasa, która wygląda tak (ja pominięciem importu dla zwięzłość):Zastępowanie właściwość tylko do odczytu w podklasie

Base.h:

@interface Base : NSObject 
@property (strong, readonly) NSString *something; 
- (id)initWithSomething:(NSString *)something; 

@end

bazowej. m:

@implementation Base 
- (id)initWithSomething:(NSString *)something { 
    self = [super init]; 
    if (self) _something = something; 
    return self; 
} 
@end 

Jak widać, 'coś' właściwość jest tylko do odczytu. Teraz chcę utworzyć podklasę, który zastępuje że nieruchomość będzie zapisu, a także:

Sub.h:

@interface Sub : Base 
@property (strong) NSString *something; 
@end 

Sub.m:

@implementation Sub 
@end 

I kod:

main.c:

int main(int argc, const char * argv[]) { 
    @autoreleasepool { 
     Sub *o = [Sub new]; 
     o.something = @"foo"; 
     NSLog(@"%@", o.something); 
    } 
    return 0; 
} 

Ten kod jest następujący:

2013-09-07 13:58:36.970 ClilTest[3094:303] *** Terminating app due to uncaught 
    exception 'NSInvalidArgumentException', reason: '-[Sub setSomething:]: unrecognized 
    selector sent to instance 0x100109ff0' 

Dlaczego tak jest? Dlaczego nie znajduje setSelector?

Kiedy to zrobić w podklasie Zamiast:

Sub.m:

@implementation Sub 
@synthesize something = _something; 
@end 

to wszystko działa. Czy to oznacza, że ​​własność podklasy nie jest domyślnie syntezowana, mimo że jest zdefiniowana jako @property w ? Czy kompilacja w jakiś sposób "widzi" automatycznie wygenerowany getter z bazy i nie generuje ustawiającego? I dlaczego, myślę, że seter powinien zostać wygenerowany, ponieważ jeszcze nie istnieje. Używam Xcode 4.6.2, a projekt to narzędzie Cli (typ Foundation), ale to samo dzieje się w moim rzeczywistym projekcie, który jest aplikacją na iPhone'a.

Tło: Mam ciężki obiekt (instancja Base), który wymaga połączenia Bluetooth z niektórymi urządzeniami i mam stworzyć kontroler widoku dla niektórych funkcji. Dla łatwego testowania nie chcę być podłączony do BT (w rzeczywistości potrzebowałbym fizycznego urządzenia i przetestować kod na nim), chciałbym móc przetestować go w symulatorze. To, co wymyśliłem, polega na tym, że po prostu utworzę podklasę (Sub), która koduje kilka metod/właściwości i używa jej zamiast tego, a kiedy kod jest gotowy, po prostu usuwam kod dla podklasy, zastępuję jego instancję poprawnym , przetestuj urządzenie, zatwierdz i pchnij. Działa to naprawdę dobrze, z wyjątkiem tej dziwnej rzeczy z powyższym @property.

Czy ktoś może mi powiedzieć, co się dzieje z przesłonięciem nieruchomości?

+0

Oprócz działającego rozwiązania, chciałem dodać, że "zwiększenie" poziomu prywatności atrybutu jest sprzeczne z zasadami OOP. Zwykle (ale nie zawsze), jeśli musisz uczynić właściwość bardziej widoczną (prywatna -> chroniona, lub chroniona -> publiczna) w podklasie, to coś jest nie tak w modelu. Odwrotnie dzieje się z metodami. Jeśli klasa nadrzędna ma metodę A, nie powinieneś uczynić A bardziej prywatnym (public -> protected, lub protected -> private) w podklasie. –

Odpowiedz

28

Dla właściwości tylko do odczytu syntetyzowana jest tylko metoda gettera, ale bez metody ustawiającej.

A przy kompilacji podklasę, kompilator nie wie jak nieruchomość jest realizowany w klasie bazowej (może to być zwyczaj getter zamiast zmiennej instancji podkładu). Nie można więc po prostu utworzyć metody setera w podklasie.


Jeżeli chcesz mieć dostęp do zapisu sama instancja zmiennej z podklasy, trzeba zadeklarować ją jako @protected w klasie bazowej (tak, że jest ona dostępna w podklasie), re -declare właściwość jak odczyt i zapis w podklasie, i przedstawienie sposobu seter:

Base.h:

@interface Base : NSObject { 
@protected 
    NSString *_something; 
} 
@property (strong, readonly) NSString *something; 
- (id)initWithSomething:(NSString *)something; 
@end 

Sub.h:

@interface Sub : Base 
@property (strong, readwrite) NSString *something; 
@end 

Sub.m:

@implementation Sub 
-(void)setSomething:(NSString *)something 
{ 
    _something = something; 
} 
@end 

Twój roztwór

@synthesize something = _something; 

generuje gettera oraz sposób setter w podklasie, stosując zmiennej oddzielne wystąpienie_something w podklasie (która jest di fferent od _something w klasie bazowej).

To również działa, po prostu powinieneś wiedzieć, że self.something odnosi się do różnych zmiennych instancji w klasie bazowej i podklasie. Aby to bardziej oczywiste, ty mógłby użyć innej zmiennej instancji w podklasie:

@synthesize something = _somethingElse; 
+0

Dlaczego kompilator nie wie, w jaki sposób został utworzony getter? Kompiluje ten sam projekt, więc może uzyskać te informacje. Pierwsza opcja polegająca na tym, że pole \ @protected faktycznie nie pomaga, seter wciąż nie jest generowany. Dziwne, mogę go skompilować bez żadnych problemów i używam domyślnych ustawień dla wszystkiego. Sądzę, że ponieważ wygenerowany iVar jest \ @private, podklasa może generować własne @private _ coś, ponieważ są to faktycznie różne zmienne. Tak przynajmniej działało, powiedzmy, Java. Czy na pewno próbowałeś dokładnie skompilować mój kod? – wujek

+0

@wujek: Tak, jestem pewien. Przetestowałem również mój kod przed jego opublikowaniem. Czy masz jakieś wyrażenie '@ synthesize' dla właściwości w Base.m? –

+0

To bardzo dziwne, ponieważ właśnie to działa dla mnie w tej chwili. Mogę udostępnić mój projekt, jeśli chciałbyś rzucić okiem. – wujek

-1

Domyślne zmienne są takie same, gdy właściwość nie jest syntetyzowana w podklasie. Tak więc w czasie wykonywania program próbuje wywołać setSomething w superklasie. Ale ponieważ nie istnieje, wyrzucany jest wyjątek.

2

Podana odpowiedź działa perfekcyjnie. Jest to alternatywna odpowiedź, podobno Apple likes a bit more.

Można zdefiniować private extension swojej klasie, plik Base+Protected.h, który musi zostać uwzględniony w Base.m i Sub.m.

Następnie, w tym nowym pliku, redefiniujesz własność jako readwrite.

@interface Base() 
@property (strong, readwrite) NSString *something; 
@end 

Ta alternatywa pozwala używać accessor self.something rathern niż ivar _something.

Uwaga: nadal musisz zachować definicję something w swojej Base.h, jaka jest.

Powiązane problemy