Unikaj dodatkowych zmiennych statycznych dla powiązanych kluczy obiektów

Podczas korzystania z powiązanych obiektów, funkcji runtime Objective - C dostępnej od systemów iOS 4 i OSX 10.6, konieczne jest zdefiniowanie klucza do przechowywania i pobierania obiektu w czasie wykonywania.

Typowe użycie to definiowanie klucza w następujący sposób

static char const * const ObjectTagKey = "ObjectTag";

A następnie użyj jest do przechowywania obiektu

objc_setAssociatedObject(self, ObjectTagKey, newObjectTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

I odzyskać

objc_getAssociatedObject(self, ObjectTagKey);

(Przykład wzięty przez http://oleb.net/blog/2011/05/faking-ivars-in-objc-categories-with-associative-references/)

Jest jest jakiś sposób na zdefiniowanie powiązanego klucza obiektu, który nie wymaga deklaracji dodatkowych zmiennych?

Author: Gabriele Petronella, 2013-04-15

3 answers

Zgodnie z tym wpisem na blogu Erica Sadun (której kredyty idą do Gwynne Raskind ), jest.

objc_getAssociatedObject i objc_getAssociatedObject wymagają klucza do przechowywania obiektu. Taki klucz musi być stałym wskaźnikiem void. Więc w końcu potrzebujemy tylko stałego adresu, który pozostaje stały w czasie.

Okazuje się, że implementacja @selector zapewnia dokładnie to, czego potrzebujemy, ponieważ używa stałych adresów.

Możemy więc po prostu pozbyć się kluczowej deklaracji i wystarczy użyć adresu selektora naszego obiektu.

Więc jeśli kojarzysz w runtime właściwość taką jak

@property (nonatomic, retain) id anAssociatedObject;

Możemy zapewnić dynamiczne implementacje dla gettera / settera, które wyglądają jak

- (void)setAnAssociatedObject:(id)newAssociatedObject {
     objc_setAssociatedObject(self, @selector(anAssociatedObject), newAssociatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)anAssociatedObject {
    return objc_getAssociatedObject(self, @selector(anAssociatedObject));
}

Bardzo schludne i zdecydowanie czystsze niż zdefiniowanie dodatkowego klucza zmiennej statycznej dla każdego powiązanego obiektu.

Czy to bezpieczne?

Ponieważ jest to zależne od implementacji, uzasadnione pytanie brzmi: Czy łatwo się złamie? Cytowanie wpisu na blogu

Apple prawdopodobnie musiałoby zaimplementować zupełnie nowe ABI, aby tak się stało.]}
Jeśli weźmiemy te słowa za prawdziwe, to jest to w miarę bezpieczne.
 48
Author: Gabriele Petronella,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-18 20:21:41

Jeśli potrzebujesz dostępu do klucza spoza zakresu pojedynczej metody, dobrym wzorcem dla tego, co prowadzi do bardziej czytelnego kodu, jest utworzenie wskaźnika, który po prostu wskazuje na swój własny adres w stosie. Na przykład:

static void const *MyAssocKey = &MyAssocKey;

Jeśli tylko potrzebujesz dostępu z zakresu jednej metody, możesz po prostu użyć _cmd, która jest gwarantowana jako unikalna. Na przykład:

objc_setAssociatedObject(obj, _cmd, associatedObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 6
Author: Will Pragnell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-31 12:53:50

Lekką wariacją na temat omawianego pomysłu @ Gabriele Petronella jest przypisanie słownika do każdego obiektu:

//NSObject+ADDLAssociatedDictionary.h

#import <Foundation/Foundation.h>

@interface NSObject (ADDLAssociatedDictionary)
- (void)addl_setAssociatedObject:(id)object forKey:(id<NSCopying>)key;
- (id)addl_associatedObjectForKey:(id<NSCopying>)key;
@end

//NSObject+ADDLAssociatedDictionary.m

#import <objc/runtime.h>

@interface NSObject (ADDLAssociatedDictionaryInternal)
- (NSMutableDictionary *)addl_associatedDictionary;
@end

@implementation NSObject (ADDLAssociatedDictionary)

- (void)addl_setAssociatedObject:(id)object forKey:(id<NSCopying>)key
{
    if (object) {
        self.addl_associatedDictionary[key] = object;
    } else {
        [self.addl_associatedDictionary removeObjectForKey:key];
    }
}
- (id)addl_associatedObjectForKey:(id<NSCopying>)key
{
    return self.addl_associatedDictionary[key];
}

@end

@implementation NSObject (ADDLAssociatedDictionaryInternal)
const char addl_associatedDictionaryAssociatedObjectKey;
- (NSMutableDictionary *)addl_associatedDictionaryPrimitive
{
    return objc_getAssociatedObject(self, &addl_associatedDictionaryAssociatedObjectKey);
}
- (void)addl_setAssociatedDictionaryPrimitive:(NSMutableDictionary *)associatedDictionary
{
    objc_setAssociatedObject(self, &addl_associatedDictionaryAssociatedObjectKey, associatedDictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSMutableDictionary *)addl_generateAssociatedDictionary
{
    NSMutableDictionary *associatedDictionary = [[NSMutableDictionary alloc] init];
    [self addl_setAssociatedDictionaryPrimitive:associatedDictionary];
    return associatedDictionary;
}

- (NSMutableDictionary *)addl_associatedDictionary
{
    NSMutableDictionary *res = nil;

    @synchronized(self) {
        if (!(res = [self addl_associatedDictionaryPrimitive])) {
            res = [self addl_generateAssociatedDictionary];
        }
    }

    return res;
}
@end

Następnie w naszej kategorii na jakiejś podklasie wywodzącej się z NSObject

//Derived+Additions.h

#import "Derived.h"

@interface Derived (Additions)
@property (nonatomic) id anAssociatedObject;
@end

//Derived+Additions.m

#import "NSObject+ADDLAssociatedDictionary.h"

@implementation Derived (Additions)
- (void)setAnAssociatedObject:(id)anAssociatedObject
{
    [self addl_setAssociatedObject:anAssociatedObject forKey:NSStringFromSelector(@selector(anAssociatedObject))];
}
- (id)anAssociatedObject
{
    return [self addl_associatedObjectForKey:NSStringFromSelector(@selector(anAssociatedObject))];
}
@end

Jedną z zalet skojarzonego podejścia słownikowego jest zwiększona elastyczność, która wynika z możliwości ustawiania obiektów dla kluczy generowanych w czasie wykonywania, nie wspominając o znacznie ładniejszej składni.

A korzyść szczególna w stosowaniu

NSStringFromSelector(@selector(anAssociatedObject))

Czy to NSStringFromSelector jest gwarantowane, aby dać NSString reprezentację selektora, który zawsze będzie akceptowalnym kluczem słownikowym. W związku z tym nie musimy się wcale martwić (choć nie sądzę, że jest to uzasadniona troska) o zmiany ABI.

 5
Author: Nate Chandler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-16 20:52:59