Aslen Objective-C ile çalışan bir Java programcısıyım. Soyut bir sınıf oluşturmak istiyorum, ancak Objective-C'de bu mümkün görünmüyor. Mümkün mü?
Değilse, Objective-C'de soyut bir sınıfa ne kadar yaklaşabilirim?
Aslen Objective-C ile çalışan bir Java programcısıyım. Soyut bir sınıf oluşturmak istiyorum, ancak Objective-C'de bu mümkün görünmüyor. Mümkün mü?
Değilse, Objective-C'de soyut bir sınıfa ne kadar yaklaşabilirim?
Yanıtlar:
Tipik olarak, Objective-C sınıfı sadece kural gereği soyuttur; yazar bir sınıfı soyut olarak belgelerse, onu alt sınıflamadan kullanmayın. Ancak, soyut bir sınıfın somutlaştırılmasını engelleyen derleme zamanı uygulaması yoktur. Aslında, bir kullanıcının bir kategori (yani çalışma zamanında) üzerinden soyut yöntemlerin uygulanmasını engelleyecek hiçbir şey yoktur. Soyut sınıfınızda bu yöntemlerin uygulanmasında bir istisna oluşturarak bir kullanıcıyı en azından belirli yöntemleri geçersiz kılmaya zorlayabilirsiniz:
[NSException raise:NSInternalInconsistencyException
format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];
Yönteminiz bir değer döndürürse, kullanımı biraz daha kolaydır
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
userInfo:nil];
o zaman yöntemden bir return ifadesi eklemenize gerek yoktur.
Abstract sınıfı gerçekten bir arayüz ise (yani somut yöntem uygulamaları yoksa), Objective-C protokolünü kullanmak daha uygun seçenektir.
@protocol
tanımı, ama orada yöntemlerini tanımlamak mümkün değil.
+ (instancetype)exceptionForCallingAbstractMethod:(SEL)selector
bu çok iyi çalışıyor.
doesNotRecognizeSelector
.
Hayır, Objective-C'de soyut bir sınıf yaratmanın bir yolu yoktur.
Soyut bir sınıfı taklit edebilirsiniz - yöntemleri / seçicileri doesNotRecognizeSelector çağrısı yaparak: ve bu nedenle sınıfı kullanılamaz hale getiren bir istisna oluşturabilirsiniz.
Örneğin:
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
Bunu init için de yapabilirsiniz.
NSObject
referans bir yöntemi devralmak istemiyorum, bir yöntemi geçersiz kılma zorlamak için değil bunu kullanmanızı önerir. Bunlar aynı şey olsa da, belki :)
Sadece @Barry Wark'ın yukarıdaki cevabını (ve iOS 4.3 için güncelleme) riffleme ve bunu kendi referansım için bırakma:
#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define methodNotImplemented() mustOverride()
o zaman yöntemlerinizde bunu kullanabilirsiniz
- (void) someMethod {
mustOverride(); // or methodNotImplemented(), same thing
}
Notlar: Bir makroyu C işlevi gibi göstermenin iyi bir fikir olup olmadığından emin değilim, ancak tam tersine eğitim verilene kadar saklayacağım. Sanırım NSInvalidArgumentException
(daha ziyade NSInternalInconsistencyException
) kullanmak daha doğru çünkü çalışma zamanı doesNotRecognizeSelector
çağrısında yanıt olarak bu atıyor (bkz. NSObject
Dokümanlar).
universe.thing
ama genişliyor [Universe universe].thing
. Çok eğlenceli, binlerce kodun kaydedilmesi ...
#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
.
__PRETTY_FUNCTION__
yerde, burada önerildiği gibi bir DLog (...) makrosu aracılığıyla kullanıyoruz: stackoverflow.com/a/969291/38557 .
#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'
kasadaki i u da olsun önerdi HomeViewController - method not implemented
HomeViewController Üssü'nden miras nereye - bu daha fazla bilgi verecek
Geldiğim çözüm:
Bu şekilde derleyici, protokolde alt sınıfınız tarafından uygulanmayan herhangi bir yöntem için bir uyarı verecektir.
Java'daki kadar özlü değil, ancak istediğiniz derleyici uyarısını alıyorsunuz.
NSObject
). Daha sonra protokolü ve temel sınıf bildirimini aynı üstbilgi dosyasına koyarsanız, soyut bir sınıftan neredeyse ayırt edilemez.
Gönderen Omni Grup posta listesi :
Objective-C şu anda Java gibi soyut derleyici yapısına sahip değil.
Tek yapmanız gereken soyut sınıfı diğer normal sınıflar olarak tanımlamak ve boş olan ya da seçicinin desteklemediğini bildiren soyut yöntemler için yöntem saplamaları uygulamaktır. Örneğin...
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
Soyut sınıfın varsayılan başlatıcı aracılığıyla başlatılmasını önlemek için aşağıdakileri de yaparım.
- (id)init
{
[self doesNotRecognizeSelector:_cmd];
[self release];
return nil;
}
Soyut bir temel sınıf oluşturmaya çalışmak yerine, bir protokol (Java arabirimine benzer) kullanmayı düşünün. Bu, bir dizi yöntem tanımlamanıza ve daha sonra protokole uyan ve yöntemleri uygulayan tüm nesneleri kabul etmenize olanak tanır. Örneğin, bir İşlem protokolü tanımlayabilir ve sonra böyle bir işlevi olabilir:
- (void)performOperation:(id<Operation>)op
{
// do something with operation
}
Operasyon protokolünü uygulayan herhangi bir nesne op olabilir.
Soyut temel sınıfınızın yöntemleri tanımlamaktan daha fazlasını yapması gerekiyorsa, düzenli bir Objective-C sınıfı oluşturabilir ve bu nesnenin somutlaştırılmasını engelleyebilirsiniz. Sadece - (id) init işlevini geçersiz kılın ve nil veya assert (false) döndürmesini sağlayın. Çok temiz bir çözüm değil, ama Objective-C tamamen dinamik olduğundan, soyut bir temel sınıfa gerçekten eşdeğer bir şey yok.
Bu konu biraz eski ve paylaşmak istediğim şeylerin çoğu zaten burada.
Ancak, en sevdiğim yöntemden bahsedilmiyor ve AFAIK'ın şu anki Clang'da yerel bir desteği yok, işte burada…
Birincisi ve en önemlisi (diğerlerinin zaten işaret ettiği gibi) soyut sınıflar Objective-C'de çok nadirdir - bunun yerine genellikle kompozisyon kullanırız (bazen delegasyon yoluyla). Muhtemelen böyle bir özelliğin, @dynamic
CoreData'nın tanıtılmasına eşlik eden ObjC 2.0'da IIRC'nin eklediği özellikler dışında, dilde / derleyicide zaten bulunmamasının nedeni budur .
Ama heyet (ya da genel olarak kompozisyon) sıra problem çözme için uygun olmadığını sonucuna vardık (! Durumunuza dikkatle değerlendirildikten sonra) aşağıdaki gibi yapılır göz önüne alındığında ben bunu:
[self doesNotRecognizeSelector:_cmd];
…__builtin_unreachable();
geçersiz olmayan yöntemlerle ilgili alacağınız uyarıyı susturmak için “boşluksuz işlevin sonuna erişildiğini kontrol etmeden kontrol et” diyor.-[NSObject doesNotRecognizeSelector:]
kullanarak açıklama ekleyin ve projenizin PCH'sine o kategorinin üstbilgisini ekleyin.__attribute__((__noreturn__))
Şahsen makro versiyonunu tercih ediyorum, çünkü kazanı mümkün olduğunca azaltmama izin veriyor.
İşte burada:
// Definition:
#define D12_ABSTRACT_METHOD {\
[self doesNotRecognizeSelector:_cmd]; \
__builtin_unreachable(); \
}
// Usage (assuming we were Apple, implementing the abstract base class NSString):
@implementation NSString
#pragma mark - Abstract Primitives
- (unichar)characterAtIndex:(NSUInteger)index D12_ABSTRACT_METHOD
- (NSUInteger)length D12_ABSTRACT_METHOD
- (void)getCharacters:(unichar *)buffer range:(NSRange)aRange D12_ABSTRACT_METHOD
#pragma mark - Concrete Methods
- (NSString *)substringWithRange:(NSRange)aRange
{
if (aRange.location + aRange.length >= [self length])
[NSException raise:NSInvalidArgumentException format:@"Range %@ exceeds the length of %@ (%lu)", NSStringFromRange(aRange), [super description], (unsigned long)[self length]];
unichar *buffer = (unichar *)malloc(aRange.length * sizeof(unichar));
[self getCharacters:buffer range:aRange];
return [[[NSString alloc] initWithCharactersNoCopy:buffer length:aRange.length freeWhenDone:YES] autorelease];
}
// and so forth…
@end
Gördüğünüz gibi makro, soyut yöntemlerin tam olarak uygulanmasını sağlayarak gerekli kazan plakasını mutlak bir minimuma indirir.
Daha da iyi bir seçenek , Clang ekibinin özellik talepleri yoluyla bu vaka için bir derleyici niteliği sağlaması için lobi yapmak olabilir. (Daha iyisi, çünkü bu aynı zamanda NSIncrementalStore gibi alt sınıflar oluşturduğunuz senaryolar için derleme zamanı tanılaması sağlar.)
__builtin_unreachable()
insanları şaşırtabilir, ama anlaması da kolay.)Bu son noktanın bir açıklamaya ihtiyacı var sanırım:
Bazı (en çok?) İnsanlar sürüm derlemelerinde iddiaları kaldırırlar. (Bu alışkanlığa katılmıyorum, ama bu başka bir hikaye…) Gerekli bir yöntemi uygulayamama - ancak - kötü , korkunç , yanlış ve temel olarak programınız için evrenin sonu . Programınız bu konuda doğru çalışamıyor çünkü tanımsız ve tanımsız davranış şimdiye kadarki en kötü şey. Bu nedenle, bu teşhisleri yeni teşhis oluşturmadan çıkarabilmek tamamen kabul edilemez olacaktır.
Bu tür programcı hataları için uygun derleme zamanı tanılaması elde edemeyeceğiniz kadar kötüdür ve bunlar için çalışma zamanında keşfe başvurmak zorunda kalırsınız, ancak sürüm sürümlerinde bunun üzerine sıva yapabiliyorsanız, ilk yer?
__builtin_unreachable();
mücevher bu işi mükemmel yapıyor. Makro, kendi kendini belgelemesini sağlar ve bir nesnede eksik bir yöntem çağırırsanız, davranış ne olursa olsun davranır.
Kullanılması @property
ve @dynamic
ayrıca işe yarayabilir. Dinamik bir özellik bildirir ve eşleşen bir yöntem uygulaması vermezseniz, her şey uyarı olmadan derlenir ve unrecognized selector
erişmeye çalışırsanız çalışma zamanında bir hata alırsınız . Bu aslında arama ile aynı şeydir [self doesNotRecognizeSelector:_cmd]
, ancak çok daha az yazarak.
Xcode (clang vb kullanarak) __attribute__((unavailable(...)))
soyut sınıfları etiketlemek için kullanmak istiyorum böylece denemek ve kullanmak eğer bir hata / uyarı olsun.
Yöntemin yanlışlıkla kullanılmasına karşı bir miktar koruma sağlar.
Temel sınıf @interface
etiketinde "soyut" yöntemler:
- (void)myAbstractMethod:(id)param1 __attribute__((unavailable("You should always override this")));
Bu bir adım ileri gidip bir makro oluşturuyorum:
#define UnavailableMacro(msg) __attribute__((unavailable(msg)))
Bu, bunu yapmanızı sağlar:
- (void)myAbstractMethod:(id)param1 UnavailableMacro(@"You should always override this");
Dediğim gibi, bu gerçek bir derleyici koruması değil ama soyut yöntemleri desteklemeyen bir dilde girmeniz kadar iyi.
-init
Alt sınıfınızda bir yöntem bulunduğundan emin olun .
Sorunun cevabı, zaten verilen cevapların altındaki yorumlarda dağılmıştır. Yani, ben sadece özetliyor ve basitleştiriyorum.
Uygulama olmadan soyut bir sınıf oluşturmak istiyorsanız 'Protokoller'i kullanın. Protokolü devralan sınıflar, protokoldeki yöntemleri uygulamakla yükümlüdür.
@protocol ProtocolName
// list of methods and properties
@end
Eğer "Template Method Pattern" gibi kısmi uygulamalarla soyut bir sınıf oluşturmak istiyorsanız çözüm budur. Objective-C - Şablon yöntemleri örüntüsü?
Başka bir alternatif
Sadece ne olursa olsun, Abstract sınıfındaki ve Assert veya Exception'daki sınıfı kontrol edin.
@implementation Orange
- (instancetype)init
{
self = [super init];
NSAssert([self class] != [Orange class], @"This is an abstract class");
if (self) {
}
return self;
}
@end
Bu, geçersiz kılma gereğini ortadan kaldırır init
(daha fazla ilgili öneri)
Ben programcı "çocuktan çağırma" bilmek ve tamamen geçersiz kılmak için bir yol var istedim (benim durumda hala uzatılmamış zaman ebeveyn adına bazı varsayılan işlevsellik sunuyoruz):
typedef void override_void;
typedef id override_id;
@implementation myBaseClass
// some limited default behavior (undesired by subclasses)
- (override_void) doSomething;
- (override_id) makeSomeObject;
// some internally required default behavior
- (void) doesSomethingImportant;
@end
Avantajı, programlayıcının bildirimdeki "geçersiz kılmayı" GÖRECEK ve çağırmamaları gerektiğini bilmesidir [super ..]
.
Kabul edilirse, bunun için bireysel dönüş türlerini tanımlamak çirkin olmakla birlikte, yeterince iyi bir görsel ipucu olarak hizmet eder ve bir alt sınıf tanımında "override_" bölümünü kolayca kullanamazsınız.
Tabii ki bir sınıf isteğe bağlı olduğunda varsayılan bir uygulamaya sahip olabilir. Ancak diğer cevapların söylediği gibi, uygun olduğunda, soyut (sanal) sınıflar gibi bir çalışma zamanı istisnası uygulayın.
Bunun gibi derleyici ipuçlarını inşa etmek güzel olurdu, hatta yorum / dokümantasyon veya ... varsayalım.
Diğer dillerde soyut örnekleme ihlallerini yakalayan derleyiciye alışıksanız, Objective-C davranışı hayal kırıklığı yaratır.
Geç bağlayıcı bir dil olarak, Objective-C'nin bir sınıfın gerçekten soyut olup olmadığı konusunda statik kararlar veremeyeceği açıktır (çalışma zamanında işlev ekliyor olabilirsiniz), ancak tipik kullanım durumlarında bu bir eksiklik gibi görünüyor. Derleyici düz-out çalışma zamanında bir hata atmak yerine soyut sınıf örnekleme önledi tercih ederim.
Başlatıcıları gizlemek için birkaç teknik kullanarak bu tür statik kontrolü elde etmek için kullandığımız bir model:
//
// Base.h
#define UNAVAILABLE __attribute__((unavailable("Default initializer not available.")));
@protocol MyProtocol <NSObject>
-(void) dependentFunction;
@end
@interface Base : NSObject {
@protected
__weak id<MyProtocol> _protocolHelper; // Weak to prevent retain cycles!
}
- (instancetype) init UNAVAILABLE; // Prevent the user from calling this
- (void) doStuffUsingDependentFunction;
@end
//
// Base.m
#import "Base.h"
// We know that Base has a hidden initializer method.
// Declare it here for readability.
@interface Base (Private)
- (instancetype)initFromDerived;
@end
@implementation Base
- (instancetype)initFromDerived {
// It is unlikely that this becomes incorrect, but assert
// just in case.
NSAssert(![self isMemberOfClass:[Base class]],
@"To be called only from derived classes!");
self = [super init];
return self;
}
- (void) doStuffUsingDependentFunction {
[_protocolHelper dependentFunction]; // Use it
}
@end
//
// Derived.h
#import "Base.h"
@interface Derived : Base
-(instancetype) initDerived; // We cannot use init here :(
@end
//
// Derived.m
#import "Derived.h"
// We know that Base has a hidden initializer method.
// Declare it here.
@interface Base (Private)
- (instancetype) initFromDerived;
@end
// Privately inherit protocol
@interface Derived () <MyProtocol>
@end
@implementation Derived
-(instancetype) initDerived {
self= [super initFromDerived];
if (self) {
self->_protocolHelper= self;
}
return self;
}
// Implement the missing function
-(void)dependentFunction {
}
@end
@Yar tarafından önerilen bir yöntemi kullanabilirsiniz (bazı değişikliklerle):
#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
Burada şöyle bir mesaj alacaksınız:
<Date> ProjectName[7921:1967092] <Class where method not implemented> - method not implemented
<Date> ProjectName[7921:1967092] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[<Base class (if inherited or same if not> <Method name>] must be overridden in a subclass/category'
Veya iddia:
NSAssert(![self respondsToSelector:@selector(<MethodName>)], @"Not implemented");
Bu durumda alacaksınız:
<Date> ProjectName[7926:1967491] *** Assertion failure in -[<Class Name> <Method name>], /Users/kirill/Documents/Projects/root/<ProjectName> Services/Classes/ViewControllers/YourClass:53
Ayrıca protokolleri ve diğer çözümleri de kullanabilirsiniz - ancak bu en basitlerinden biridir.
Kakao soyut denen bir şey sağlamaz. Sadece çalışma zamanında kontrol edilen bir sınıf özeti oluşturabiliriz ve derleme zamanında bu kontrol edilmez.
Genellikle soyutlamak istediğim bir sınıfta init yöntemini devre dışı bırakırım:
- (instancetype)__unavailable init; // This is an abstract class.
Bu, o sınıfta init çağırdığınızda derleme zamanında bir hata oluşturur. Sonra her şey için sınıf yöntemleri kullanıyorum.
Objective-C'nin soyut sınıfları bildirmek için yerleşik bir yolu yoktur.
@ DotToString'in yorumunu uygulayarak @redfood'ın önerisini biraz değiştirerek, aslında Instagram'ın IGListKit tarafından benimsenen çözümünüz var .
AbstractClass
yönteme girmesi veya bir yöntemle çıkması gerekiyorsa, AbstractClass<Protocol>
bunun yerine yazın.Çünkü AbstractClass
uygulamıyor Protocol
, bir sağlamanın tek yolu AbstractClass<Protocol>
örneği subclassing gereğidir. Gibi AbstractClass
yalnız projede yerde kullanılamaz, soyut hale gelir.
Tabii ki bu, tavsiye edilmeyen geliştiricilerin basitçe atıfta AbstractClass
bulunan ve artık (artık değil) soyut sınıfın bir örneğine izin veren yeni yöntemler eklemelerini engellemez .
Gerçek dünya örneği: IGListKit'inIGListSectionController
protokolü uygulamayan bir temel sınıfı vardır IGListSectionType
, ancak o sınıfın bir örneğini gerektiren her yöntem aslında türü sorar IGListSectionController<IGListSectionType>
. Bu nedenle IGListSectionController
, kendi türlerinde yararlı olan herhangi bir şey için bir tür nesne kullanmanın bir yolu yoktur .
Aslında, Objective-C'nin soyut sınıfları yoktur, ancak aynı etkiyi elde etmek için Protokolleri kullanabilirsiniz . İşte örnek:
#import <Foundation/Foundation.h>
@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end
#import <Foundation/Foundation.h>
#import "CustomProtocol.h"
@interface TestProtocol : NSObject <CustomProtocol>
@end
#import "TestProtocol.h"
@implementation TestProtocol
- (void)methodA
{
NSLog(@"methodA...");
}
- (void)methodB
{
NSLog(@"methodB...");
}
@end
Soyut bir sınıf yaratmanın basit bir örneği
// Declare a protocol
@protocol AbcProtocol <NSObject>
-(void)fnOne;
-(void)fnTwo;
@optional
-(void)fnThree;
@end
// Abstract class
@interface AbstractAbc : NSObject<AbcProtocol>
@end
@implementation AbstractAbc
-(id)init{
self = [super init];
if (self) {
}
return self;
}
-(void)fnOne{
// Code
}
-(void)fnTwo{
// Code
}
@end
// Implementation class
@interface ImpAbc : AbstractAbc
@end
@implementation ImpAbc
-(id)init{
self = [super init];
if (self) {
}
return self;
}
// You may override it
-(void)fnOne{
// Code
}
// You may override it
-(void)fnTwo{
// Code
}
-(void)fnThree{
// Code
}
@end
Sadece bir temsilci oluşturamaz mısınız?
Bir temsilci, hangi işlevlerin tanımlanması gerektiğini söylediğiniz anlamında soyut bir temel sınıf gibidir, ancak aslında bunları tanımlamazsınız.
Daha sonra, temsilcinizi (yani soyut sınıf) her uyguladığınızda, derleyici tarafından davranışı tanımlamak için hangi isteğe bağlı ve zorunlu işlevler konusunda uyarılırsınız.
Bu bana soyut bir temel sınıf gibi geliyor.