Bunun gerçekten harika bir soru olduğunu düşünüyorum ve gerçek soruyu ele almak yerine çoğu cevabın sorunu çözdüğü ve sadece swizzling kullanmamanın söylendiği utanç verici.
Cızırtılı yöntem kullanmak mutfakta keskin bıçak kullanmak gibidir. Bazı insanlar keskin bıçaklardan korkarlar çünkü kendilerini kötü bir şekilde keseceklerini düşünürler, ancak gerçek şu ki keskin bıçaklar daha güvenlidir .
Yöntem swizzling daha iyi, daha verimli, daha sürdürülebilir kod yazmak için kullanılabilir. Ayrıca istismar edilebilir ve korkunç hatalara yol açabilir.
Arka fon
Tüm tasarım modellerinde olduğu gibi, modelin sonuçlarının tamamen farkında olursak, onu kullanıp kullanmama konusunda daha bilinçli kararlar verebiliriz. Singletons oldukça tartışmalı bir şeye iyi bir örnektir ve iyi bir nedenden dolayı - düzgün bir şekilde uygulanması gerçekten zordur. Yine de birçok kişi hala singleton kullanmayı tercih ediyor. Aynı şey swizzling için de söylenebilir. Hem iyiyi hem de kötüyü tam olarak anladıktan sonra kendi fikrinizi oluşturmalısınız.
Tartışma
İşte yöntem swizzling tuzaklarından bazıları:
- Yöntem swizzling atomik değil
- Sahip olunmayan kodun davranışını değiştirir
- Olası adlandırma çakışmaları
- Swizzling, yöntemin argümanlarını değiştirir
- Swizzles sırası önemlidir
- Anlaması zor (özyinelemeli görünüyor)
- Hata ayıklamak zor
Bu noktaların hepsi geçerlidir ve bunları ele alırken hem yöntem swizzling anlayışımızı hem de sonuca ulaşmak için kullanılan metodolojiyi geliştirebiliriz. Her birini birer birer alacağım.
Yöntem swizzling atomik değil
Henüz aynı anda kullanmak güvenli bir yöntem swizzling bir uygulama görmedim 1 . Yöntem swizzling kullanmak istediğiniz vakaların% 95'inde bu bir sorun değildir. Genellikle, bir yöntemin uygulanmasını değiştirmek istersiniz ve bu uygulamanın programınızın tüm ömrü boyunca kullanılmasını istersiniz. Bu, yönteminizi girerek yapmanız gerektiği anlamına gelir +(void)load
. load
Sınıf yöntemi uygulamanın başlangıcında seri yürütülür. Burada dolandırıcılık yaparsanız eşzamanlılık ile ilgili herhangi bir sorun olmaz. +(void)initialize
Bununla birlikte, eğer swizzle içindeyseniz, swizzling uygulamanızda bir yarış koşulu ile sonuçlanabilir ve çalışma zamanı garip bir duruma gelebilir.
Sahip olunmayan kodun davranışını değiştirir
Bu kıpır kıpır bir sorun, ama bütün mesele bu. Amaç bu kodu değiştirebilmektir. İnsanların bunu büyük bir şey olarak göstermesinin nedeni, sadece şeyleri değiştirmek istediğiniz bir örnek için bir NSButton
şeyleri değiştirmek değil, bunun yerine NSButton
uygulamanızdaki tüm örnekler için bir şeyleri değiştirmenizdir . Bu nedenle, swizzle yaparken dikkatli olmalısınız, ancak tamamen kaçınmanıza gerek yoktur.
Şöyle düşünün ... sınıftaki bir yöntemi geçersiz kılarsanız ve süper sınıf yöntemini çağırmazsanız, sorunların ortaya çıkmasına neden olabilirsiniz. Çoğu durumda, süper sınıf bu yöntemin çağrılmasını bekler (aksi belirtilmedikçe). Aynı düşünceyi swizzling'e uygularsanız, çoğu konuyu ele aldınız. Daima orijinal uygulamayı arayın. Eğer yapmazsan, muhtemelen güvende olmak için çok fazla değişiyorsun.
Olası adlandırma çakışmaları
Adlandırma anlaşmazlıkları tüm Kakao genelinde bir sorundur. Kategorilere sınıf adlarını ve yöntem adlarını sık sık önek olarak ekleriz. Ne yazık ki, adlandırma çatışmaları dilimizde bir veba. Yine de dolandırıcılık durumunda olmak zorunda değiller. Sadece yöntem dolandırıcılığı hakkında düşünme şeklimizi değiştirmemiz gerekiyor. Çoğu swizzling böyle yapılır:
@interface NSView : NSObject
- (void)setFrame:(NSRect)frame;
@end
@implementation NSView (MyViewAdditions)
- (void)my_setFrame:(NSRect)frame {
// do custom work
[self my_setFrame:frame];
}
+ (void)load {
[self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];
}
@end
Bu iyi çalışıyor, ancak my_setFrame:
başka bir yerde tanımlanırsa ne olurdu ? Bu sorun dolandırıcılığa özgü değil, ama yine de bu sorunu çözebiliriz. Geçici çözümün diğer tuzaklara karşı ek bir yararı da vardır. Bunun yerine yaptıklarımız:
@implementation NSView (MyViewAdditions)
static void MySetFrame(id self, SEL _cmd, NSRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);
static void MySetFrame(id self, SEL _cmd, NSRect frame) {
// do custom work
SetFrameIMP(self, _cmd, frame);
}
+ (void)load {
[self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];
}
@end
Bu, Objective-C'ye (işlev işaretçileri kullandığından) biraz daha az benzese de, adlandırma çakışmalarını önler. Prensip olarak, standart swizzling ile aynı şeyi yapıyor. Bu, bir süredir tanımlandığı gibi swizzling'i kullanan insanlar için biraz değişiklik olabilir, ancak sonunda, daha iyi olduğunu düşünüyorum. Swizzling yöntemi bu şekilde tanımlanır:
typedef IMP *IMPPointer;
BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
IMP imp = NULL;
Method method = class_getInstanceMethod(class, original);
if (method) {
const char *type = method_getTypeEncoding(method);
imp = class_replaceMethod(class, original, replacement, type);
if (!imp) {
imp = method_getImplementation(method);
}
}
if (imp && store) { *store = imp; }
return (imp != NULL);
}
@implementation NSObject (FRRuntimeAdditions)
+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end
Yöntemleri yeniden adlandırarak dolaşma yöntemin argümanlarını değiştirir
Aklımdaki büyük olan bu. Bu, standart yöntem swizzlinginin yapılmaması gerektiğidir. Özgün yöntemin uygulanmasına iletilen bağımsız değişkenleri değiştiriyorsunuz. İşte burada:
[self my_setFrame:frame];
Bu çizginin yaptığı:
objc_msgSend(self, @selector(my_setFrame:), frame);
Hangi uygulama çalışma aramak için kullanır my_setFrame:
. Uygulama bulunduğunda, aynı argümanlarla uygulamayı başlatır. Bulduğu uygulama orijinal uygulamasıdır setFrame:
, bu yüzden devam eder ve bunu çağırır, ancak _cmd
argüman setFrame:
olması gerektiği gibi değildir . Şimdi my_setFrame:
. Orijinal uygulama, asla almasını beklemediği bir argümanla çağrılıyor. Bu iyi değil.
Basit bir çözüm var - yukarıda tanımlanan alternatif swizzling tekniğini kullanın. Argümanlar değişmeden kalacaktır!
Swizzles sırası önemlidir
Yöntemlerin değişme sırası önemlidir. Varsayım setFrame:
sadece üzerinde tanımlanır, NSView
bu şeylerin sırasını hayal edin:
[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
Üzerinde yöntem NSButton
donduğunda ne olur ? Çoğu swizzling, setFrame:
tüm görünümlerin uygulanmasının yerini almamasını sağlayacaktır , bu nedenle örnek yöntemini çekecektir . Bu, uygulamada alış verişin tüm görünümleri etkilememesi setFrame:
için NSButton
sınıfta yeniden tanımlamak için mevcut uygulamayı kullanır . Mevcut uygulama, tanımlanan uygulamadır NSView
. Aynı şey swizzling yaparken de olur NSControl
(yine NSView
uygulamayı kullanarak ).
Bir setFrame:
düğmeyi çağırdığınızda, swizzled yönteminizi çağırır ve doğrudan setFrame:
başlangıçta tanımlanan yönteme atlar NSView
. NSControl
Ve NSView
swizzled uygulamalar denilen edilmeyecektir.
Peki ya sipariş olsaydı:
[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];
Görünümde şişme ilk olarak gerçekleştiğinden, kontrolde şişme doğru yöntemi çekebilecektir . Benzer şekilde, kontrol dalgalanması düğmenin şişmesinden önce olduğundan, düğme kontrolün swizzled uygulamasını yukarı çekecektir setFrame:
. Bu biraz kafa karıştırıcı, ama bu doğru sıra. Bu şeylerin düzenini nasıl sağlayabiliriz?
Yine, sadece load
işleri karıştırmak için kullanın . İçeri girerseniz load
ve yalnızca yüklenen sınıfta değişiklikler yaparsanız, güvende olursunuz. load
Süper sınıf yük yöntemi, herhangi bir alt sınıfları önce adı verilecek bu yöntem garanti eder. Doğru siparişi alacağız!
Anlaması zor (özyinelemeli görünüyor)
Geleneksel olarak tanımlanmış swizzled yöntemine baktığımızda, neler olduğunu anlatmanın gerçekten zor olduğunu düşünüyorum. Ancak yukarıda dolandırıcılık yapmanın alternatif yoluna bakıldığında, anlaşılması oldukça kolaydır. Bu zaten çözüldü!
Hata ayıklamak zor
Hata ayıklama sırasındaki karışıklıklardan biri, swizzled isimlerin karıştığı ve her şeyin kafanızda karıştığı garip bir geri iz görmektir. Yine, alternatif uygulama bunu ele almaktadır. Geriye çekmelerde açıkça adlandırılmış işlevleri göreceksiniz. Yine de, swizzling'in hatalarını gidermek zor olabilir, çünkü swizzling'in ne gibi bir etkisi olduğunu hatırlamak zor. Kodunuzu iyi bir şekilde belgelendirin (kodu görecek tek kişi olduğunuzu düşünseniz bile). İyi uygulamaları takip edin, iyi olacaksınız. Hata ayıklamak çok iş parçacıklı koddan daha zor değildir.
Sonuç
Doğru şekilde kullanılırsa yöntem swizzling güvenlidir. Alabileceğiniz basit bir güvenlik önlemi sadece içeri girmektir load
. Programlamadaki birçok şey gibi, tehlikeli olabilir, ancak sonuçların anlaşılması onu düzgün bir şekilde kullanmanıza izin verecektir.
1 Yukarıda tanımlanan swizzling yöntemini kullanarak, trambolin kullanacak olursanız iş parçacığını güvenli hale getirebilirsiniz. İki trambolin gerekir. Yöntemin başlangıcında, işaret store
eden adres değişene kadar dönen bir işleve işlev işaretçisini atamanız gerekir store
. Bu, store
işlev işaretçisini ayarlamadan önce swizzled yönteminin çağrıldığı herhangi bir yarış koşulunu önler . Daha sonra uygulamanın sınıfta önceden tanımlanmadığı durumlarda bir trambolin kullanmanız ve trambolin aramasına sahip olmanız ve süper sınıf yöntemini doğru bir şekilde çağırmanız gerekir. Yöntemi dinamik olarak süper uygulamaya bakacak şekilde tanımlamak, dönen çağrıların sırasının önemli olmamasını sağlayacaktır.