it-swarm.dev

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

Ponieważ nie mogę utworzyć zsyntetyzowanej właściwości w kategorii w Objective-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ę wiele rzeczy, aby obliczyć wynik. Zwykle przy użyciu zsyntetyzowanej właściwości przechowuję wartość w teście IVar _test przy pierwszym wywołaniu metody i zwracam ten IVar następnym razem. Jak mogę zoptymalizować powyższy kod?

117
dhrm

Metoda @ lorean będzie działać (uwaga: odpowiedź jest teraz usunięta), ale miałbyś tylko jedno miejsce do przechowywania. Więc jeśli chciałbyś użyć tego w wielu instancjach i mieć dla każdej instancji obliczoną odrębną wartość, to nie zadziałałoby.

Na szczęście środowisko wykonawcze Objective-C ma nazywa się Associated Objects , które może 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
122
Dave DeLong

. Plik h

@interface NSObject (LaserUnicorn)

@property (nonatomic, strong) LaserUnicorn *laserUnicorn;

@end

. m-file

#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

Tak jak normalna właściwość - dostępna z notacją kropkową

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

Łatwiejsza składnia

Alternatywnie możesz użyć @selector(nameOfGetter) zamiast tworzyć statyczny klucz wskaźnika tak:

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

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

Aby uzyskać więcej informacji, patrz https://stackoverflow.com/a/16020927/202451

169
hfossli

Podana odpowiedź działa świetnie, a moja propozycja jest tylko jej rozszerzeniem, która pozwala uniknąć pisania zbyt dużej liczby kodów źródłowych.

Aby uniknąć wielokrotnego pisania metod pobierających i ustawiających właściwości kategorii, w odpowiedzi wprowadzono makra. Ponadto te makra ułatwiają korzystanie z właściwości typu pierwotnego, takich jak int lub BOOL.

Tradycyjne podejście bez makr

Tradycyjnie definiujesz właściwość kategorii jak

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

Następnie musisz zaimplementować metodę pobierającą i ustawiającą za pomocą powiązany obiekt i pobierz selektor jako klucz ( 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, zamiast tego napiszesz:

@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_SET dodaje moduł pobierający i ustawiający dla danej właściwości. Właściwości tylko do odczytu lub tylko do zapisu będą używać CATEGORY_PROPERTY_GET i CATEGORY_PROPERTY_SET makro odpowiednio.

Typy pierwotne wymagają nieco większej uwagi

Ponieważ typy pierwotne nie są obiektami, powyższe makra zawierają przykład użycia unsigned int jako typ właściwości. Robi to poprzez zawijanie wartości całkowitej do obiektu NSNumber. Jego użycie jest analogiczne do poprzedniego przykładu:

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

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

Zgodnie z tym wzorcem możesz po prostu dodać więcej makr, aby również obsługiwać signed int, BOOL itd.

Ograniczenia

  1. Wszystkie makra używają OBJC_ASSOCIATION_RETAIN_NONATOMIC domyślnie.

  2. IDE, takie jak Kod aplikacji obecnie nie rozpoznają nazwy osoby ustawiającej podczas refaktoryzacji nazwy właściwości. Musisz zmienić nazwę samodzielnie.

31
Lars Blumberg

Wystarczy użyć biblioteki libextobjc :

plik h:

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

plik m:

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

@synthesizeAssociation (MyClass, test);

@end

Więcej o @synthesizeAssociation

7
Mansurov Ruslan

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

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
3
Rikco