Cocoaのプリミティブ型でperformSelector:withObject:afterDelay:を使用する方法


97

このNSObjectメソッドをperformSelector:withObject:afterDelay:使用すると、一定の時間が経過すると、オブジェクト引数を使用してオブジェクトのメソッドを呼び出すことができます。非オブジェクト引数を持つメソッド(int、float、struct、非オブジェクトポインターなど)には使用できません。

非オブジェクト引数を持つメソッドで同じことを達成する最も簡単な方法は何ですか?通常のperformSelector:withObject:では、解決策を使用することを知っていますNSInvocation(ところで、これは本当に複雑です)。しかし、「遅延」部分の処理方法がわかりません。

おかげで、


少しハックですが、高速なコードを書くのは便利です。次のようなもの:id object = [array performSelector:@selector(objectAtIndex :) withObject:(__bridge_transfer)(void *)1]; 。引数が0の場合、0は「特別」であり、idはそれと互換性があるため、ブリッジキャストも必要ありません。
Ramy Al Zuhouri 2013年

回答:


72

これは、NSInvocationを使用して変更できないものを呼び出すために使用したものです。

SEL theSelector = NSSelectorFromString(@"setOrientation:animated:");
NSInvocation *anInvocation = [NSInvocation
            invocationWithMethodSignature:
            [MPMoviePlayerController instanceMethodSignatureForSelector:theSelector]];

[anInvocation setSelector:theSelector];
[anInvocation setTarget:theMovie];
UIInterfaceOrientation val = UIInterfaceOrientationPortrait;
BOOL anim = NO;
[anInvocation setArgument:&val atIndex:2];
[anInvocation setArgument:&anim atIndex:3];

[anInvocation performSelector:@selector(invoke) withObject:nil afterDelay:1];

なぜしないのです[theMovie setOrientation: UIInterfaceOrientationPortrait animated:NO]か?または、invokeメッセージは遅延後に実行するメソッドにあるということですか?
Peter Hosey、

