Cocoa의 기본 유형과 함께 performSelector : withObject : afterDelay :를 사용하는 방법?


97

이 NSObject메서드를 performSelector:withObject:afterDelay:사용하면 일정 시간 후에 개체 인수를 사용하여 개체에 대한 메서드를 호출 할 수 있습니다. 객체가 아닌 인수 (예 : 정수, 부동 소수점, 구조체, 객체가 아닌 포인터 등)가있는 메서드에는 사용할 수 없습니다.

객체가 아닌 인수가있는 메서드로 동일한 작업을 수행 하는 가장 간단한 방법 은 무엇입니까 ? 나는 regular 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];
— Morty

24
모르는 사람들을위한 참고 사항입니다. 인덱스 0과 1은 숨겨진 인수 "self"와 "_cmd"를 위해 예약되어 있기 때문에 인덱스 2에서 인수를 추가하기 시작합니다.
— Vishal Singh

35

float, boolean, int 또는 이와 유사한 것을 NSNumber로 감싸십시오.

구조체의 경우 편리한 솔루션을 모르지만 이러한 구조체를 소유하는 별도의 ObjC 클래스를 만들 수 있습니다.


5
래퍼 메서드 -delayedMethodWithArgument : (NSNumber *) arg를 만듭니다. NSNumber에서 기본 요소를 가져온 후 원래 메서드를 호출하도록합니다.
— James Williams

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
확인 됨 : 수신 할 매개 변수를 전달 해야합니다 ( )nilBOOLNOFALSE
— Nicolas Miari

13

이 답변을 사용하지 마십시오. 나는 역사적 목적으로 만 남겨 두었습니다. 아래 설명을 참조하십시오.

BOOL 매개 변수 인 경우 간단한 트릭이 있습니다.

아니요에는 nil을 전달하고 YES에는 self를 전달하십시오. nil은 BOOL 값 NO로 캐스트됩니다. self는 YES의 BOOL 값으로 캐스트됩니다.

이 접근 방식은 BOOL 매개 변수가 아닌 경우 분류됩니다.

self가 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 (예)과 동일하게 취급하지 마십시오.
— user102008

1
감사합니다. 래퍼를 만들거나 못생긴 GCD 구문을 작성하고 싶지 않았습니다.
— 0xSina

10
아무도 사용하지 마십시오 . 이 접근 방식은 찾기 어려운 심각한 버그의 원인입니다. self와 같은 주소를 상상해보십시오 0x0123400. 이 접근 방식을 사용하면 0.4 % 의 사례 에서 '예 '가 ' 아니요'로 표시 됩니다. 더 나쁜 것은이 확률로이 솔루션이 모든 테스트를 통과하고 나중에 밝혀 질 수 있다는 것입니다.
— Oleg Trakhman 2012

1
UPD : 메모리 정렬로 인해 6 % 확률로 YES의 NO가 표시 됩니다.
— Oleg Trakhman 2012

4
체크 아웃 @iVishal BOOL의 날카로운 모서리
— Farray

8

아마도 NSValue , 지연 후에도 포인터가 여전히 유효한지 확인하십시오 (즉, 스택에 할당 된 객체가 없음).


NSValue가능한 경우 이전 방법 이 예상대로 "unbox" type됩니까?
— Alex Gray

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];     
});

이것은 유지 루프를 만듭니다. 이것이 제대로 작동하려면 self에 대한 약한 참조를 만들고 해당 참조를 사용하여 선택기를 수행해야합니다. "__block typeof (self) weakSelf = self;"
— Tim Wachter 2013

1
self는 블록을 유지하지 않기 때문에 여기서 약한 참조가 필요하지 않습니다 (리테이너 루프가 없음). self가 그렇지 않으면 할당이 해제 될 수 있으므로 실제로 강력한 참조를 사용하는 것이 좋습니다. 참조 : 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

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;
}

다른 예는 Michael의 대답 (+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= ( 0이 아닌, 즉 TRUE). 아마도 런타임이 이것보다 더 똑똑하고 NSNumber!
— Nicolas Miari 2012-06-26

나는 동의한다. 더 나은 방법은 BOOL을 모두 피하고 정수 0을 NO에 사용하고 1을 YES에 사용하고 NSNumber 또는 NSValue로 래핑하는 것입니다.
— GeneCode

변경 사항이 있습니까? 그렇다면, 갑자기 진정 : 코코아에 실망하고
— 니콜라스 미아리

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 메시지에는 두 개의 숨겨진 첫 번째 인수, 메시지가 전송되는 개체와 전송되는 선택기가 있습니다. 그런 다음 적절한 유형의 함수 포인터를 만들고 기본 objc_msgSend 함수를 가리 키도록 설정합니다. 반환 값이 float 또는 struct 인 경우 objc_msgSend의 다른 변형을 사용해야합니다. 마지막으로 Objective-C가 시트 아래에서 사용하는 것과 동일한 기계를 사용하여 메시지를 보냅니다.


"수레 또는 구조체를 보내는 경우 ..."라는 의미는 float 또는 구조체 반환 을 의미 합니다
— user102008

이 세 줄은 유형 안전성을 제공하고 인수의 수가 선택자가 예상하는 것임을 보장합니다. objc_msgSend는 실제로 어셈블리 접착제로 구현 된 vararg 함수이므로 typecast를하지 않으면 아무것도 줄 수 있습니다.
— Jason Harris

1

NSTimer를 사용하여 선택기를 호출 할 수 있습니다.

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

당신은 논쟁을 놓치고 있습니다. yourMethod:귀하의 예제에서에 대한 인수 는 타이머 자체이며 userInfo. 를 사용하더라도 userInfo피해와 Remus Rusanu가 제안한대로 값을 상자에 넣어야합니다.
— Peter Hosey

-1 performSelector를 사용하여 해제 필요한 NSTimer의 인스턴스를 생성하지 않는
— Applicasa 아이폰 OS 개발자

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 객체가 아닌 기본 유형을 전달하는 것입니다.
— Rudolf Adamkovič 2015 년

질문은 OP가 메서드에 대한 외부 액세스 권한 만 갖고 있고 서명을 변경할 수 없다고 가정합니다.
— Alex Gray
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.