Objective-C: Kategorideki özellik / örnek değişkeni


122

Objective-C'de bir Kategori içinde sentezlenmiş bir özellik oluşturamadığım için, aşağıdaki kodu nasıl optimize edeceğimi bilmiyorum:

@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

Test-yöntem çalışma zamanı üzerinde birden çok kez denir ve ben sonucu hesaplamak için bir sürü şey yapıyorum. Normalde sentezlenmiş bir özellik kullanarak, değeri yöntem ilk kez çağrıldığında bir IVar _test içinde depolar ve bir dahaki sefere bu IVar'ı döndürürüm. Yukarıdaki kodu nasıl optimize edebilirim?


2
Neden normalde yaptığınız şeyi yalnızca bir kategori yerine yapmıyorsunuz, özelliği bir Sınıfım temel sınıfa eklemiyorsunuz? Ve bunu daha da ileri götürmek için, ağır işlerinizi arka planda gerçekleştirin ve işlemin bir bildirimi ateşlemesini sağlayın veya işlem tamamlandığında MyClass için bir işleyici çağırın.
Jeremy

4
MyClass, Core Data'dan oluşturulmuş bir sınıftır. Oluşturulan sınıfın içindeki özel nesne kodum dışında, sınıfı Çekirdek Verilerimden yeniden oluşturursam kaybolur. Bu nedenle bir kategori kullanıyorum.
dhrm

1
Belki başlığa en uygun soruyu kabul edersiniz? ("Kategorideki Emlak")
hfossli

Neden bir alt sınıf oluşturmuyorsunuz?
Scott Zhu

Yanıtlar:


124

@ lorean'ın yöntemi işe yarayacaktır (not: yanıt artık silinmiştir) , ancak yalnızca tek bir depolama yuvanız olacaktır . Dolayısıyla, bunu birden çok örnekte kullanmak ve her bir örneğin farklı bir değer hesaplamasını istiyorsanız, bu işe yaramaz.

Neyse ki, Objective-C çalışma zamanı, tam olarak istediğiniz şeyi yapabilen Associated Objects adında bir şeye sahiptir :

#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


5
@DaveDeLong Bu çözüm için teşekkür ederiz! Çok güzel değil ama işe yarıyor :)
dhrm

42
"çok güzel değil" mi? Bu, Objective-C'nin güzelliğidir! ;)
Dave DeLong

6
Mükemmel cevap! Statik değişkeni @selector(test)burada açıklandığı gibi anahtar olarak kullanarak bile kurtarabilirsiniz : stackoverflow.com/questions/16020918/…
Gabriele Petronella

1
@HotFudgeSunday - hızlı çalıştığını düşünün: stackoverflow.com/questions/24133058/…
Scott Corscadden

174

.h dosyası

@interface NSObject (LaserUnicorn)

@property (nonatomic, strong) LaserUnicorn *laserUnicorn;

@end

.m dosyası

#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

Normal bir mülk gibi - noktalı gösterimle erişilebilir

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

Daha kolay sözdizimi

Alternatif olarak @selector(nameOfGetter), aşağıdaki gibi statik bir işaretçi anahtarı oluşturmak yerine kullanabilirsiniz :

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

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

Daha fazla ayrıntı için bkz. Https://stackoverflow.com/a/16020927/202451


4
İyi makale. Dikkat edilmesi gereken bir nokta, makaledeki bir güncellemedir. 22 Aralık 2011 Güncellemesi: İlişkilendirme anahtarının bir dize değil, void işaretçi void * anahtarı olduğuna dikkat etmek önemlidir. Bu, ilişkili bir başvuruyu alırken, çalışma zamanına tam olarak aynı işaretçiyi iletmeniz gerektiği anlamına gelir. Anahtar olarak bir C dizesi kullandıysanız, ardından dizeyi bellekte başka bir yere kopyaladıysanız ve işaretçiyi kopyalanan dizeye bir anahtar olarak ileterek ilişkili referansa erişmeye çalışsanız, amaçlandığı gibi çalışmaz.
Mr Rogers

