PerformSelector: withObject: afterDelay: Cocoa'daki ilkel türlerle nasıl kullanılır?


97

NSObjectYöntem performSelector:withObject:afterDelay:bana belirli bir süre sonra bir nesne bağımsız değişkenle nesne üzerinde bir yöntem çağırmak için izin verir. Nesne olmayan bir argümana sahip yöntemler için kullanılamaz (örneğin, ints, floats, structs, non-object pointer, vb.).

Nesne olmayan bir argümana sahip bir yöntemle aynı şeyi elde etmenin en basit yolu nedir ? Normalde performSelector:withObject:çözümün kullanmak olduğunu biliyorum NSInvocation(bu arada gerçekten karmaşık). Ama "gecikme" kısmıyla nasıl başa çıkacağımı bilmiyorum.

Teşekkürler,


Biraz zor ama ben hızlı kod yazmayı faydalı buluyorum. Şuna benzer bir şey: id object = [array performSelector: @selector (objectAtIndex :) withObject: (__bridge_transfer) (void *) 1]; . Eğer argüman 0 ise, köprü dökümüne bile ihtiyacınız yoktur, çünkü 0 "özel" dir ve id onunla uyumludur.
Ramy Al Zuhouri

Yanıtlar:


72

NSInvocation kullanarak değiştiremediğim bir şeyi şöyle adlandırırdım:

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

Neden sadece yapmıyorsun [theMovie setOrientation: UIInterfaceOrientationPortrait animated:NO]? Yoksa invokemesajın gecikmeden sonra uyguladığınız yöntemde olduğunu mu söylüyorsunuz?
Peter Hosey

Ah ya, demeyi unuttum .. "[bir Çağrı performSelector: @selector (invoke) afterDelay: 0.5];
Morty

25
Bilmeyenler için sadece bir yan not, indeks 2'den argümanlar eklemeye başlıyoruz çünkü indeks 0 ve 1 gizli argümanlar "self" ve "_cmd" için ayrılmıştır.
Vishal Singh

35

Sadece float, boolean, int veya benzeri bir NSNumber içine sarın.

Yapılar için kullanışlı bir çözüm bilmiyorum, ancak böyle bir yapıya sahip ayrı bir ObjC sınıfı oluşturabilirsiniz.


5
-DelayedMethodWithArgument: (NSNumber *) argümanında bir sarmalayıcı yöntemi oluşturun. İlkeli NSNumber'dan çıkardıktan sonra orijinal yöntemi çağırsın.
James Williams

1
Anladım. O halde zarif bir çözümden emin değilim, ancak performSelector'ınızın hedefi olması amaçlanan başka bir yöntem ekleyebilirsiniz: NSNumber'ı çözer ve şu anda aklınızdaki yönteme göre son seçiciyi gerçekleştirir. Keşfedebileceğiniz başka bir fikir de NSObject'te perforSelector: withInt: ... (ve benzeri) ekleyen bir kategori oluşturmaktır.
zarar verir