ああ、私は言うのを忘れた.. `[anInvocation performSelector:@selector(invoke)afterDelay:0.5];
モーティ

24
知らない人のための補足として、インデックス0と1は非表示の引数「self」と「_cmd」用に予約されているため、インデックス2から引数を追加します。
Vishal Singh 2013

35

float、boolean、intなどをNSNumberでラップするだけです。

構造体の場合、私は便利な解決策を知りませんが、そのような構造体を所有する別のObjCクラスを作成できます。


5
ラッパーメソッド-delayedMethodWithArgument:(NSNumber *)argを作成します。プリミティブをNSNumberから引き出した後で、元のメソッドを呼び出すようにします。
ジェームズウィリアムズ、

1
わかった。次に、エレガントな解決策はわかりませんが、performSelector:のターゲットとして意図されている別のメソッドを追加できます。これは、NSNumberをアンパックし、現在考えているメソッドで最終的なセレクターを実行します。あなたが探求できる別のアイデアは、perforSelector:withInt:...(および同様の)を追加するNSObjectにカテゴリを作成することです。

32
NSValue(NSNumberのスーパークラス)は、指定したものをすべてラップします。「bar」と呼ばれる「struct foo」のインスタンスをラップする場合は、「[NSValue valueWithBytes:&bar objCType:@encode(struct foo)];」を実行します。
Jim Dovey、

2
NSValueを使用して、構造体をラップできます。どちらの方法でも、NSNumber / NSValueが正しいソリューションです。
Peter Hosey、

6
確認:MUST渡すnilためにBOOL受信するようにパラメータNOFALSE
ニコラMiari

13

この回答は使用しないでください。私は歴史的な目的のためだけにそれを残しました。以下のコメントをご覧ください。

それがBOOLパラメータである場合、単純なトリックがあります。

NOの場合はnilを、YESの場合はselfを渡します。nilはBOOL値NOにキャストされます。selfはYESのBOOL値にキャストされます。

このアプローチは、BOOLパラメータ以外の場合は機能しません。

自分自身がUIViewであると仮定します。

//nil will be cast to NO when the selector is performed
[self performSelector:@selector(setHidden:) withObject:nil afterDelay:5.0];

//self will be cast to YES when the selector is performed
[self performSelector:@selector(setHidden:) withObject:self afterDelay:10.0];

BOOL値は常に0または1である必要があると思います。ほとんどのコードは気にせず、単にif ()テストするだけですが、一部のコードは0または1に依存しているため、勝つことが考えられます一部の大きなアドレス値を1と同じものとして扱いません(YES)。
user102008 2010年

1
そのおかげで、ラッパーを作成したり、それを行うために醜いGCD構文を作成したりしたくありませんでした。
0xSina 2012

10
だれでも使用しないでください。このアプローチは深刻なバグの原因であり、見つけるのは困難です。のselfようなアドレスを想像してみてください0x0123400。このアプローチにより、0.4%のケースでYESの意図はありません。さらに悪いことに、この確率で、このソリューションはすべてのテストに合格し、後で明らかになる可能性があります。
Oleg Trakhman

1
UPD: 記憶の調整のために、6%の確率でYESの指示はありません
Oleg Trakhman


8

おそらくNSValueです。ポインタが遅延後も有効であることを確認してください(つまり、スタックにオブジェクトが割り当てられていない)。


古いメソッドは、NSValue(可能であれば)期待どおりに「開梱」されtypeますか?
Alex Grey

8

私はこれが古い質問であることを知っていますが、iOS SDK 4+を構築している場合は、ブロックを使用してこれを非常に少ない労力で実行し、読みやすくすることができます。

double delayInSeconds = 2.0;
int primitiveValue = 500;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self doSomethingWithPrimitive:primitiveValue];     
});

これにより、保持ループが作成されます。これが適切に機能するためには、自分自身に弱い参照を作成し、その参照を使用してセレクターを実行する必要があります。「__block typeof(self)weakSelf = self;」
Tim Wachter 2013年

1
自己はブロックを保持しないため(保持ループがないため)、弱参照は必要ありません。自分自身が割り当て解除される可能性があるため、実際には強い参照を使用することをお勧めします。参照:stackoverflow.com/a/19018633/251687
Sebastien Martin

6

PerformSelector:WithObjectは常にオブジェクトを取得するため、int / double / floatなどの引数を渡すために、次のように使用できます。

//NSNumber is an object..

    [self performSelector:@selector(setUserAlphaNumber:) withObject: [NSNumber numberWithFloat: 1.0f]
    afterDelay:1.5];

    -(void) setUserAlphaNumber: (NSNumber*) number{

     [txtUsername setAlpha: [number floatValue] ];
    }

同じように[NSNumber numberWithInt:]などを使用できます。受信メソッドでは、数値を[number int]または[number double]の形式に変換できます。


1
+ performSelector:withObject:+に制限されている場合、投稿された唯一の正解はIMOです。しかし、ブロックを使用した@MichaelGaylordの答えは、それがあなたのためのオプションである場合、よりきれいです。
big_m 2013年

5

ブロックは進むべき道です。複雑なパラメータやタイプセーフを使用できます。これは、ここでのほとんどの古い回答よりもはるかに単純で安全です。たとえば、次のように書くことができます。

[MONBlock performBlock:^{[obj setFrame:SOMETHING];} afterDelay:2];

ブロックを使用すると、任意のパラメーターリスト、参照オブジェクト、変数をキャプチャできます。

バッキング実装(基本):

@interface MONBlock : NSObject

+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay;

@end

@implementation MONBlock

+ (void)imp_performBlock:(void(^)())pBlock
{
 pBlock();
}

+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay
{
  [self performSelector:@selector(imp_performBlock:)
             withObject:[pBlock copy]
             afterDelay:pDelay];
}

@end

例:

int main(int argc, const char * argv[])
{
 @autoreleasepool {
  __block bool didPrint = false;
  int pi = 3; // close enough =p

  [MONBlock performBlock:^{NSLog(@"Hello, World! pi is %i", pi); didPrint = true;} afterDelay:2];

  while (!didPrint) {
   [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeInterval:0.1 sinceDate:NSDate.date]];
  }

  NSLog(@"(Bye, World!)");
 }
 return 0;
}

別の例については、マイケルの回答(+1)も参照してください。


3

渡すオブジェクトとしてNSMutableArrayを使用することを常にお勧めします。これは、押されたボタンやその他の値など、複数のオブジェクトを渡すことができるためです。NSNumber、NSInteger、およびNSStringは、いくつかの値のコンテナーにすぎません。正しいコンテナタイプを参照する配列からオブジェクトを取得することを確認してください。NSコンテナを渡す必要があります。そこで値をテストできます。値を比較する場合、コンテナーはisEqualを使用することに注意してください。

#define DELAY_TIME 5

-(void)changePlayerGameOnes:(UIButton*)sender{
    NSNumber *nextPlayer = [NSNumber numberWithInt:[gdata.currentPlayer intValue]+1 ];
    NSMutableArray *array = [[NSMutableArray alloc]initWithObjects:sender, nil];
    [array addObject:nextPlayer];
    [self performSelector:@selector(next:) withObject:array afterDelay:DELAY_TIME];
}
-(void)next:(NSMutableArray*)nextPlayer{
    if(gdata != nil){ //if game choose next player
       [self nextPlayer:[nextPlayer objectAtIndex:1] button:[nextPlayer objectAtIndex:0]];
    }
}

2

私もこれをやりたかったのですが、BOOLパラメータを受け取るメソッドが必要です。NSNumberでブール値をラップしています。値を渡すことができませんでした。なぜか分かりません。

だから私は単純なハックをすることになりました。必要なパラメーターを別のダミー関数に入れ、performSelectorを使用してその関数を呼び出します。ここで、withObject = nil;

[self performSelector:@selector(dummyCaller:) withObject:nil afterDelay:5.0];

-(void)dummyCaller {

[self myFunction:YES];

}

危害の答えに関する上記の私のコメントを参照してください。以来、のように見える-performSelector[...]方法が期待オブジェクト(代わりにプリミティブデータタイプの)を、それが呼び出さセレクタブールを受け入れることを知らない、おそらくのアドレス(ポインタ値)NSNumberインスタンスは盲目的に「キャスト」ですBOOL(=ゼロ以外、つまりTRUE)。おそらく、ランタイムはこれよりも賢く、NSNumber!でラップされたゼロのブール値を認識します。
Nicolas Miari

同意する。私は、BOOLを完全に回避し、整数の0をNOに、1をYESに使用し、それをNSNumberまたはNSValueでラップすることをお勧めします。
GeneCode

何か変化はありますか?もしそうなら、突然、私は本当にココアに失望します:)
Nicolas Miari

2

これを行う最も迅速な(しかし多少汚い)方法は、objc_msgSendを直接呼び出すことです。ただし、ドキュメントを読み、戻り値のタイプに適切なバリアントを使用していることを確認し、objc_msgSendがコンパイラの便宜のためにvarargとして定義されているため、実際には高速アセンブリ接着剤として実装されているため、直接呼び出すことは危険。デリゲートメソッド-[delegate integerDidChange:]を呼び出すために使用するコードをいくつか示します。これは、単一の整数引数を取ります。

#import <objc/message.h>


SEL theSelector = @selector(integerDidChange:);
if ([self.delegate respondsToSelector:theSelector])
{
    typedef void (*IntegerDidChangeFuncPtrType)(id, SEL, NSInteger);
    IntegerDidChangeFuncPtrType MyFunction = (IntegerDidChangeFuncPtrType)objc_msgSend;
    MyFunction(self.delegate, theSelector, theIntegerThatChanged);
}

セレクターを複数回参照するので、最初にセレクターが保存され、タイプミスを作成するのは簡単です。次に、デリゲートがセレクターに実際に応答することを確認します。セレクターはオプションのプロトコルである可能性があります。次に、セレクターの実際のシグニチャーを指定する関数ポインター型を作成します。すべてのObjective-Cメッセージには2つの非表示の最初の引数があります。メッセージが送信されるオブジェクトと送信されるセレクターです。次に、適切なタイプの関数ポインターを作成し、基になるobjc_msgSend関数を指すように設定します。戻り値がfloatまたはstructの場合は、objc_msgSendの別のバリアントを使用する必要があることに注意してください。最後に、Objective-Cがシート​​の下で使用するのと同じ機構を使用してメッセージを送信します。


「浮動小数点数または構造体を送信する場合は、
注意して

これらの3行は、タイプセーフを提供し、引数の数がセレクターが期待するものであることを保証します。objc_msgSendは、実際にはアセンブリ接着剤として実装される可変引数関数であるため、型キャストを行わない場合は、何でも指定できます。
Jason Harris

1

NSTimerを使用してセレクターを呼び出すだけです。

[NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(yourMethod:) userInfo:nil repeats:NO]

あなたは議論を見逃している。yourMethod:あなたの例のへの引数はタイマー自体であり、には何も渡していませんuserInfo。を使用userInfoしたとしても、害とRemus Rusanuが示唆するように、値をボックス化する必要があります。
Peter Hosey、2010年

-1は、performSelectorを使用せず、不要なNSTimerインスタンスを作成する場合
Applicasa iOS開発者

1

NSNumberまたは他のNSValueを使用してperformSelectorを呼び出すことはできません。NSValue / NSNumberの値を使用する代わりに、ポインタをintやfloatなどに効果的にキャストして使用します。

しかし、解決策は単純で明白です。NSInvocationを作成して呼び出す

[invocation performSelector:@selector(invoke) withObject:nil afterDelay:delay]


0

おそらく...たぶん、おそらく何かが足りないのですが、CGFloatなどの非オブジェクトタイプ変数のコンテナとして、オブジェクトタイプ、たとえばNSNumberを作成しないのはなぜですか?

CGFloat myFloat = 2.0; 
NSNumber *myNumber = [NSNumber numberWithFloat:myFloat];

[self performSelector:@selector(MyCalculatorMethod:) withObject:myNumber afterDelay:5.0];

問題は、NSNumberオブジェクトではなく、プリミティブ型を渡すことです。
ルドルフアダムコビッチ2012

質問では、OPはメソッドへの外部アクセスのみを持ち、署名を変更できないと想定しています。
Alex Grey
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.