4
Gerçekten ihtiyacınız yok @dynamic objectTag;. @dynamicayarlayıcı ve alıcı başka bir yerde oluşturulacağı anlamına gelir, ancak bu durumda bunlar tam burada uygulanır.
IluTov

@NSAddict doğru! Sabit!
hfossli

1
Manuel bellek yönetimi için laserUnicorns'un dealloc'una ne dersiniz? Bu bir hafıza sızıntısı mı?
Manny

Otomatik olarak serbest bırakılır stackoverflow.com/questions/6039309/…
hfossli

32

Verilen cevap harika çalışıyor ve benim önerim çok fazla standart kod yazmaktan kaçınan sadece bir uzantısı.

Kategori özellikleri için tekrar tekrar alıcı ve ayarlayıcı yöntemleri yazmaktan kaçınmak için bu cevap makroları tanıtmaktadır. Ayrıca bu makrolar, intveya gibi ilkel tür özelliklerinin kullanımını kolaylaştırır BOOL.

Makro içermeyen geleneksel yaklaşım

Geleneksel olarak aşağıdaki gibi bir kategori özelliği tanımlarsınız

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

Ardından, ilişkili bir nesneyi ve anahtar olarak alma seçiciyi kullanarak bir alıcı ve ayarlayıcı yöntemi uygulamanız gerekir ( orijinal yanıta bakın ):

#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

Benim önerdiğim yaklaşım

Şimdi, bunun yerine bir makro kullanarak yazacaksınız:

@implementation MyClass (Category)

CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)

@end

Makrolar şu şekilde tanımlanır:

#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, verilen özellik için bir alıcı ve ayarlayıcı ekler. Salt okunur veya salt yazılır özellikler sırasıyla CATEGORY_PROPERTY_GETve CATEGORY_PROPERTY_SETmakroyu kullanır .

İlkel türlerin biraz daha dikkat edilmesi gerekiyor

İlkel türler nesne olmadığından, yukarıdaki makrolar unsigned intözelliğin türü olarak kullanmak için bir örnek içerir . Bunu tamsayı değerini bir NSNumbernesneye sararak yapar . Dolayısıyla kullanımı önceki örneğe benzer:

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

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

Bu kalıbı takip ederek signed int, destek BOOLvb. İçin daha fazla makro ekleyebilirsiniz .

Sınırlamalar

  1. Tüm makrolar OBJC_ASSOCIATION_RETAIN_NONATOMICvarsayılan olarak kullanıyor .

  2. Uygulama Kodu gibi IDE'ler, mülkün adını yeniden düzenlerken şu anda ayarlayıcının adını tanımıyor. Kendi başınıza yeniden adlandırmanız gerekir.


1
#import <objc/runtime.h>Aksi takdirde .m dosyasını kategoriye almayı unutmayın . derleme zamanı hatası: C99'da 'objc_getAssociatedObject' fonksiyonunun örtük bildirimi geçersiz geliyor. stackoverflow.com/questions/9408934/…
Sathe_Nagaraja


3

Yalnızca iOS 9 ile test edilmiştir Örnek: UINavigationBar'a (Kategori) bir UIView özelliği ekleme

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

-2

Kullanılmayan, belki de daha kolay olan başka bir olası çözüm, Associated Objectskategori uygulama dosyasında aşağıdaki gibi bir değişken bildirmektir:

@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

Bu tür bir uygulamanın dezavantajı, nesnenin bir örnek değişkeni olarak değil, bir sınıf değişkeni olarak işlev görmesidir. Ayrıca, özellik öznitelikleri atanamaz (örneğin OBJC_ASSOCIATION_RETAIN_NONATOMIC gibi İlişkili Nesnelerde kullanılanlar gibi)


Soru, örnek değişkeni hakkında sorular sorar. Çözümünüz tıpkı Lorean gibi
hfossli

1
Gerçekten mi?? Ne yaptığını biliyor muydun Bir dosya AMA bir sınıf nesnesi için bağımsız olan bir dahili değişkene erişmek için bir çift alıcı / ayarlayıcı yöntemi oluşturdunuz, yani, iki UIAlertView sınıf nesnesi ayırırsanız, bunların nesne değerleri aynıdır!
Itachi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.