Gizleme uyarısı "Kategori, birincil sınıfı tarafından da uygulanacak bir yöntem uyguluyor"


98

Uyarıyı nasıl bastıracağımı merak ediyordum:

Kategori, birincil sınıfı tarafından da uygulanacak bir yöntemi uygulamaktadır.

Buna belirli bir kod kategorisi için sahibim:

+ (UIFont *)systemFontOfSize:(CGFloat)fontSize {
    return [self aCustomFontOfSize:fontSize];
}

Yöntemle dövüşerek. Bunu yapmasam da - belki bunun yerine aynı yöntemi geçersiz kılan bir UIFont alt sınıfı oluşturabilir ve superbaşka türlü çağırabilirsiniz .
Alan Zeino

4
Senin sorunun uyarı değil. Senin sorunun, aynı yöntem adına sahip olman ve bu da sorunlara yol açacak.
gnasher729

Neden kategorileri kullanan yöntemleri geçersiz kılmamanız gerektiğine ilişkin nedenler ve alternatif çözümler için Objective-C'de kategorileri kullanarak yöntemleri geçersiz kılma konusuna bakın .
Duyarlı

Uygulama genelinde yazı tipini ayarlamak için daha zarif bir çözüm biliyorsanız, bunu gerçekten duymak isterim!
To1ne

Yanıtlar:


64

Kategori, mevcut bir sınıfa yeni yöntemler eklemenize olanak tanır. Sınıfta zaten var olan bir yöntemi yeniden uygulamak istiyorsanız, genellikle kategori yerine bir alt sınıf oluşturursunuz.

Apple belgeleri: Mevcut sınıfları özelleştirme

Bir kategoride bildirilen bir yöntemin adı, orijinal sınıftaki bir yöntemle veya aynı sınıftaki (veya hatta bir üst sınıf) başka bir kategorideki bir yöntemle aynıysa, davranış, hangi yöntem uygulamasının kullanılacağına ilişkin tanımsızdır. Çalışma süresi.

Aynı sınıfta tamamen aynı imzaya sahip iki yöntem, öngörülemeyen davranışlara yol açar, çünkü her arayan, hangi uygulamayı istediklerini belirleyemez.

Bu nedenle, bir kategori kullanmalı ve sınıf için yeni ve benzersiz yöntem adları sağlamalısınız veya bir sınıfta var olan bir yöntemin davranışını değiştirmek istiyorsanız alt sınıf sağlamalısınız.


1
Yukarıda açıklanan fikirlere tamamen katılıyorum ve geliştirme sırasında bunları takip etmeye çalışıyorum. ancak yine de kategorideki geçersiz kılma yöntemlerinin uygun olabileceği durumlar olabilir. örneğin, çoklu kalıtımın (c ++ 'da olduğu gibi) veya arayüzlerin (c #' de olduğu gibi) kullanılabileceği durumlar. projemde bununla yeni yüzleştim ve kategorilerdeki geçersiz kılma yöntemlerinin en iyi seçim olduğunu fark ettim.
peetonn

4
İçinde singleton bulunan bazı kodları birim test ederken faydalı olabilir. İdeal olarak, tekil kodlar bir protokol olarak koda enjekte edilmeli ve uygulamayı değiştirmenize izin vermelidir. Ancak kodunuzun içine zaten gömülü bir tane varsa, birim testinize bir tekil kategorisi ekleyebilir ve bunları kukla nesnelere dönüştürmek için paylaşılan durum ve kontrol ettiğiniz yöntemleri geçersiz kılabilirsiniz.
bandejapaisa

Teşekkürler @ PsychoDad. Bağlantıyı güncelledim ve bu gönderiyle ilgili belgelerden bir alıntı ekledim.
bneely

İyi görünüyor. Apple, mevcut bir yöntem adıyla bir kategoriyi kullanma davranışına ilişkin belgeler sağlıyor mu?
jjxtra

1
harika, kategori mi yoksa alt sınıf mı kullanmam gerektiğinden emin değildim :-)
kernix

343

Söylenen her şey doğru olsa da, aslında uyarının nasıl bastırılacağı sorunuzu yanıtlamaz.

Herhangi bir nedenle bu koda sahip olmanız gerekiyorsa (benim durumumda projemde HockeyKit var ve bunlar UIImage kategorisindeki bir yöntemi geçersiz kılıyor [düzenleme: bu artık geçerli değil]) ve projenizi derlemeniz gerekiyorsa , #pragmauyarıyı şu şekilde engellemek için ifadeler kullanabilirsiniz :

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"

// do your override

