performSelector, seçici bilinmediğinden sızıntıya neden olabilir


1258

ARC derleyicisi tarafından aşağıdaki uyarıyı alıyorum:

"performSelector may cause a leak because its selector is unknown".

İşte yaptığım şey:

[_controller performSelector:NSSelectorFromString(@"someMethod")];

Neden bu uyarıyı alıyorum? Derleyicinin seçicinin var olup olmadığını kontrol edemediğini anlıyorum, ancak bu neden bir sızıntıya neden olur? Ve artık bu uyarıyı almamak için kodumu nasıl değiştirebilirim?


3
Değişkenin adı dinamiktir, başka birçok şeye bağlıdır. Var olmayan bir şey deme riskim var, ama sorun bu değil.
Eduardo Scoz

6
@matt Neden bir nesneye dinamik olarak yöntemin çağrılması kötü bir uygulamadır? NSSelectorFromString () yönteminin amacı bu uygulamayı desteklemiyor mu?
Eduardo Scoz

7
PerformSelector ile ayarlamadan önce [_controller yanıtlarToSelector: mySelector] öğesini de test etmelisiniz / seçebilirsiniz:
mattacular

50
@mattacular Keşke oy verebilseydim: "Bu ... kötü bir uygulamadır."
ctpenrose

6
Dizenin değişmez olduğunu biliyorsanız, derleyicinin seçici adının ne olduğunu söylemesi için @selector () işlevini kullanın. Gerçek kodunuz, çalışma zamanında oluşturulan veya sağlanan bir dize ile NSSelectorFromString () öğesini çağırıyorsa, NSSelectorFromString () öğesini kullanmanız gerekir.
Chris Page

Yanıtlar:


1211

Çözüm

Derleyici bu konuda bir uyarı yapıyor. Bu uyarının göz ardı edilmesi çok nadirdir ve etrafta dolaşmak kolaydır. Bunu nasıl yapacağınız aşağıda açıklanmıştır:

if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);

Veya daha tersine (okunması zor olsa da, gardiyan olmadan):

SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);

açıklama

Burada olup bitenlerden, denetleyiciye denetleyiciye karşılık gelen yöntem için C işlev işaretçisini sormanız gerekir. Tümü NSObjectyanıt verir methodForSelector:, ancak class_getMethodImplementationObjective-C çalışma zamanında da kullanabilirsiniz (yalnızca bir protokol başvurunuz varsa yararlıdır id<SomeProto>). Bu işlev işaretçileri IMPs olarak adlandırılır ve basit typedefdüzen işlev işaretçileri ( id (*IMP)(id, SEL, ...)) 1'dir . Bu, yöntemin gerçek yöntem imzasına yakın olabilir, ancak her zaman tam olarak eşleşmez.

Bir kez sahip olduktan sonra IMP, ARC'nin ihtiyaç duyduğu tüm ayrıntıları içeren (iki gizli gizli argüman selfve _cmdher Objective-C yöntemi çağrısı dahil) bir işlev işaretçisine yayınlamanız gerekir . Bu üçüncü satırda ele alınır ( (void *)sağ tarafta derleyiciye basitçe işaretçi türleri uyuşmadığından bir uyarı oluşturmamak için ne yaptığınızı bildiğinizi söyler).

Son olarak, fonksiyon işaretçisini 2 çağırırsınız .

Karmaşık Örnek

Seçici argüman aldığında veya bir değer döndürdüğünde, işleri biraz değiştirmeniz gerekir:

SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
  func(_controller, selector, someRect, someView) : CGRectZero;

Uyarı Nedeni

Bu uyarının nedeni ARC ile çalışma zamanının aradığınız yöntemin sonucu ile ne yapacağını bilmesi gerektiğidir. Sonuç şey olabilir: void, int, char, NSString *, id, vb ARC normalde birlikte çalıştığınız nesne türü başlığındaki bu bilgileri alır. 3

ARC'nin dönüş değeri için dikkate alacağı sadece 4 şey var: 4

  1. Göz ardı olmayan nesne türleri ( void, intvs.)
  2. Nesne değerini koruyun, ardından artık kullanılmadığında bırakın (standart varsayım)
  3. Artık kullanılmadığında yeni nesne değerlerini serbest bırakın ( init/ copyailesindeki veya ile ilişkilendirilen yöntemler ns_returns_retained)
  4. Hiçbir şey yapma ve yerel kapsamda geçerli olacak döndürülen nesne değeri varsayılmaktadır (iç en bırakma havuz boşaltılır kadar olan atfedilen ns_returns_autoreleased)

