Почему передача какого-либо селектора объекту Nil ничего не делает, но отправка "недопустимого" селектора в любой NSObject вызывает исключение?
Кто-нибудь знает, почему NextStep/Apple решила принять "удобный метод" ничего не делать при передаче объекта Nil сообщение, но "метод Java" для создания исключения при передаче экземпляром объекта недействительным селектором?
Например,
// This does "nothing"
NSObject *object = Nil;
[object thisDoesNothing];
object = [[NSObject alloc] init];
// This causes an NSInvalidArgumentException to be raised
[object thisThrowsAnException];
Итак, с одной стороны, нам удобнее не проверять Nil (если мы не слишком заботимся о результате вызова метода), но, с другой стороны, нам нужно проверить исключение если наш объект не отвечает на метод?
Если я не уверен, ответит ли объект, я либо должен:
@try {
[object thisThrowsAnException];
} @catch (NSException *e){
// do something different with object, since we can't call thisThrowsAnException
}
Или
if([object respondsToSelector:@selector(thisThrowsAnException)]) {
[object thisThrowsAnException];
}
else {
// do something different with object, since we can't call thisThrowsAnException
}
(Последний, вероятно, лучший способ сделать это, так как если объект является Nil, селектор НЕ будет генерировать исключение, поэтому ваш код может не вести себя так, как вы хотите).
Мой вопрос:
ПОЧЕМУ Apple решила реализовать его таким образом?
Почему бы не вызвать непризнанный вызов селектора, чтобы экземпляр объекта не вызывал исключение?
В качестве альтернативы, почему бы не создать объект Nil исключение, если вы попытаетесь вызвать метод на нем?
Ответы
Ответ 1
Я не могу полностью ответить на ваш вопрос, но я могу ответить на его часть. Objective-C позволяет отправлять сообщение nil
, потому что он делает код более элегантным. Здесь вы можете прочитать об этом проектном решении, и я украду его пример:
Предположим, вы хотите получить последний номер телефона, который набирал какой-то человек на своем служебном телефоне. Если вы не можете отправлять сообщения на nil
, вы должны написать это следующим образом:
Office *office = [somePerson office];
// Person might not have an office, so check it...
if (office) {
Telephone *phone = [office telephone];
// The office might not have a telephone, so check it...
if (phone) {
NSString *lastNumberDialed = [phone lastNumberDialed];
// The phone might be brand new, so there might be no last-dialed-number...
if (lastNumberDialed) {
// Use the number, for example...
[myTextField setText:lastNumberDialed];
}
}
}
Теперь предположим, что вы можете отправлять сообщения на nil
(и всегда получать nil
назад):
NSString *lastNumberDialed = [[[somePerson office] telephone] lastNumberDialed];
if (lastNumberDialed) {
[myTextField setText:lastNumberDialed];
}
Что касается того, почему отправка непризнанного селектора объекту вызывает исключение: я не знаю точно. Я подозреваю, что это более распространенное явление, чтобы быть ошибкой, чем быть безвредным. В моем коде я хочу, чтобы незарегистрированный селектор молча игнорировался, когда мне нужно отправить необязательное сообщение протокола (например, отправить необязательное сообщение делегату). Поэтому я хочу, чтобы система рассматривала его как ошибку и позволяла мне быть явным в относительно редком случае, когда я не хочу, чтобы это была ошибка.
Обратите внимание, что вы можете в какой-то мере изменить способ использования непризнанных селекторов в своих классах несколькими способами. Взгляните на методы forwardingTargetForSelector:
, forwardInvocation:
, doesNotRecognizeSelector:
и resolveInstanceMethod:
NSObject
.
Ответ 2
Из документации хорошего документа:
В Objective-C, действительно, нужно отправить сообщение в nil-it просто не имеет эффект во время выполнения.
Что касается другой проблемы непризнанного поведения селектора, старый файл реализации NSObject (из библиотеки MySTEP) показывает, что виновником является метод NSObject -doesNotRecognizeSelector:
, который выглядит несколько следующим образом:
- (void) doesNotRecognizeSelector:(SEL)aSelector
{
[NSException raise:NSInvalidArgumentException
format:@"NSObject %@[%@ %@]: selector not recognized",
object_is_instance(self)[email protected]"-":@"+",
NSStringFromClass([self class]),
NSStringFromSelector(aSelector)];
}
Это означает, что методы ObjC можно по-настоящему переделать так, чтобы на самом деле им не приходилось поднимать ошибку. Это означает, что решение было совершенно произвольным, так же как и решение переключиться на сообщения "прием пищи" до нуля. Подвиг, который можно выполнить с помощью метода swozling NSObject (совершенно опасного, поскольку он будет поднимать EXC_BAD_ACCESS или EXC_I386_BPT на Mac, но по крайней мере он не вызывает исключения)
void Swizzle(Class c, SEL orig, SEL new)
{
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, new);
if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
}
-(void)example:(id)sender {
Swizzle([NSObject class], @selector(doesNotRecognizeSelector:), @selector(description));
[self performSelector:@selector(unrecog)];
}
Категория:
@implementation NSObject (NoExceptionMessaging)
-(void)doesNotRecognizeSelector:(SEL)aSelector {
NSLog(@"I've got them good ol' no exception blues.");
}
@end
Ответ 3
Для всех развлечений, из-за обсуждения у CodaFi и у меня было, здесь быстро взломанный способ нормально нормально переписывать сообщения и вернуть их nil
:
@interface EaterOfBadMessages : NSObject
@end
@implementation EaterOfBadMessages
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature * sig = [super methodSignatureForSelector:aSelector];
if( !sig ){
sig = [NSMethodSignature signatureWithObjCTypes:"@@:"];
}
return sig;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
id nilPtr = nil;
[anInvocation setReturnValue:&nilPtr];
}
@end
int main(int argc, const char * argv[])
{
@autoreleasepool {
EaterOfBadMessages * e = [[EaterOfBadMessages alloc] init];
// Of course, pre-ARC you could write [e chewOnThis]
NSLog(@"-[EaterOfBadMessages chewOnThis]: %@", [e performSelector:@selector(chewOnThis)]);
}
return 0;
}
Пожалуйста, не используйте это в реальной жизни.