2009-09-10 3 views
6

Mise à jour:Comment "fausser" les ivars dans une catégorie Obj-C (iPhone)

iPhone OS 3.1 possède des objets associés. Cependant, le simulateur de l'iPhone ne fonctionne pas. Si vous voulez tester le code des objets associés dans le simulateur, vous devez déposer un bug.

Voir ma question SO here.

rdar: // 7477326


Snow Leopard objets est maintenant associé.

Existe-t-il un moyen d'accomplir quelque chose de similaire sans les objets associés? (Spécifiquement pour l'iPhone.)

Je suis assez sûr d'avoir vu quelque chose comme ça il y a quelque temps, mais je ne me souviens plus où. Quelque chose à propos de transformer un objet dans un conteneur KVC.

+0

Oui, il est possible d'émuler l'extension ivars par catégorie sans aucune routine d'exécution supplémentaire. [Ici] (http://stackoverflow.com/questions/4887004/category-like-extension-for-instance-variables/4899521#4899521) est ma description comment. –

Répondre

13

objc_setAssociatedObject() et les amis ont été ajoutés à l'iPhone OS 3.1, donc si vous avez la possibilité de ciblage seulement 3.1+ périphériques vous pouvez en effet faire exactement la même chose que sur Snow Leopard ...

Si vous vous ne pouvez pas créer un dictionnaire statique des associations et des singes patch NSObjects méthode dealloc. Pour diverses raisons techniques, cette solution ne peut pas fonctionner correctement en présence de GC (ce qui explique pourquoi Apple a ajouté les trucs d'association), mais puisque l'iPhone ne supporte pas le GC, cela n'est pas un problème. Si vous venez de commencer à travailler sur ce projet, je recommande fortement d'utiliser les fonctions d'exécution et de cibler 3.1 plus, mais si ce n'est pas une option, voici un exemple de la façon dont vous le faites.

LGAssociativeStorage.h:

#import <pthread.h> 
#import <Foundation/Foundation.h> 

@interface NSObject (LGAssociativeStorage) 
@property (retain) id associatedObject; 
@end 

LGAssociativeStorage.mm

#import <objc/runtime.h> 
#import "LGAssociativeStorage.h" 

/* We are using STL containers because: 
    1) Using Objective C containers can cause deallocs which cause recursion issues 
    2) STL containers are high perf containers that don't introduce external code dependencies 
    Ideally one could include a thread safe map implementation, but I don't need one currently 
*/ 

#include <map> 

typedef std::map<id,id> idMap_t; 
typedef std::pair<id,id> idPair_t; 

static NSMutableDictionary * data = nil; 
static pthread_mutex_t data_lock = PTHREAD_MUTEX_INITIALIZER; 
static IMP gOriginalNSObjectDealloc = nil; 
static idMap_t associatedObjectMap; 

static 
void removeAssociatedObjectFromMap(id self) { 
    idMap_t::iterator iter = associatedObjectMap.find(self); 
    if(iter != associatedObjectMap.end()) { 
     [iter->second release]; 
     associatedObjectMap.erase(iter); 
    } 
} 

static 
id newNSObjectDealloc(id self, SEL deallocSelector, ...) { 
    pthread_mutex_lock(&data_lock); 
    removeAssociatedObjectFromMap(self); 
    pthread_mutex_unlock(&data_lock); 
    return gOriginalNSObjectDealloc(self, deallocSelector); 
} 

static void initIfNecessary(void) { 
    if (!data) { 
     data = [[NSMutableDictionary alloc] init]; 

     // The below line of code is abusive... in the future the Objective C runtime will use it as evidence 
     // that I am an unfit software engineer and take custody of all my code 
     gOriginalNSObjectDealloc = class_replaceMethod([NSObject class], @selector(dealloc), newNSObjectDealloc, "[email protected]:"); 
    } 
} 



@implementation NSObject (LGAssociativeStorage) 

- (id) associatedObject { 
    id retval = nil; 
    pthread_mutex_lock(&data_lock); 
    initIfNecessary(); 
    idMap_t::iterator iter = associatedObjectMap.find(self); 
    if(iter != associatedObjectMap.end()) { 
     retval = iter->second; 
    } 
    pthread_mutex_unlock(&data_lock); 
    return retval; 
} 

- (void) setAssociatedObject:(id)object_ { 
    pthread_mutex_lock(&data_lock); 
    initIfNecessary(); 
    removeAssociatedObjectFromMap(self); 
    [object_ retain]; 
    associatedObjectMap.insert(idPair_t(self, object_)); 
    pthread_mutex_unlock(&data_lock); 
} 

@end 
+0

Merci, aurait dû lire mes notes de version. Échanger l'implémentation de dealloc de NSObject pourrait définitivement vous interdire quelque part. –

3

Vous pouvez toujours les stocker dans un singleton.

2

Il n'y a pas de bons moyens de le faire dans une catégorie générique.

Vous pouvez facilement ajouter des données pour un objet en disposant d'un NSMutableDictionary global mappé à partir de n'importe quel objet NSObject arbitraire vers les données de votre choix. Le problème est qu'il n'y a aucun moyen de savoir quand l'objet est désalloué, donc vous ne pouvez pas dire (en général) quand les données sont périmées. La seule façon générique de résoudre ceci est d'utiliser la méthode swizzling pour remplacer la méthode dealloc de NSObject pour signaler l'abandon de l'objet et libérer les données associées. Je suis sûr que quelqu'un a fait cela, mais c'est un tel hack hideux qu'il serait très difficile de recommander comme un appropach valide. Maintenant, si vos objets dans les questions ont un autre moyen de contrôler le cycle de vie (par exemple, un hook de désallocation, comme une méthode déléguée objectWillClose), alors vous pouvez vous y connecter pour libérer vos données associées et rendrait la technique assez simple et légitime.

+0

iPhone n'a évidemment pas GC, mais ne serait-il pas possible d'utiliser un NSMapTable avec des touches faibles à la place? http://developer.apple.com/documentation/Cocoa/Reference/NSMapTable_class/ –

+0

En fait, vous ne pouvez pas parce qu'en général la clé de cette table est la seule chose qui enracine l'objet associé, donc si elle est faible, alors elle être immédiatement éligible pour être GCed et remis à zéro après l'avoir placé une fois qu'il n'est plus directement enraciné par la pile. –

0

Je vais ajouter une réponse.

J'ai trouvé l'original blog post, il était de Steve Degutis.

Il consiste essentiellement à remplacer les méthodes de NSObject pour valueForUndefinedKey:, setValue:ForUndefinedKey: et dealloc. Ensuite, en utilisant un dictionnaire statique pour stocker les clés non définies.

À peu près aussi méchant et amusant que la solution de Louis.

0

Malgré les problèmes de concurrence, pourquoi ne pas simplement utiliser des variables globales? Même en utilisant les méthodes objc_set/get AssociatedObject(), vous ne passez pas une adresse de variable statique "globale", auquel cas vous avez encore des problèmes de concurrence, n'est-ce pas?

Questions connexes