methodForSelector:To call, çağırdığı yöntemin dönüş değerinin bir nesne olduğunu varsayar, ancak bunu tutmaz / serbest bırakmaz. Böylece, nesnenizin yukarıdaki # 3'teki gibi serbest bırakılması gerekiyorsa bir sızıntı oluşturabilirsiniz (yani, çağırdığınız yöntem yeni bir nesne döndürür).

Bu dönüşü voidveya diğer nesne olmayan nesneleri çağırmaya çalıştığınız seçiciler için , uyarıcıyı yok saymak üzere derleyici özelliklerini etkinleştirebilirsiniz, ancak tehlikeli olabilir. Clang'ın yerel değişkenlere atanmamış dönüş değerlerini nasıl ele aldığının birkaç yinelemesinden geçtiğini gördüm. ARC etkinken, methodForSelector:kullanmak istemeseniz bile döndürülen nesne değerini alamaması ve serbest bırakamamasının bir nedeni yoktur . Derleyicinin bakış açısından, sonuçta bir nesnedir. Bu, çağırdığınız yöntem someMethod, bir nesne olmayan (dahil void) döndürüyorsa , bir çöp işaretçisi değerinin korunmasına / bırakılmasına ve çökmesine neden olabileceği anlamına gelir .

Ek Bağımsız Değişkenler

Göz önünde bulundurulması gereken bir nokta, bunun aynı uyarının gerçekleşeceğidir performSelector:withObject:ve bu yöntemin parametreleri nasıl tükettiğini bildirmemekle benzer sorunlarla karşılaşabilirsiniz. ARC, tüketilen parametreleri bildirmeye izin verir ve yöntem parametreyi tüketirse, muhtemelen sonunda bir zombi ve çökmeye bir mesaj gönderirsiniz. Köprülü döküm ile bu sorunu çözmenin yolları vardır, ancak gerçekten IMPyukarıdaki ve işlev işaretçisi yöntemini kullanmak daha iyi olacaktır . Tüketilen parametreler nadiren sorun olduğundan, bunun ortaya çıkması muhtemel değildir.

Statik Seçiciler

İlginçtir ki, derleyici statik olarak beyan edilen seçicilerden şikayet etmeyecektir:

[_controller performSelector:@selector(someMethod)];

Bunun nedeni, derleyicinin derleme sırasında seçici ve nesne hakkındaki tüm bilgileri gerçekten kaydedebilmesidir. Hiçbir şey hakkında varsayımlarda bulunmaya gerek yoktur. (Bunu bir yıl önce kaynağa bakarak kontrol ettim, ancak şu anda bir referansım yok.)

Bastırma

Bu uyarının bastırılmasının gerekli olacağı bir durumu ve iyi kod tasarımını düşünmeye çalışırken boş kalıyorum. Birisi, bu uyarıyı susturmanın gerekli olduğu bir deneyim yaşadıysa (ve yukarıdakiler işleri düzgün bir şekilde ele almıyorsa) paylaşın.

Daha

Bunu NSMethodInvocationda ele almak için bir inşa etmek mümkündür , ancak bunu yapmak çok daha fazla yazmayı gerektirir ve aynı zamanda daha yavaştır, bu yüzden bunu yapmak için çok az neden vardır.

Tarih

Ne zaman performSelector:yöntemlerin aile önce Objective-C eklendi, ARC yoktu. ARC oluştururken Apple, geliştiricilere adlandırılmış bir seçici aracılığıyla rastgele mesajlar gönderirken belleğin nasıl ele alınması gerektiğini açık bir şekilde tanımlamak için diğer araçları kullanmaya yönlendirmek için bu yöntemler için bir uyarı oluşturulmasına karar verdi. Objective-C'de, geliştiriciler bunu ham işlev işaretçileri üzerinde C stili dökümler kullanarak yapabilirler.

Swift'in tanıtımı ile Apple ,performSelector: yöntem ailesini "doğası gereği güvensiz" olarak belgeledi ve Swift için mevcut değil.

Zamanla, bu ilerlemeyi gördük:

  1. Objective-C'nin önceki sürümleri izin verir performSelector:(manuel bellek yönetimi)
  2. ARC'li Objective-C, performSelector:
  3. Swift'in performSelector:bu yöntemlere "doğal olarak güvenli olmayan" erişimi yoktur ve bu belgeleri belgelemektedir

Bununla birlikte, adlandırılmış bir seçiciyi temel alan mesaj gönderme fikri "doğal olarak güvensiz" bir özellik değildir. Bu fikir, Objective-C ve diğer birçok programlama dilinde uzun süredir başarıyla kullanılmaktadır.