32
NSValue (NSNumber'ın üst sınıfı) verdiğiniz her şeyi sarmalayacaktır. 'Bar' adında bir 'struct foo' örneğini sarmak istiyorsanız, '[NSValue valueWithBytes: & bar objCType: @encode (struct foo)];'
Jim Dovey

2
Bir yapıyı sarmak için NSValue kullanabilirsiniz. Her iki durumda da NSNumber / NSValue doğru çözümdür.
Peter Hosey

6
Onaylandı: ( ) alması için bir parametre geçmesi ZORUNLUnilBOOLNOFALSE
Nicolas Miari

13

BU CEVABI KULLANMAYIN. SADECE TARİHİ AMAÇLAR İÇİN BIRAKTIM. AŞAĞIDAKİ YORUMLARI GÖR.

BOOL parametresiyse basit bir numara var.

HAYIR için nil ve EVET için self. nil, NO'nun BOOL değerine dönüştürülür. self, EVET'in BOOL değerine dönüştürülür.

BOOL parametresinden başka bir şeyse bu yaklaşım bozulur.

Benliğin bir UIView olduğunu varsayarsak.

//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 değerlerinin her zaman 0 veya 1 olması gerektiğine inanıyorum. Çoğu kodun umursamayacağı ve basitçe if ()üzerinde bir test yapacağı doğrudur , ancak bazı kodların 0 veya 1 olmasına bağlı olacağı ve dolayısıyla kazanacağı düşünülebilir. Bazı büyük adres değerlerini 1 (EVET) ile aynı kabul et.
user102008

1
Bunun için teşekkürler, bunu yapmak için bir sarmalayıcı oluşturmak veya çirkin GCD sözdizimi yazmak istemedim.
0xSina

10
KİMSEYE KULLANMAYIN . Bu yaklaşım, bulunması zor olan ciddi hataların kaynağıdır. selfGibi adreslerle hayal edin 0x0123400. Bu yaklaşımla, vakaların % 0,4'ünde EVET HAYIR alacaksınız . Daha da kötüsü, bu olasılıkla bu çözüm tüm testleri geçebilir ve daha sonra ortaya çıkabilir.
Oleg Trakhman

1
UPD: Bellek hizalaması nedeniyle % 6 olasılıkla EVET HAYIR elde edilir .
Oleg Trakhman

4
@iVishal BOOL'un keskin köşelerine
Farray

8

Belki NSValue , sadece işaretçilerinizin gecikmeden sonra hala geçerli olduğundan emin olun (yani, yığın üzerinde hiçbir nesne ayrılmamışsa).


Herhangi bir eski yöntem NSValue(mümkünse) beklenen şekilde "kutudan çıkarılacak" typemı?
Alex Grey

8

Bunun eski bir soru olduğunu biliyorum, ancak iOS SDK 4+ oluşturuyorsanız, bunu çok az çabayla yapmak ve daha okunaklı hale getirmek için blokları kullanabilirsiniz:

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

Bu, bir tutma döngüsü oluşturur. Bunun düzgün çalışması için kendine zayıf bir referans vermeniz ve seçiciyi gerçekleştirmek için bu referansı kullanmanız gerekir. "__block typeof (self) poorSelf = self;"
Tim Wachter

1
Burada zayıf bir referansa ihtiyacınız yok çünkü self bloğu korumaz (tutma döngüsü yoktur). Aksi takdirde öz ayrımı kaldırılabileceğinden, muhtemelen güçlü bir referans kullanmak tercih edilir. Bakınız: stackoverflow.com/a/19018633/251687
Sebastien Martin

6

PerformSelector: WithObject her zaman bir nesne alır, dolayısıyla int / double / float vb. Argümanları iletmek için ..... Bunun gibi bir şey kullanabilirsiniz.

//NSNumber is an object..

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

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

     [txtUsername setAlpha: [number floatValue] ];
    }

Aynı şekilde [NSNumber numberWithInt:] vb. Kullanabilirsiniz ve alma yönteminde sayıyı [sayı int] veya [sayı çift] olarak biçiminize dönüştürebilirsiniz.


1
Biri + performSelector: withObject: + ile sınırlandırılmışsa, bu yayınlanan tek doğru cevap, IMO'dur. Ancak @ MichaelGaylord'un blokları kullanarak cevabı daha temiz, eğer bu sizin için bir seçenekse.
big_m

5

Bloklar gitmenin yoludur. Karmaşık parametrelere sahip olabilirsiniz, güvenlik yazabilirsiniz ve buradaki eski cevapların çoğundan çok daha basit ve daha güvenlidir. Örneğin, şöyle yazabilirsiniz:

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

Bloklar, rastgele parametre listelerini, referans nesneleri ve değişkenleri yakalamanıza izin verir.

Destek Uygulaması (temel):

@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

Misal:

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

Ayrıca başka bir örnek için Michael'ın cevabına (+1) bakın.


3

Ben her zaman NSMutableArray nesnesini aktarılacak nesne olarak kullanmanızı tavsiye ederim. Bunun nedeni, daha sonra basılan düğme ve diğer değerler gibi birkaç nesneyi geçirebilmenizdir. NSNumber, NSInteger ve NSString yalnızca belirli bir değere sahip kapsayıcılardır. Başvurduğunuz diziden nesneyi aldığınızda, doğru bir kap türüne baktığınızdan emin olun. NS konteynerlerini devretmeniz gerekiyor. Orada değeri test edebilirsiniz. Değerler karşılaştırılırken kapların isEqual kullandığını unutmayın.

#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

Bunu da yapmak istedim, ancak bir BOOL parametresi alan bir yöntemle. Bool değerini NSNumber ile sarma, DEĞERİ GEÇMEDİ. Neden olduğuna dair hiçbir fikrim yok.

Böylece basit bir hack yaptım. Gerekli parametreyi başka bir kukla işleve koyuyorum ve bu işlevi performSelector kullanarak çağırıyorum, burada withObject = nil;

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

