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.