1 Tüm Objective-C yöntemlerinin iki gizli bağımsız değişkeni vardır selfve _cmdbunlar bir yöntemi çağırdığınızda örtük olarak eklenir.

2NULL C'de bir işlev çağırmak güvenli değildir. Denetleyicinin varlığını kontrol etmek için kullanılan koruma, bir nesneye sahip olmamızı sağlar. Bu nedenle biz alırsınız biliyorum IMPden methodForSelector:(o olabilir ama _objc_msgForward, mesaj iletme sistemine girişi). Temel olarak, gardiyan yerinde olduğunda, arayacak bir fonksiyonumuz olduğunu biliyoruz.

3 Aslında, nesneleri nesne olarak idbildirirseniz ve tüm başlıkları içe aktarmazsanız yanlış bilgi alması mümkündür . Derleyicinin iyi olduğunu düşündüğü koddaki çökmelere neden olabilirsiniz. Bu çok nadirdir, ancak olabilir. Genellikle iki yöntem imzasından hangisinin seçileceğini bilmediğine dair bir uyarı alırsınız.

4 Daha fazla ayrıntı için alıkonan iade değerleri ve atılmayan getiri değerleri hakkındaki ARC referansına bakın.


@wbyoung Kodunuz tutma sorununu çözüyorsa, performSelector:yöntemlerin neden bu şekilde uygulanmadığını merak ediyorum . Katı yöntem imzalarına sahiptirler (geri dönüş id, bir veya iki idsaniye alır), bu nedenle ilkel tiplerin ele alınması gerekmez.
Tricertops

1
@Ve argüman, yöntemin prototipinin tanımına dayanarak işlenir (saklanmaz / yayınlanmaz). Endişe çoğunlukla geri dönüş türüne dayanmaktadır.
wbyoung

2
"Karmaşık Örnek" Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'en son Xcode kullanılırken bir hata verir . (5.1.1) Yine de çok şey öğrendim!
Stan James

2
void (*func)(id, SEL) = (void *)imp;void (*func)(id, SEL) = (void (*)(id, SEL))imp;
derlemez

1
değişim void (*func)(id, SEL) = (void *)imp;için <…> = (void (*))imp;ya<…> = (void (*) (id, SEL))imp;
Isaak Osipovich Dunayevsky

1182

Xcode 4.2'deki LLVM 3.0 derleyicisinde uyarıyı aşağıdaki gibi engelleyebilirsiniz:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop

Hatayı birkaç yerde alıyorsanız ve pragmaları gizlemek için C makro sistemini kullanmak istiyorsanız, uyarıyı bastırmayı kolaylaştırmak için bir makro tanımlayabilirsiniz:

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

Makroyu şu şekilde kullanabilirsiniz:

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

Gerçekleştirilen mesajın sonucuna ihtiyacınız varsa, bunu yapabilirsiniz:

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);

Bu yöntem, en iyileştirme Yok dışında bir değere ayarlandığında bellek sızıntılarına neden olabilir.
Eric

4
@Eric Hayır, "initSomething" veya "newSomething" veya "somethingCopy" gibi komik yöntemler çağırmıyorsanız, yapamazsınız.
Andrey Tarantsov

3
@Julian Çalışır, ancak tüm dosya için uyarıyı kapatır - buna ihtiyacınız olmayabilir veya istemeyebilirsiniz. İle Wrappping popve push-pragmas daha temiz ve daha güvenli.
Emil

2
Bütün bunlar derleyiciyi susturuyor. Bu sorunu çözmez. Seçici yoksa, hemen hemen berbatsınız demektir.
Andra Todorescu

