Ответ 1
Достаточно просто перевести это поведение на произвольные классы. Я представляю с уверенностью, но без гарантии, следующий код, который вы должны использовать, чтобы добавить реализацию исключения valueForUndefinedKey:
для любого класса с одной, централизованной линией кода для каждого класса при запуске приложения. Если вы хотите сохранить еще больше кода, вы можете сделать все классы, которые хотели бы, чтобы это поведение наследовалось от общего подкласса NSManagedObject, а затем применить его к этому общему классу, и все ваши подклассы наследуют поведение. Подробнее после, но вот код:
Заголовок (NSObject+ValueForUndefinedKeyAdding.h
):
@interface NSObject (ValueForUndefinedKeyAdding)
+ (void)addCustomValueForUndefinedKeyImplementation: (IMP)handler;
@end
Реализация (NSObject+ValueForUndefinedKeyAdding.m
):
#import "NSObject+ValueForUndefinedKeyAdding.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation NSObject (ValueForUndefinedKeyAdding)
+ (void)addCustomValueForUndefinedKeyImplementation: (IMP)handler
{
Class clazz = self;
if (clazz == nil)
return;
if (clazz == [NSObject class] || clazz == [NSManagedObject class])
{
NSLog(@"Don't try to do this to %@; Really.", NSStringFromClass(clazz));
return;
}
SEL vfuk = @selector(valueForUndefinedKey:);
@synchronized([NSObject class])
{
Method nsoMethod = class_getInstanceMethod([NSObject class], vfuk);
Method nsmoMethod = class_getInstanceMethod([NSManagedObject class], vfuk);
Method origMethod = class_getInstanceMethod(clazz, vfuk);
if (origMethod != nsoMethod && origMethod != nsmoMethod)
{
NSLog(@"%@ already has a custom %@ implementation. Replacing that would likely break stuff.",
NSStringFromClass(clazz), NSStringFromSelector(vfuk));
return;
}
if(!class_addMethod(clazz, vfuk, handler, method_getTypeEncoding(nsoMethod)))
{
NSLog(@"Could not add valueForUndefinedKey: method to class: %@", NSStringFromClass(clazz));
}
}
}
@end
Затем, в вашем классе AppDelegate (или действительно в любом месте, но, вероятно, имеет смысл разместить его где-то в центре, поэтому вы знаете, где его найти, когда вы хотите добавить или удалить классы из списка) поместите этот код, который добавляет этот функциональность для классов по вашему выбору во время запуска:
#import "MyAppDelegate.h"
#import "NSObject+ValueForUndefinedKeyAdding.h"
#import "MyOtherClass1.h"
#import "MyOtherClass2.h"
#import "MyOtherClass3.h"
static id ExceptionlessVFUKIMP(id self, SEL cmd, NSString* inKey)
{
NSLog(@"Not throwing an exception for undefined key: %@ on instance of %@", inKey, [self class]);
return nil;
}
@implementation MyAppDelegate
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[MyOtherClass1 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
[MyOtherClass2 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
[MyOtherClass3 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
});
}
// ... rest of app delegate class ...
@end
Что я делаю здесь, это добавить пользовательскую реализацию для valueForUndefinedKey:
к классам MyOtherClass1, 2 и 3. Реализация примера, которую я предоставил только NSLog
, и возвращает нуль, но вы можете изменить реализацию, чтобы сделать независимо от того, что вы хотите, изменив код в ExceptionlessVFUKIMP
. Если вы удалите NSLog
и просто верните нуль, я подозреваю, что вы получите то, что хотите, в зависимости от вашего вопроса.
Этот код НИКОГДА не проверяет методы, он добавляет только один, если он не существует. Я поставил проверки, чтобы это не использовалось на классах, у которых уже есть свои собственные реализации valueForUndefinedKey:
, потому что если кто-то поместит этот метод в свой класс, ожидается ожидание того, что он будет продолжать вызываться. Также обратите внимание, что может быть код AppKit, который EXPECTS исключает из реализации NSObject/NSManagedObject. (Я точно этого не знаю, но это возможность рассмотреть.)
Несколько примечаний:
NSManagedObject
предоставляет настраиваемую реализацию для valueForUndefinedKey:
. Выполняя свою сборку в отладчике, все, что она делает, это бросить примерно одно и то же исключение с немного другим сообщением. Основываясь на этом 5-минутном исследовании отладчика, я чувствую, что это должно быть безопасно использовать это с подклассами NSManagedObject
, но я не уверен на 100% - там может быть какое-то поведение, которое я не поймал. Осторожно.
Также, если вы используете этот подход, у вас нет хорошего способа узнать, возвращается ли valueForKey:
nil
, потому что keyPath действителен и состояние оказалось nil
, или если он возвращает nil
, потому что keyPath недействителен, а обработчик с привитой обработкой возвращает nil. Для этого вам нужно будет сделать что-то другое и конкретную реализацию. (Возможно, верните [NSNull null]
или какое-либо другое значение дозорного знака или установите некоторый флаг в локальном хранилище потоков, который вы могли бы проверить, но на данный момент это действительно намного проще, чем @try/@catch
?) Просто об этом нужно знать.
Кажется, это работает очень хорошо для меня; Надеюсь, это вам полезно.