13

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 オブジェクトでメソッドを呼び出そうとした場合に、Nil オブジェクトで例外を発生させてみませんか?

4

3 に答える 3

13

あなたの質問に完全には答えられませんが、その一部には答えることができます。nilObjective-C を使用すると、コードがより洗練されたものになるため 、メッセージを送信できます。この設計上の決定についてはこちらで読むことができます。その例を盗みます。

ある人がオフィスの電話で最後にダイヤルした電話番号を取得したいとします。にメッセージを送信できない場合は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

于 2012-07-17T21:06:28.133 に答える
5

古き良きドキュメントから:

Objective-C では、メッセージを nil に送信することは有効です。実行時にはまったく効果がありません。

認識されないセレクター動作の他の問題については、( MySTEP ライブラリーからの) NSObject の古い実装ファイルは、犯人が NSObject メソッド-doesNotRecognizeSelector:であることを示しています。

- (void) doesNotRecognizeSelector:(SEL)aSelector
{
    [NSException raise:NSInvalidArgumentException
                format:@"NSObject %@[%@ %@]: selector not recognized", 
                        object_is_instance(self)?@"-":@"+",
                        NSStringFromClass([self class]), 
                        NSStringFromSelector(aSelector)];
}

つまり、実際にエラーを発生させる必要がないように、ObjC メソッドをうまくいじることができます。つまり、「メソッドを食べる」メッセージを nil に切り替えるという決定と同じように、決定は完全に恣意的でした。NSObject をスウィズリングするメソッドを介して実行できる偉業 (Mac では EXC_BAD_ACCESS または EXC_I386_BPT が発生するため、非常に危険ですが、少なくとも例外は発生しません)

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
于 2012-07-17T21:18:04.350 に答える
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;
}

実生活では使用しないでください。

于 2012-07-17T22:12:44.567 に答える