2
Bu yalnızca bir if ([_target respondsToSelector:_selector]) {veya benzer bir mantıkla sarıldığında kullanılmalıdır .

208

Benim tahminim şudur: seçici derleyici tarafından bilinmediği için ARC doğru bellek yönetimini uygulayamaz.

Aslında, bellek yönetiminin belirli bir kuralla yöntemin adına bağlandığı zamanlar vardır. Özellikle, düşünce duyuyorum kolaylık yapıcıları karşı yapmak yöntemlerle; eskiden sözleşmeyle otomatik olarak geri gönderilen bir nesne; ikincisi tutulan bir nesne. Kural seçicinin adlarına dayanır, bu nedenle derleyici seçiciyi bilmiyorsa, uygun bellek yönetimi kuralını uygulayamaz.

Bu doğruysa, her şeyi bellek yönetimi için Tamam olduğundan emin olmak, kodunuzu güvenle kullanabilirsiniz düşünüyorum (örneğin, yöntemleri tahsis nesneleri döndürmez).


5
Cevabınız için teşekkürler, neler olduğunu görmek için buna daha fazla bakacağım. Uyarıyı nasıl atlayabileceğim ve yok olmasını nasıl sağlayabileceğim hakkında bir fikrin var mı? Güvenli bir çağrı için sonsuza kadar benim kod oturan uyarı nefret ediyorum.
Eduardo Scoz

84
Bu yüzden forumlarında Apple'dan birinden gerçekten durumun böyle olduğunu onayladım. İnsanların gelecekteki sürümlerde bu uyarıyı devre dışı bırakmasına izin vermek için unutulmuş bir geçersiz kılma ekleyecekler. Teşekkürler.
Eduardo Scoz

5
Bu yanıt, ARC'nin kongre ve yöntem adlarına dayanarak bir şeyi ne zaman yayınlayacağına karar vermesi gibi bazı soruları gündeme getiriyor, o zaman "referans sayımı" nasıl oluyor? Tanımladığınız davranış, eğer ARC kodun hangi kural takip edilirse uygulansın referansları takip etmek yerine kodun belirli bir sözleşmeyi izlediğini varsayarsa, tamamen keyfi olmaktan çok marjinal olarak daha iyi gelir.
aroth

8
ARC, derleme sırasında tutma ve bırakma ekleme işlemini otomatikleştirir. Çöp toplama değildir (bu yüzden inanılmaz derecede hızlı ve düşük ek yüktür). Hiç keyfi değil. Varsayılan kurallar, onlarca yıldır tutarlı bir şekilde uygulanan iyi kurulmuş ObjC kurallarına dayanmaktadır. Bu __attribute, bellek yönetimini açıklayan her yönteme açıkça bir ekleme yapma ihtiyacını ortadan kaldırır . Ancak, aynı zamanda, müşterinin bu modeli (eskiden çok yaygın olan, ancak son yıllarda daha sağlam desenlerle değiştirilen bir model) düzgün bir şekilde işlemesini imkansız hale getirir.
Rob Napier

8
Yani artık bir ivar tipine sahip olamayız SELve duruma bağlı olarak farklı seçiciler atayabiliriz?
Gidilecek

121

Projenizin Oluşturma Ayarları'nda , Diğer Uyarı Bayrakları ( WARNING_CFLAGS) altında ,
-Wno-arc-performSelector-leaks

Şimdi aradığınız seçicinin nesnenizin tutulmasına veya kopyalanmasına neden olmadığından emin olun.


12
Tüm proje yerine belirli dosyalar için aynı bayrağı ekleyebileceğinizi unutmayın. Derleme Aşamaları -> Derleme Kaynakları altına bakarsanız, dosya başına Derleyici Bayrakları'nı (dosyaları ARC'den hariç tutmak için yapmak istediğiniz gibi) ayarlayabilirsiniz. Projemde sadece bir dosya seçicileri bu şekilde kullanmalı, bu yüzden sadece dışladım ve diğerlerini bıraktım.
Michael

111

Derleyici uyarıyı geçersiz kılmaya izin verene kadar bir geçici çözüm olarak, çalışma zamanını kullanabilirsiniz

objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

onun yerine

[_controller performSelector:NSSelectorFromString(@"someMethod")];

Zorunda olacaksın

#import <objc/message.h>


8
ARC, Kakao sözleşmelerini tanır ve ardından bu sözleşmelere dayanarak muhafazalar ve yayınlar ekler. C bu kurallara uymadığı için ARC sizi manuel bellek yönetimi tekniklerini kullanmaya zorlar. Bir CF nesnesi oluşturuyorsanız, CFRelease () yöntemini kullanmalısınız. Dispatch_queue_create () yöntemini kullanırsanız, dispatch_release () yöntemini kullanmanız gerekir. Sonuç olarak, ARC uyarılarından kaçınmak istiyorsanız, C nesnelerini ve manuel bellek yönetimini kullanarak bunlardan kaçınabilirsiniz. Ayrıca, bu dosyadaki -fno-objc-arc derleyici bayrağını kullanarak her dosya için ARC'yi devre dışı bırakabilirsiniz.
jluckyiv

8
Döküm olmadan olmaz, yapamazsın. Varargs, açıkça yazılan bir argüman listesiyle aynı değildir. Genellikle tesadüfle çalışır, ancak "tesadüfle" doğru olduğunu düşünmüyorum.
bbum

21
Bunu yapma [_controller performSelector:NSSelectorFromString(@"someMethod")];ve objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));eşdeğer değil! Göz at Yöntem İmza uyumsuzluklar ve Objective-C'nin zayıf yazarak büyük bir zayıflık onlar derinlemesine sorunu açıklıyoruz.
9'da 0x 0x

