Cómo crear una categoría con una propiedad para NSNumber objects

Por diversos motivos, necesito asociar un formatting de número pnetworkingeterminado (un NSNumberFormatter ) con los objects NSNumber . Quiero admitir esto incluso para los objects creados fuera de mi control, por lo tanto, en lugar de crear una subclass de NSNumber, he usado una categoría y la funcionalidad del object asociado ObjC para lograr esto:

 @interface NSNumber (defaultNumberFormat) @property (nonatomic,strong) NSNumberFormatter *defaultNumberFormat; @end @implementation NSNumber (defaultNumberFormat) @dynamic defaultNumberFormat; - (void)setDefaultNumberFormat:(NSNumberFormatter *)format { [self willChangeValueForKey:@"defaultNumberFormat"]; objc_setAssociatedObject(self, @selector(defaultNumberFormat), format, OBJC_ASSOCIATION_COPY_NONATOMIC); [self didChangeValueForKey:@"defaultNumberFormat"]; } - (NSNumberFormatter *)defaultNumberFormat { return objc_getAssociatedObject(self, @selector(defaultNumberFormat)); } @end 

Esto funciona bien en una compilation de 32 bits, sin embargo, en iOS7 para objectives de 64 bits, para algunos valores se bloquea con EXC_BAD_ACCESS (código = EXC_I386_GPFLT) en la llamada obj_setAssociatedObject .

Resulta que el motivo es que iOS utiliza pointers labeldos para objectives de 64 bits para objects seleccionados con valores pequeños para mejorar el performance (evita la necesidad de asignación de memory y limpieza de ARC para el object). Esto incluye algunos valores NSString, NSNumber y NSDate. Vea más información aquí y aquí .

Entonces, ¿cómo logras una categoría con una propiedad para NSNumber bajo el modo de 64 bits?

Mi primer bash de resolver esto intentó gestionar la asociación manualmente por exception cuando se detectó un object de puntero labeldo. Esto funciona, pero no fue una solución práctica, ya que pierde todos los beneficios de ARC para el valor asociado (no se limpian cuando el object puntero labeldo ya no está en uso).

En cambio, una solución viable hace que la propiedad sea readonly, luego usa un acceso independiente para transformar el object puntero labeldo en un object normal antes de aplicar el object asociado:

 @interface NSNumber (defaultNumberFormat) @property (nonatomic,strong,readonly) NSNumberFormatter *defaultNumberFormat; - (NSNumber *)applyDefaultNumberFormat:(NSNumberFormatter *)format; @end @implementation NSNumber (defaultNumberFormat) @dynamic defaultNumberFormat; - (NSNumber *)applyDefaultNumberFormat:(NSNumberFormatter *)format { NSNumber *newNumber = self; #ifdef WORKAROUND_IOS_TAGGED_POINTER_ISSUE unsigned long ptrValue = (unsigned long)self; if (ptrValue & 0x1) { // We have a non-aligned pointer - ie a tagged short-cut object stonetworking inside the pointer. objc_setAssociatedObject() is broken for these object types (it will cause memory access faults). // Transform ourselves into a non-tagged object. newNumber = [NSDecimalNumber decimalNumberWithDecimal:[self decimalValue]]; ptrValue = (unsigned long)newNumber; if (ptrValue & 0x1 || ![newNumber isKindOfClass:[NSNumber class]]) { NSLog(@"Failed to create a non-tagged NSNumber for number: %@ - hence default number format not set", [self description]); return self; } } #endif [newNumber willChangeValueForKey:@"defaultNumberFormat"]; objc_setAssociatedObject(newNumber, @selector(defaultNumberFormat), format, OBJC_ASSOCIATION_COPY_NONATOMIC); [newNumber didChangeValueForKey:@"defaultNumberFormat"]; return newNumber; } - (NSNumberFormatter *)defaultNumberFormat { #ifdef WORKAROUND_IOS_TAGGED_POINTER_ISSUE unsigned long ptrValue = (unsigned long)self; if (ptrValue & 0x1) return nil; #endif return objc_getAssociatedObject(self, @selector(defaultNumberFormat)); } @end