Cel-C: Zmienna właściwości / instancji w kategorii

122

Ponieważ nie mogę utworzyć zsyntetyzowanej właściwości w kategorii w celu C, nie wiem, jak zoptymalizować następujący kod:

@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end

@implementation MyClass (Variant)

@dynamic test;

- (NSString *)test {
    NSString *res;
    //do a lot of stuff
    return res;
}

@end

Metoda testowa jest wywoływana wiele razy w czasie wykonywania i robię dużo rzeczy, aby obliczyć wynik. Zwykle używając zsyntetyzowanej właściwości, przechowuję wartość w IVar _test przy pierwszym wywołaniu metody i zwracam tę wartość IVar następnym razem. Jak mogę zoptymalizować powyższy kod?

dhrm
źródło
2
Dlaczego nie zrobić tego, co zwykle robisz, tylko zamiast kategorii dodać właściwość do klasy bazowej MyClass? Aby pójść dalej, wykonuj ciężkie rzeczy w tle i pozwól, aby proces uruchomił powiadomienie lub zadzwoń do programu obsługi MyClass, gdy proces się zakończy.
Jeremy
4
MyClass to klasa wygenerowana z Core Data. Gdybym oprócz mojego niestandardowego kodu obiektowego wewnątrz wygenerowanej klasy zniknął, gdybym ponownie wygenerował klasę z moich danych podstawowych. Z tego powodu używam kategorii.
dhrm
1
Może przyjąć pytanie, które najlepiej pasuje do tytułu? ("Nieruchomość w kategorii")
hfossli
Dlaczego po prostu nie utworzyć podklasy?
Scott Zhu

Odpowiedzi:

124

Metoda @ lorean zadziała (uwaga: odpowiedź została usunięta) , ale miałbyś tylko jedno miejsce na dane. Więc jeśli chcesz użyć tego w wielu wystąpieniach i kazać każdemu wystąpieniu obliczać odrębną wartość, nie zadziała.

Na szczęście środowisko wykonawcze Objective-C ma coś, co nazywa się obiektami powiązanymi, które mogą robić dokładnie to, czego chcesz:

#import <objc/runtime.h>

static void *MyClassResultKey;
@implementation MyClass

- (NSString *)test {
  NSString *result = objc_getAssociatedObject(self, &MyClassResultKey);
  if (result == nil) {
    // do a lot of stuff
    result = ...;
    objc_setAssociatedObject(self, &MyClassResultKey, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  }
  return result;
}

@end
Dave DeLong
źródło
poprawiony link do obiektów powiązanych: developer.apple.com/library/ios/#documentation/cocoa/Conceptual/…
MechEthan
5
@DaveDeLong Dziękujemy za to rozwiązanie! Niezbyt piękne, ale działa :)
dhrm
42
„niezbyt piękna”? Na tym polega piękno Objective-C! ;)
Dave DeLong
6
Świetna odpowiedź! Możesz nawet pozbyć się zmiennej statycznej, używając @selector(test)jako klucza, jak wyjaśniono tutaj: stackoverflow.com/questions/16020918/ ...
Gabriele Petronella
1
@HotFudgeSunday - myślę, że działa w szybkim
tempie
174

plik h

@interface NSObject (LaserUnicorn)

@property (nonatomic, strong) LaserUnicorn *laserUnicorn;

@end

plik .m

#import <objc/runtime.h>

static void * LaserUnicornPropertyKey = &LaserUnicornPropertyKey;

@implementation NSObject (LaserUnicorn)

- (LaserUnicorn *)laserUnicorn {
    return objc_getAssociatedObject(self, LaserUnicornPropertyKey);
}

- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
    objc_setAssociatedObject(self, LaserUnicornPropertyKey, unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

@end

Podobnie jak zwykła właściwość - dostępna za pomocą notacji kropkowej

NSObject *myObject = [NSObject new];
myObject.laserUnicorn = [LaserUnicorn new];
NSLog(@"Laser unicorn: %@", myObject.laserUnicorn);

Łatwiejsza składnia

Alternatywnie możesz użyć @selector(nameOfGetter)zamiast tworzenia statycznego klawisza wskaźnika, jak poniżej:

- (LaserUnicorn *)laserUnicorn {
    return objc_getAssociatedObject(self, @selector(laserUnicorn));
}

- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
    objc_setAssociatedObject(self, @selector(laserUnicorn), unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

Więcej informacji można znaleźć pod adresem https://stackoverflow.com/a/16020927/202451

hfossli
źródło
4
Dobry artykuł. Należy zwrócić uwagę na aktualizację w artykule. Aktualizacja z 22 grudnia 2011: Należy zauważyć, że klucz skojarzenia to void pointer void * key, a nie ciąg. Oznacza to, że podczas pobierania powiązanego odwołania musisz przekazać dokładnie ten sam wskaźnik do środowiska wykonawczego. Nie działałoby to zgodnie z przeznaczeniem, gdybyś użył łańcucha C jako klucza, a następnie skopiował łańcuch do innego miejsca w pamięci i spróbował uzyskać dostęp do skojarzonego odwołania, przekazując wskaźnik do skopiowanego ciągu jako klucz.
Pan Rogers,
4
Naprawdę nie potrzebujesz @dynamic objectTag;. @dynamicoznacza, że ​​setter i getter zostaną wygenerowane gdzie indziej, ale w tym przypadku są one zaimplementowane tutaj.
IluTov
@NSAddict true! Naprawiony!
hfossli
1
A co z cofnięciem przydziału laserowych jednorożców do ręcznego zarządzania pamięcią? Czy to wyciek pamięci?
Manny
Jest wydany automatycznie stackoverflow.com/questions/6039309/…
hfossli
32

Podana odpowiedź działa świetnie, a moja propozycja jest tylko jej rozszerzeniem, które pozwala uniknąć pisania zbyt wielu standardowych kodów.

Aby uniknąć wielokrotnego pisania metod pobierających i ustawiających dla właściwości kategorii, ta odpowiedź wprowadza makra. Dodatkowo te makra ułatwiają korzystanie z właściwości typów pierwotnych, takich jak intlub BOOL.

Tradycyjne podejście bez makr

Tradycyjnie definiujesz właściwość kategorii, taką jak

@interface MyClass (Category)
@property (strong, nonatomic) NSString *text;
@end

Następnie musisz zaimplementować metodę pobierającą i ustawiającą, używając skojarzonego obiektu i selektora pobierania jako klucza ( patrz oryginalna odpowiedź ):

#import <objc/runtime.h>

@implementation MyClass (Category)
- (NSString *)text{
    return objc_getAssociatedObject(self, @selector(text));
}

- (void)setText:(NSString *)text{
    objc_setAssociatedObject(self, @selector(text), text, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

Moje sugerowane podejście

Teraz, używając makra, napiszesz zamiast tego:

@implementation MyClass (Category)

CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)

@end

Makra są zdefiniowane w następujący sposób:

#import <objc/runtime.h>

#define CATEGORY_PROPERTY_GET(type, property) - (type) property { return objc_getAssociatedObject(self, @selector(property)); }
#define CATEGORY_PROPERTY_SET(type, property, setter) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), property, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
#define CATEGORY_PROPERTY_GET_SET(type, property, setter) CATEGORY_PROPERTY_GET(type, property) CATEGORY_PROPERTY_SET(type, property, setter)

#define CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(type, property, valueSelector) - (type) property { return [objc_getAssociatedObject(self, @selector(property)) valueSelector]; }
#define CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(type, property, setter, numberSelector) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), [NSNumber numberSelector: property], OBJC_ASSOCIATION_RETAIN_NONATOMIC); }

#define CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(unsigned int, property, unsignedIntValue)
#define CATEGORY_PROPERTY_SET_UINT(property, setter) CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(unsigned int, property, setter, numberWithUnsignedInt)
#define CATEGORY_PROPERTY_GET_SET_UINT(property, setter) CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_SET_UINT(property, setter)

Makro CATEGORY_PROPERTY_GET_SETdodaje metodę pobierającą i ustawiającą dla danej właściwości. Właściwości tylko do odczytu lub tylko do zapisu będą używać odpowiednio makra CATEGORY_PROPERTY_GETi CATEGORY_PROPERTY_SET.

Trochę więcej uwagi wymagają typy prymitywne

Ponieważ typy pierwotne nie są obiektami, powyższe makra zawierają przykład użycia unsigned intjako typu właściwości. Czyni to poprzez zawijanie wartości całkowitej do NSNumberobiektu. Więc jego użycie jest analogiczne do poprzedniego przykładu:

@interface ...
@property unsigned int value;
@end

@implementation ...
CATEGORY_PROPERTY_GET_SET_UINT(value, setValue:)
@end

W następstwie tego wzorca, można po prostu dodać więcej makr również wspierać signed int, BOOLitp ...

Ograniczenia

  1. Wszystkie makra są OBJC_ASSOCIATION_RETAIN_NONATOMICdomyślnie używane.

  2. IDE, takie jak App Code , obecnie nie rozpoznają nazwy ustawiającego podczas refaktoryzacji nazwy właściwości. Musisz samodzielnie zmienić jego nazwę.

Lars Blumberg
źródło
1
#import <objc/runtime.h>w przeciwnym razie nie zapomnij dodać do kategorii .m pliku. błąd czasu kompilacji: niejawna deklaracja funkcji „objc_getAssociatedObject” jest nieprawidłowa w C99 . stackoverflow.com/questions/9408934/…
Sathe_Nagaraja
7

Po prostu użyj biblioteki libextobjc :

plik h:

@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end

m-plik:

#import <extobjc.h>
@implementation MyClass (Variant)

@synthesizeAssociation (MyClass, test);

@end

Więcej o @synthesizeAssociation

Mansurov Ruslan
źródło
Jaki jest prawidłowy sposób na zastąpienie tego akcesorium (np. Leniwe ładowanie właściwości)
David James,
3

Testowane tylko z iOS 9 Przykład: Dodawanie właściwości UIView do UINavigationBar (Category)

UINavigationBar + Helper.h

#import <UIKit/UIKit.h>

@interface UINavigationBar (Helper)
@property (nonatomic, strong) UIView *tkLogoView;
@end

UINavigationBar + Helper. M

#import "UINavigationBar+Helper.h"
#import <objc/runtime.h>

#define kTKLogoViewKey @"tkLogoView"

@implementation UINavigationBar (Helper)

- (void)setTkLogoView:(UIView *)tkLogoView {
    objc_setAssociatedObject(self, kTKLogoViewKey, tkLogoView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIView *)tkLogoView {
    return objc_getAssociatedObject(self, kTKLogoViewKey);
}

@end
Rikco
źródło
-2

Innym możliwym rozwiązaniem, być może łatwiejszym, którego nie używa, Associated Objectsjest zadeklarowanie zmiennej w pliku implementacji kategorii w następujący sposób:

@interface UIAlertView (UIAlertViewAdditions)

- (void)setObject:(id)anObject;
- (id)object;

@end


@implementation UIAlertView (UIAlertViewAdditions)

id _object = nil;

- (id)object
{
    return _object;
}

- (void)setObject:(id)anObject
{
    _object = anObject;
}
@end

Wadą tego rodzaju implementacji jest to, że obiekt nie działa jako zmienna instancji, ale raczej jako zmienna klasy. Nie można również przypisywać atrybutów właściwości (takich jak używane w obiektach powiązanych, takich jak OBJC_ASSOCIATION_RETAIN_NONATOMIC)

kernix
źródło
Pytanie dotyczy zmiennej instancji. Twoje rozwiązanie jest jak Lorean
hfossli
1
Naprawdę?? Czy wiesz, co robisz? Utworzyłeś parę metod getter / setter, aby uzyskać dostęp do zmiennej wewnętrznej, która jest niezależna dla pliku, ALE obiektu klasy, to znaczy, jeśli przydzielisz dwa obiekty klasy UIAlertView, ich wartości obiektów są takie same!
Itachi