5
@ 0xced Bu durumda sorun yok. objc_msgSend, performSelector: ya da varyantlarında doğru bir şekilde çalışacak herhangi bir seçici için nesne imza uyuşmazlığı oluşturmaz, çünkü nesneleri yalnızca parametre olarak alırlar. Tüm parametreleriniz işaretçiler (nesneler dahil), çiftler ve NSInteger / long olduğu ve dönüş türünüz geçersiz, işaretçi veya uzun olduğu sürece objc_msgSend düzgün çalışacaktır.
Matt Gallagher

88

Hatayı yalnızca perform seçici bulunan dosyada yok saymak için #pragma'yı aşağıdaki gibi ekleyin:

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

Bu, bu satırdaki uyarıyı yok sayar, ancak yine de projenizin geri kalanında buna izin verir.


6
Söz konusu yöntemden hemen sonra uyarıyı tekrar açabileceğinizi düşünüyorum #pragma clang diagnostic warning "-Warc-performSelector-leaks". Bir uyarıyı kapatırsam, mümkün olan en kısa sürede tekrar açmayı severim, bu yüzden yanlışlıkla başka bir beklenmedik uyarının geçmesine izin vermem. Bunun bir sorun olması muhtemel değildir, ancak bir uyarıyı her kapattığımda benim pratiğim.
Rob

2
Ayrıca #pragma clang diagnostic warning push, herhangi bir değişiklik yapmadan önce #pragma clang diagnostic warning popve önceki durumu geri yüklemek için kullanarak önceki derleyici yapılandırma durumunuzu geri yükleyebilirsiniz. Yükleri kapatıyorsanız ve kodunuzda pragma satırlarının yeniden etkinleştirilmesini istemiyorsanız kullanışlıdır.
deanWombourne

Sadece aşağıdaki satırı yoksayacak mı?
hfossli

70

Garip ama gerçek: kabul edilebilirse (yani sonuç geçersizdir ve çalışma döngüsü döngüsüne bir kez izin vermenin sakıncası yoksa), bu sıfır olsa bile bir gecikme ekleyin:

[_controller performSelector:NSSelectorFromString(@"someMethod")
    withObject:nil
    afterDelay:0];

Bu, muhtemelen derleyiciye hiçbir nesnenin geri döndürülemeyeceğini ve bir şekilde yanlış yönetilemeyeceğini bildirdiği için uyarıyı kaldırır.


2
Bunun ilgili bellek yönetimi sorunlarını gerçekten çözüp çözmediğini veya aynı sorunlara sahip olup olmadığını biliyor musunuz, ancak Xcode sizi bu kodla uyarmak için yeterince akıllı değil mi?
Aaron Brager

Bu anlamsal olarak aynı şey değildir! PerformSelector: withObject: AfterDelay: seçiciyi çalışma döngüsünün sonraki çalıştırmasında gerçekleştirir. Bu nedenle, bu yöntem hemen geri döner.
Florian

10
@Florian Tabii ki aynı değil! Cevabımı Oku: Diyorum eğer kabul edilebilir sonuç geçersizdir ve runloop döngüleri çünkü. Budur ilk cümlesi benim cevap.
matt

34

Yukarıda verilen cevaba göre güncellenmiş bir makro. Bu, bir return ifadesiyle bile kodunuzu sarmanıza izin vermelidir.

#define SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(code)                        \
    _Pragma("clang diagnostic push")                                        \
    _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")     \
    code;                                                                   \
    _Pragma("clang diagnostic pop")                                         \


SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(
    return [_target performSelector:_action withObject:self]
);

6
returnmakronun içinde olmak zorunda değildir; return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);ayrıca çalışır ve daha sağlıklı görünür.
uasi

31

Bu kod, derleyici bayrakları veya doğrudan çalışma zamanı çağrılarını içermez:

SEL selector = @selector(zeroArgumentMethod);
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation invoke];

NSInvocationperformSelectorherhangi bir yöntem üzerinde çalışacağından farklı olarak birden çok bağımsız değişkenin ayarlanmasına izin verir .


3
Bunun ilgili bellek yönetimi sorunlarını gerçekten çözüp çözmediğini veya aynı sorunlara sahip olup olmadığını biliyor musunuz, ancak Xcode sizi bu kodla uyarmak için yeterince akıllı değil mi?
Aaron Brager