#pragma clang diagnostic pop

Bilgileri burada buldum: http://www.cocoabuilder.com/archive/xcode/313767-disable-warning-for-override-in-category.html


Çok teşekkürler! Yani pragmaların uyarıları da bastırdığını görüyorum. :-p
Constantino Tsarouhas

Evet, bunlar LLVM'ye özgü ifadeler olsa da, GCC için de benzer olanlar var.
Ben Baron

1
Test projenizdeki uyarı bir bağlayıcı uyarısıdır, bir llvm derleyici uyarısı değildir, bu nedenle llvm pragma hiçbir şey yapmaz. Bununla birlikte, test projenizin hala bir bağlayıcı uyarısı olduğu için "uyarıları hata olarak ele al" seçeneği açıkken oluşturulduğunu fark edeceksiniz.
Ben Baron

12
Soruyu gerçekten cevapladığı düşünüldüğünde, bu gerçekten kabul edilen cevap olmalıdır.
Rob Jones 13

1
Bu cevap doğru cevap olmalıdır. Her neyse, cevap olarak seçilenden daha fazla oyu var.
Juan Katalan

20

Daha iyi bir alternatif (bu uyarının sizi neden felaketten kurtardığına dair bneely'nin cevabına bakın), yöntemle karıştırmayı kullanmaktır. Metot karıştırmayı kullanarak, bir kategorideki mevcut bir metodu kimin "kazandığına" dair belirsizlik olmadan ve eski metoda çağrı yapma yeteneğini koruyarak değiştirebilirsiniz. İşin sırrı, geçersiz kılmaya farklı bir yöntem adı vermek, ardından bunları çalışma zamanı işlevlerini kullanarak değiştirmektir.

#import <objc/runtime.h> 
#import <objc/message.h>

void MethodSwizzle(Class c, SEL orig, SEL new) {
    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);
    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    else
    method_exchangeImplementations(origMethod, newMethod);
}

Ardından özel uygulamanızı tanımlayın:

+ (UIFont *)mySystemFontOfSize:(CGFloat)fontSize {
...
}

Varsayılan uygulamayı sizinkiyle geçersiz kılın:

MethodSwizzle([UIFont class], @selector(systemFontOfSize:), @selector(mySystemFontOfSize:));

10

Bunu kodunuzda deneyin:

+(void)load{
    EXCHANGE_METHOD(Method1, Method1Impl);
}

UPDATE2: Bu makroyu ekle

#import <Foundation/Foundation.h>
#define EXCHANGE_METHOD(a,b) [[self class]exchangeMethod:@selector(a) withNewMethod:@selector(b)]