-(void)dummyCaller {

[self myFunction:YES];

}

Zararların cevabı hakkındaki yorumuma bakınız. Görünüşe göre, -performSelector[...]yöntem bir nesne beklediğinden (ilkel bir veri türü yerine) ve çağrılan seçicinin bir boolean kabul ettiğini bilmediğinden, belki de örneğin adresi (işaretçi değeri) NSNumberkörü körüne BOOL(= sıfır olmayan, yani TRUE). Belki çalışma zamanı bundan daha akıllı olabilir ve bir NSNumber!
Nicolas Miari

Katılıyorum. Sanırım yapılacak daha iyi bir şey BOOL'dan tamamen kaçınmak ve HAYIR için 0 ve YES için 1 tamsayısını kullanmak ve bunu NSNumber veya NSValue ile sarmaktır.
GeneCode

Bu bir şeyi değiştirir mi? Öyleyse, birden Cocoa beni gerçekten hayal kırıklığına uğrattı :)
Nicolas Miari

2

Bunu yapmanın en hızlı (ama biraz kirli) yolunun doğrudan objc_msgSend'i çağırmak olduğunu anladım. Bununla birlikte, doğrudan çağırmak tehlikelidir çünkü belgeleri okumanız ve dönüş değeri türü için doğru varyantı kullandığınızdan emin olmanız gerekir ve objc_msgSend, derleyici rahatlığı için vararg olarak tanımlanır, ancak aslında hızlı montaj tutkalı olarak uygulanır. . Bir temsilci yöntemini çağırmak için kullanılan bazı kodlar - tek bir tamsayı bağımsız değişkeni alan [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);
}

Bu, önce seçiciyi kaydeder çünkü ona birden çok kez atıfta bulunacağız ve bir yazım hatası oluşturmak kolay olacaktır. Ardından, temsilcinin gerçekten seçiciye yanıt verdiğini doğrular - bu isteğe bağlı bir protokol olabilir. Ardından, seçicinin gerçek imzasını belirten bir işlev işaretçi türü oluşturur. Tüm Objective-C mesajlarının iki gizli ilk argümanı olduğunu unutmayın; mesaj gönderilen nesne ve gönderilen seçici. Daha sonra uygun türde bir işlev işaretçisi oluşturuyoruz ve onu temeldeki objc_msgSend işlevini gösterecek şekilde ayarlıyoruz. Dönüş değeri bir float veya struct ise, farklı bir objc_msgSend varyantı kullanmanız gerektiğini unutmayın. Son olarak, mesajı Objective-C'nin sayfaların altında kullandığı aynı makineyi kullanarak gönderin.



Bu üç satır size tür güvenliği sağlar ve seçicinizin beklediği argüman sayısının olduğunu garanti eder. objc_msgSend, aslında montaj yapıştırıcısı olarak uygulanan bir vararg işlevidir, yani döküm yazmazsanız, ona her şeyi verebilirsiniz.
Jason Harris

1

Bir seçici çağırmak için sadece NSTimer kullanabilirsiniz:

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

Tartışmayı kaçırıyorsun. Örneğinizdeki argüman yourMethod:zamanlayıcının kendisidir ve siz hiçbir şeyi geçmediniz userInfo. Kullanmış olsanız bile userInfo, zararlar ve Remus Rusanu'nun önerdiği gibi, yine de değeri küçültmeniz gerekir.
Peter Hosey

PerformSelector kullanmamak ve gerekli olmayan NSTimer örneği oluşturmak için -1
Applicasa iOS geliştiricisi

1

PerformSelector'ı bir NSNumber veya başka bir NSValue ile çağırmak işe yaramaz. NSValue / NSNumber değerini kullanmak yerine, göstericiyi bir int, float veya her neyse etkin bir şekilde çevirecek ve onu kullanacaktır .

Ancak çözüm basit ve açıktır. NSInvocation'ı oluşturun ve arayın

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


0

Pehaps ... tamam, büyük olasılıkla bir şeyi kaçırıyorum, ama neden CGFloat gibi nesne türü olmayan değişkeninize bir kap olarak bir nesne türü, mesela NSNumber oluşturmuyorsunuz?

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

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

Soru NSNumber nesnelerinin değil, ilkel türlerin iletilmesiyle ilgilidir.
Rudolf Adamkovič

Soru, OP'nin yalnızca yönteme harici erişime sahip olduğunu ve imzayı değiştiremeyeceğini varsayar.
Alex Grey
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.