1
Bellek yönetimi sorunlarını çözdüğünü söyleyebilirsiniz; ancak bunun temelde davranışı belirtmenize izin vermesidir. Örneğin, çağrının bağımsız değişkenleri korumasına izin vermeyi seçebilirsiniz. Şu anki bilgilerime göre, ne yaptığınızı bildiğinize ve yanlış veriler sağlamadığınıza güvenerek ortaya çıkabilecek imza uyuşmazlığı sorunlarını çözmeye çalışır. Tüm kontrollerin çalışma zamanında gerçekleştirilip gerçekleştirilemeyeceğinden emin değilim. Başka bir yorumda belirtildiği gibi, mikeash.com/pyblog/… uyumsuzlukların neler yapabileceğini güzelce açıklar.
Mihai Timar

20

Pek çok cevap burada, ama bu biraz farklı olduğundan, birkaç cevabı birleştirdiğimi düşündüm. Seçicinin geçersiz döndüğünden emin olmak için kontrol eden ve derleyiciyi bastıran bir NSObject kategorisi kullanıyorum uyarı.

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "Debug.h" // not given; just an assert

@interface NSObject (Extras)

// Enforce the rule that the selector used must return void.
- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object;
- (void) performVoidReturnSelector:(SEL)aSelector;

@end

@implementation NSObject (Extras)

// Apparently the reason the regular performSelect gives a compile time warning is that the system doesn't know the return type. I'm going to (a) make sure that the return type is void, and (b) disable this warning
// See http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown

- (void) checkSelector:(SEL)aSelector {
    // See http://stackoverflow.com/questions/14602854/objective-c-is-there-a-way-to-check-a-selector-return-value
    Method m = class_getInstanceMethod([self class], aSelector);
    char type[128];
    method_getReturnType(m, type, sizeof(type));

    NSString *message = [[NSString alloc] initWithFormat:@"NSObject+Extras.performVoidReturnSelector: %@.%@ selector (type: %s)", [self class], NSStringFromSelector(aSelector), type];
    NSLog(@"%@", message);

    if (type[0] != 'v') {
        message = [[NSString alloc] initWithFormat:@"%@ was not void", message];
        [Debug assertTrue:FALSE withMessage:message];
    }
}

- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    // Since the selector (aSelector) is returning void, it doesn't make sense to try to obtain the return result of performSelector. In fact, if we do, it crashes the app.
    [self performSelector: aSelector withObject: object];
#pragma clang diagnostic pop    
}

- (void) performVoidReturnSelector:(SEL)aSelector {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self performSelector: aSelector];
#pragma clang diagnostic pop
}

@end

'V' _C_VOID ile değiştirilmeli mi? _C_VOID, <objc / runtime.h> içinde bildirilmiştir.
Rik Renich

16

Posterity aşkına, şapkamı ringe atmaya karar verdim :)

Son zamanlarda çok görüştüğüm ve daha uzak yeniden target/ selectortür protokoller, bloklar, vb Ancak, bir drop-in orada değiştirilmesi için olduğu gibi şeylerin lehine, paradigma performSelectorşimdi birkaç kez kullandım o:

[NSApp sendAction: NSSelectorFromString(@"someMethod") to: _controller from: nil];

Bunlar için, temiz ARC-kasa ve neredeyse özdeş yedek görünmektedir performSelectorile ilgili çok gerek kalmadan objc_msgSend().

Yine de, iOS'ta kullanılabilir bir analog olup olmadığı hakkında hiçbir fikrim yok.


6
Bu dahil için teşekkürler .. O iOS kullanılabilir: [[UIApplication sharedApplication] sendAction: to: from: forEvent:]. Bir kez baktım, ancak alan adınızın veya hizmetinizin ortasında yalnızca dinamik bir çağrı yapmak için UI ile ilgili bir sınıf kullanmak biraz garip geliyor ... Buna rağmen teşekkürler!
Eduardo Scoz

2
Ew! Daha fazla yükü olacaktır (çünkü yöntemin kullanılabilir olup olmadığını kontrol etmesi ve eğer değilse yanıtlayıcı zincirini yukarı doğru yürümesi gerekir) ve farklı hata davranışları (yanıtlayıcı zincirini yukarı kaldırır ve hiçbir şey bulamazsa NO döndürür) Bu yöntem, yalnızca kilitlenmek yerine, yönteme yanıt verir). idFrom-performSelector:...
tc

2
@tc. to:Nil olmadığı sürece "cevaplayıcı zincirini yukarı doğru yürümez" . Önceden kontrol yapılmadan doğrudan hedeflenen nesneye gider. Yani "daha fazla yük" yok. Harika bir çözüm değil, ama vermenizin nedeni sebep değil. :)
matt

15

Matt Galloway'ın bu konudaki cevabı nedenini açıklıyor:

Aşağıdakileri göz önünde bulundur:

