6

認識されないセレクタ例外をスローする代わりに、メッセージ転送を使用して、実装されていない getter メソッドが 0 を返すようにしたいと考えています。お気に入り

MyClass *r = [[MyClass alloc] init];
NSNumber *n = (NSNumber *)r;
NSLog(@"%d", [n integerValue]); // output 0
NSLog(@"%f", [n doubleValue]); // output 0.00000
NSLog(@"%@", [n stringValue]); // output (null)

だから私はこの例を書いた:

#pragma mark -
#pragma mark Application lifecycle

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    NSNumber *n = (NSNumber *)self;
    NSLog(@"%d", [n integerValue]);
    NSLog(@"%f", [n doubleValue]);
    NSLog(@"%@", [n stringValue]);

    return YES;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *ms = [super methodSignatureForSelector:aSelector];
    if(ms)
        return ms;

    // Q = uint64_t, so it should also works for double which is also 64bit
    return [NSMethodSignature signatureWithObjCTypes:"Q@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    uint64_t ZERO64 = 0;
    [anInvocation setReturnValue:&ZERO64];
}

実機での出力結果は 0, 0.00000, (null) ですが、エミュレータでは 0, NaN, (null) です。

そのため、double 型は期待どおりに動作しません。私の最初の考えは、NSMethodSignature を "d@:" (d は double) に変更することです。

出力結果はデバイスとシミュレータの両方で正しいですが、シミュレータでのみ奇妙なことが起こっています。このコードを実行すると、ある種の CALayer 例外で 6 番目のループでクラッシュします。

#pragma mark -
#pragma mark Application lifecycle

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    for(NSInteger i = 0; i < 100; i++) {
        NSInteger t = [(NSNumber *)self integerValue];

        UIViewController *view = [[UIViewController alloc] init];
        // it always crash on the 6th loop on this line**
        UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:view];
    }

    return YES;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *ms = [super methodSignatureForSelector:aSelector];
    if(ms)
        return ms;

    // we change to return double
    return [NSMethodSignature signatureWithObjCTypes:"d@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    uint64_t ZERO64 = 0;
    [anInvocation setReturnValue:&ZERO64];
}

最初の例でエミュレーターで NaN が返される理由と、2 番目の例で何が起こったのかという 2 つの質問に興味があります。

4

2 に答える 2

2

あなたの最初の質問のために、これは私がシミュレーターで見つけたものです

union {
    double d;
    uint64_t l;
} u;
NSNumber *n = (NSNumber *)self;
u.d = [n doubleValue];
NSLog(@"%f", u.d);  // nan
NSLog(@"%llx",u.l); // fff8000000000000
bzero(&u, sizeof(double));
NSLog(@"%f", u.d);  // 0.000000
NSLog(@"%llx",u.l); // 0

したがって、0.0ではなくNAN(fff8000000000000)が返されることは明らかです。

との違いを詳しく調べるには[NSMethodSignature signatureWithObjCTypes:"d@:"][NSMethodSignature signatureWithObjCTypes:"Q@:"]これを見てください

NSLog(@"%@\n%@", [[NSMethodSignature signatureWithObjCTypes:"Q@:"] debugDescription], [[NSMethodSignature signatureWithObjCTypes:"d@:"] debugDescription]);

出力

<NSMethodSignature: 0x74a0950>
    number of arguments = 2
    frame size = 8
    is special struct return? NO
    return value: -------- -------- -------- --------
        type encoding (Q) 'Q'
        flags {}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
        memory {offset = 0, size = 8}
    argument 0: -------- -------- -------- --------
        type encoding (@) '@'
        flags {isObject}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 4, size adjust = 0}
        memory {offset = 0, size = 4}
    argument 1: -------- -------- -------- --------
        type encoding (:) ':'
        flags {}
        modifiers {}
        frame {offset = 4, offset adjust = 0, size = 4, size adjust = 0}
        memory {offset = 0, size = 4}

<NSMethodSignature: 0x74a1e80>
    number of arguments = 2
    frame size = 8
    is special struct return? NO
    return value: -------- -------- -------- --------
        type encoding (d) 'd'
        flags {isFloat}    <<<<----- this flag should be set if the return value is float type
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
        memory {offset = 0, size = 8}
    argument 0: -------- -------- -------- --------
        type encoding (@) '@'
        flags {isObject}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 4, size adjust = 0}
        memory {offset = 0, size = 4}
    argument 1: -------- -------- -------- --------
        type encoding (:) ':'
        flags {}
        modifiers {}
        frame {offset = 4, offset adjust = 0, size = 4, size adjust = 0}
        memory {offset = 0, size = 4}

flags {isFloat}2番目のメソッドのシグネチャが戻り値を持っていることがわかります。私はx86とAMR、および低レベルのObjCランタイムの専門家ではありません。しかし、CPUはこのフラグを使用して戻り値のタイプを識別したと思います。したがって、x86 CPUに設定しないと、期待されるfloatの戻り値はNANとして解釈されます。


2番目の質問では、64ビットサイズの値を返すようにランタイムに指示したため、スタック上の64ビットサイズのメモリがゼロになっていると思います。ただし、呼び出し元は32ビットの戻りサイズ(NSInteger)を期待しています。したがって、ある種のスタックオーバーフローが発生し、クラッシュにつながります。


NSNullのような作品を作ることを目指して、実際に似たようなものを実装しましたnil

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (signature)
        return signature;

    const Class forwardClasses[] = {[NSNumber class], [NSString class], [NSArray class], [NSOrderedSet class]}; // add new classes if you think the list is not enough

    for (int i = 0; i < sizeof(forwardClasses)/sizeof(Class); i++) {
        Class cls = forwardClasses[i];
        signature = [cls instanceMethodSignatureForSelector:aSelector];
        if (signature) {
            return signature;
        }
    }

    return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSUInteger len = [[anInvocation methodSignature] methodReturnLength];
    char buff[len];
    bzero(buff, len);
    [anInvocation setReturnValue:buff];
}
于 2013-01-13T11:47:45.480 に答える
1

メッセージ転送を使用して、実装されていないgetterメソッドが0を返すようにする場合は、認識されないセレクター例外をスローする代わりに、+resolveInstanceMethodを使用できますか?

NSStringを返す例を次に示します。プリミティブを返すには、それを微調整する必要があります。問題があれば教えてください。

ARCを使用している場合は、ボイド*にもブリッジキャストが必要です。

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSString* name = NSStringFromSelector(sel);

    IMP imp = imp_implementationWithBlock((void*) objc_unretainedPointer(^(id me, BOOL selected)
    {
        return @"Hello!";
    }));
    class_addMethod(self, sel, imp, "@"); //The type '@' is an object. For int use 'i'. Google "obj-c runtime types" 
    return YES;
}

class_addMethodを使用する場合、3番目のパラメーターはタイプコードです。。。それらを解決するための良い方法は、実際の方法を作成し、それを内省することです。クラスの(実際の)セレクターのタイプコードを返すユーティリティは次のとおりです 。https ://github.com/jasperblues/spring-objective-c/blob/master/Source/…–user4042016分前

于 2013-01-13T14:17:39.850 に答える