@interface NSObject (MethodExchange)
+(void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel;
@end

#import <objc/runtime.h>

@implementation NSObject (MethodExchange)

+(void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel{
    Class class = [self class];

    Method origMethod = class_getInstanceMethod(class, origSel);
    if (!origMethod){
        origMethod = class_getClassMethod(class, origSel);
    }
    if (!origMethod)
        @throw [NSException exceptionWithName:@"Original method not found" reason:nil userInfo:nil];
    Method newMethod = class_getInstanceMethod(class, newSel);
    if (!newMethod){
        newMethod = class_getClassMethod(class, newSel);
    }
    if (!newMethod)
        @throw [NSException exceptionWithName:@"New method not found" reason:nil userInfo:nil];
    if (origMethod==newMethod)
        @throw [NSException exceptionWithName:@"Methods are the same" reason:nil userInfo:nil];
    method_exchangeImplementations(origMethod, newMethod);
}

@end

1
Bu tam bir örnek değil. EXCHANGE_METHOD adlı hiçbir makro, aslında amaç-c çalışma zamanı tarafından tanımlanmamıştır.
Richard J. Ross III

@Vitaly stil -1. bu yöntem sınıf türü için uygulanmaz. Hangi çerçeveyi kullanıyorsunuz?
Richard J. Ross III

Tekrar üzgünüm, bunu deneyin NSObject + MethodExchange
Vitaliy Gervazuk

NSObject'teki kategori ile neden makro ile uğraşasınız ki? Neden sadece 'exchangeMethod' ile dalga geçmiyorsun?
hvanbrug

5

Bu derleyici uyarısını bastırmak için yöntem değiştirmeyi kullanabilirsiniz. UITextBorderStyleNone ile özel bir arka plan kullandığımızda, bir UITextField'da kenar boşlukları çizmek için yöntem değiştirmeyi nasıl uyguladım:

#import <UIKit/UIKit.h>

@interface UITextField (UITextFieldCatagory)

+(void)load;
- (CGRect)textRectForBoundsCustom:(CGRect)bounds;
- (CGRect)editingRectForBoundsCustom:(CGRect)bounds;
@end

#import "UITextField+UITextFieldCatagory.h"
#import <objc/objc-runtime.h>

@implementation UITextField (UITextFieldCatagory)

+(void)load
{
    Method textRectForBounds = class_getInstanceMethod(self, @selector(textRectForBounds:));
    Method textRectForBoundsCustom = class_getInstanceMethod(self, @selector(textRectForBoundsCustom:));

    Method editingRectForBounds = class_getInstanceMethod(self, @selector(editingRectForBounds:));
    Method editingRectForBoundsCustom = class_getInstanceMethod(self, @selector(editingRectForBoundsCustom:));


    method_exchangeImplementations(textRectForBounds, textRectForBoundsCustom);
    method_exchangeImplementations(editingRectForBounds, editingRectForBoundsCustom);

}


- (CGRect)textRectForBoundsCustom:(CGRect)bounds
{
    CGRect inset = CGRectMake(bounds.origin.x + 10, bounds.origin.y, bounds.size.width - 10, bounds.size.height);
    return inset;
}

- (CGRect)editingRectForBoundsCustom:(CGRect)bounds
{
    CGRect inset = CGRectMake(bounds.origin.x + 10, bounds.origin.y, bounds.size.width - 10, bounds.size.height);
    return inset;
}

@end

2

Üstüne binme özellikleri, bir Sınıf Uzantısı (Anonim Kategori) için geçerlidir, ancak normal bir Kategori için geçerli değildir.

Bir Sınıf Uzantısı (Anonim Kategori) kullanan Apple Docs'a göre, genel bir sınıfa yönelik bir Özel arabirim oluşturabilirsiniz, böylece özel arabirim herkese açık özellikleri geçersiz kılabilir. başka bir deyişle, bir özelliği salt okunurdan okuma-yazmaya değiştirebilirsiniz.

Bunun bir kullanım örneği, ortak özelliklere erişimi kısıtlayan kitaplıklar yazarken, aynı özelliğin kitaplık içinde tam okuma yazma erişimine ihtiyaç duymasıdır.

Apple Docs bağlantısı: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html

" Özel Bilgileri Gizlemek için Sınıf Uzantılarını Kullan " araması yapın .

Dolayısıyla bu teknik, Sınıf Uzantısı için geçerlidir ancak Kategori için geçerli değildir.


1

Kategoriler iyi bir şeydir, ancak kötüye kullanılabilirler. Kategoriler yazarken, ilke olarak mevcut yöntemleri yeniden UYGULAMAMALISINIZ. Bunu yapmak, başka bir sınıfın bağlı olduğu kodu yeniden yazarken garip yan etkilere neden olabilir. bilinen bir sınıfı kırabilir ve hata ayıklayıcınızı tersyüz edebilirsiniz. Bu sadece kötü bir programlamadır.

Eğer yapmanız gerekiyorsa, gerçekten alt sınıflara ayırmalısınız.

Sonra swizzling önerisi, benim için büyük bir HAYIR-HAYIR-HAYIR.

Çalışma zamanında onu değiştirmek tam bir HAYIR-HAYIR-HAYIR.

Muzun portakal gibi görünmesini istiyorsun, ama sadece çalışma zamanında mı? Portakal istiyorsanız, bir portakal yazın.

Bir muzu portakal gibi göstermeyin. Daha da kötüsü: muzunuzu, portakalları desteklemek için dünya çapında sessizce muzları sabote edecek gizli bir ajana dönüştürmeyin.

Eyvah!


3
Yine de, çalışma zamanında swizz yapmak, test ortamındaki alay davranışları için yararlı olabilir.
Ben G

2
Esprili olmasına rağmen, cevabınız tüm olası yöntemlerin kötü olduğunu söylemekten fazlasını yapmıyor. Canavarın doğası, bazen gerçekten alt sınıflara giremeyeceğinizdir, bu nedenle bir kategori ile kalırsınız ve özellikle de kategorize ettiğiniz sınıfın koduna sahip değilseniz, bazen swizzle'ı yapmanız gerekir ve istenmeyen bir yöntem olmak konuyla ilgili değildir.
hvanbrug

1

Ana sınıf yerine bir kategoride bir temsilci yöntemini uyguladığımda bu sorunu yaşadım (ana sınıf uygulaması olmamasına rağmen). Benim için çözüm, ana sınıf başlık dosyasından kategori başlık dosyasına taşımaktı Bu iyi çalışıyor

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.