id anotherObject1 = [someObject performSelector:@selector(copy)];
id anotherObject2 = [someObject performSelector:@selector(giveMeAnotherNonRetainedObject)];

Şimdi, ARC birincinin alıkoyma sayısı 1 olan bir nesneyi döndürdüğünü ancak ikincisinin otomatik olarak serbest bırakılan bir nesneyi döndürdüğünü nasıl bilebilir?

Dönüş değerini göz ardı ediyorsanız uyarıyı bastırmanın genellikle güvenli olduğu görülmektedir. PerformSelector'dan tutulan bir nesneyi gerçekten elde etmeniz gerektiğinde en iyi uygulamanın ne olduğundan emin değilim - "bunu yapma" dışında.


14

@ c-road burada sorun açıklaması ile doğru bağlantıyı sağlar . Aşağıda, performSelector bellek sızıntısına neden olduğunda örneğimi görebilirsiniz.

@interface Dummy : NSObject <NSCopying>
@end

@implementation Dummy

- (id)copyWithZone:(NSZone *)zone {
  return [[Dummy alloc] init];
}

- (id)clone {
  return [[Dummy alloc] init];
}

@end

void CopyDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy copy];
}

void CloneDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy clone];
}

void CopyDummyWithLeak(Dummy *dummy, SEL copySelector) {
  __unused Dummy *dummyClone = [dummy performSelector:copySelector];
}

void CloneDummyWithoutLeak(Dummy *dummy, SEL cloneSelector) {
  __unused Dummy *dummyClone = [dummy performSelector:cloneSelector];
}

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    Dummy *dummy = [[Dummy alloc] init];
    for (;;) { @autoreleasepool {
      //CopyDummy(dummy);
      //CloneDummy(dummy);
      //CloneDummyWithoutLeak(dummy, @selector(clone));
      CopyDummyWithLeak(dummy, @selector(copy));
      [NSThread sleepForTimeInterval:1];
    }} 
  }
  return 0;
}

Örneğimde bellek sızıntısına neden olan tek yöntem CopyDummyWithLeak. Bunun nedeni, ARC'nin bilmediği, copySelector'un tutulan nesneyi döndürmesidir.

Bellek Sızıntısı Aracı'nı çalıştırırsanız aşağıdaki resmi görebilirsiniz: resim açıklamasını buraya girin ... ve başka bir durumda bellek sızıntısı olmaz: resim açıklamasını buraya girin


6

Scott Thompson'un makrosunu daha genel hale getirmek için:

// String expander
#define MY_STRX(X) #X
#define MY_STR(X) MY_STRX(X)

#define MYSilenceWarning(FLAG, MACRO) \
_Pragma("clang diagnostic push") \
_Pragma(MY_STR(clang diagnostic ignored MY_STR(FLAG))) \
MACRO \
_Pragma("clang diagnostic pop")

Sonra şu şekilde kullanın:

MYSilenceWarning(-Warc-performSelector-leaks,
[_target performSelector:_action withObject:self];
                )

FWIW, makroyu eklemedim. Birisi bunu yanıtıma ekledi. Şahsen ben makroyu kullanmam. Pragma kodda özel bir vaka etrafında çalışmak için var ve pragmalar neler olduğu konusunda çok açık ve doğrudan. Onları bir makronun arkasına saklamak veya soyutlamak yerine yerinde tutmayı tercih ederim, ama bu sadece benim. YMMV.
Scott Thompson

@ScottThompson Bu adil. Benim için kod tabanım boyunca bu makroyu aramak kolaydır ve genellikle altta yatan sorunla başa çıkmak için susturulmamış bir uyarı eklerim.
Ben Flynn

6

Uyarıları bastırmayın!

Derleyici ile uğraşmak için 12'den az alternatif çözüm yoktur .
İlk uygulama sırasında akıllı davranırken, Dünya üzerindeki birkaç mühendis adımlarınızı takip edebilir ve bu kod sonunda kırılacaktır.

Güvenli Rotalar:

Tüm bu çözümler, orijinal amacınızdan bir dereceye kadar farklılık gösterecek şekilde çalışacaktır. İsterseniz böyle paramolabileceğini varsayın nil:

Güvenli rota, aynı kavramsal davranış:

// GREAT
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

Güvenli rota, biraz farklı davranışlar:

( Bu yanıta bakın )
yerine herhangi bir evre kullanın [NSThread mainThread].

// GOOD
[_controller performSelector:selector withObject:anArgument afterDelay:0];
[_controller performSelector:selector withObject:anArgument afterDelay:0 inModes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorInBackground:selector withObject:anArgument];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

Tehlikeli Rotalar

Bir tür derleyici susturması gerektirir, bu da kırılmaya bağlıdır. Şu anda, bu unutmayın did içinde mola Swift .

// AT YOUR OWN RISK
[_controller performSelector:selector];
[_controller performSelector:selector withObject:anArgument];
[_controller performSelector:selector withObject:anArgument withObject:nil];

3
İfadeler çok yanlış. Güvenli yollar tehlikeli olmaktan daha güvenli değildir. Uyarıyı dolaylı olarak gizlediği için tartışmasız daha tehlikelidir.
Bryan Chen

İfadeyi hakaret etmeyecek şekilde düzelteceğim, ama sözüme dayanıyorum. Susturma uyarısını kabul edilebilir bulduğum tek zaman, kodun sahibi olmadığımdır. Hiçbir mühendis, tüm sonuçları anlamadan sessiz kodu güvenli bir şekilde koruyamaz, bu da bu argümanı okumak anlamına gelir ve bu uygulama oldukça risklidir; özellikle 12, sade İngilizce, sağlam alternatifleri göz önünde bulundurursanız.
SwiftArchitect

1
Hayýr. Demek istediđimi anlamadýn. Kullanımı performSelectorOnMainThreadise değil uyarıyı susturmak için iyi bir yoldur ve yan etkileri vardır. (bellek sızıntısını çözmez) Ekstra #clang diagnostic ignored uyarıyı çok net bir şekilde bastırır.
Bryan Chen

Doğru olmayan bir - (void)yöntemde seçici seçmenin gerçek sorun olduğu doğrudur .
SwiftArchitect

ve bununla birden fazla argümana sahip bir seçiciyi nasıl çağırırsınız ve aynı zamanda güvende olursunuz? @SwiftArchitect
Catalin

4

ARC kullandığınız için iOS 4.0 veya üstünü kullanıyor olmalısınız. Bu, blokları kullanabileceğiniz anlamına gelir. Sizi gerçekleştirmek için seçiciyi hatırlamak yerine bir blok aldıysa, ARC gerçekte neler olduğunu daha iyi izleyebilir ve yanlışlıkla bir bellek sızıntısı yapma riskini çalıştırmak zorunda kalmazsınız.


Aslında, bloklar yanlışlıkla ARC'nin çözmediği bir tutma döngüsü oluşturmayı çok kolaylaştırır. Ben hala selfbir ivar üzerinden (örneğin ivaryerine self->ivar) dolaylı olarak kullandığınızda bir derleyici uyarı olmasını diliyorum .
tc.

-Wimplicit-retain-self gibi mi demek istiyorsun?
OrangeDog

2

Bana bazı problemler veren blok yaklaşımını kullanmak yerine:

    IMP imp = [_controller methodForSelector:selector];
    void (*func)(id, SEL) = (void *)imp;

NSInvocation kullanacağım, şöyle:

    -(void) sendSelectorToDelegate:(SEL) selector withSender:(UIButton *)button 

    if ([delegate respondsToSelector:selector])
    {
    NSMethodSignature * methodSignature = [[delegate class]
                                    instanceMethodSignatureForSelector:selector];
    NSInvocation * delegateInvocation = [NSInvocation
                                   invocationWithMethodSignature:methodSignature];


    [delegateInvocation setSelector:selector];
    [delegateInvocation setTarget:delegate];

    // remember the first two parameter are cmd and self
    [delegateInvocation setArgument:&button atIndex:2];
    [delegateInvocation invoke];
    }

1

Herhangi bir argüman iletmeniz gerekmiyorsa, kolay bir çözüm kullanmaktır valueForKeyPath. Bu bir Classnesne üzerinde bile mümkündür .

NSString *colorName = @"brightPinkColor";
id uicolor = [UIColor class];
if ([uicolor respondsToSelector:NSSelectorFromString(colorName)]){
    UIColor *brightPink = [uicolor valueForKeyPath:colorName];
    ...
}

-2

Burada bir protokol de kullanabilirsiniz. Yani, şöyle bir protokol oluşturun:

@protocol MyProtocol
-(void)doSomethingWithObject:(id)object;
@end

Sınıfınızda seçicinizi çağırması gereken bir @ mülkiyete sahip olursunuz.

@interface MyObject
    @property (strong) id<MyProtocol> source;
@end

@selector(doSomethingWithObject:)MyObject örneğinde çağırmanız gerektiğinde şunu yapın :

[self.source doSomethingWithObject:object];

2
Hey Wu, teşekkürler, ama NSSelectorFromString'i kullanmanın amacı çalışma zamanı sırasında hangi seçiciyi aramak istediğinizi bilmediğiniz zamandır.
Eduardo Scoz
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.