Objective-C'de soyut bir sınıf oluşturma


507

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?


18
Aşağıdaki cevaplar harika. Ben soyut sınıflar sorunu teğetsel olarak özel yöntemlerle ilgili buluyorum - her ikisi de istemci kodunun neler yapabileceğini kısıtlamak için yöntemler ve Objective-C de mevcut değildir. Bence dilin zihniyetinin Java'dan temelde farklı olduğunu anlamaya yardımcı olur. Cevabımı görün: stackoverflow.com/questions/1020070/#1020330
Quinn Taylor

Diğer diller yerine Objective-C topluluğunun zihniyeti hakkında bilgi için teşekkürler. Bu gerçekten ilgili bazı soruları çözer (neden özel yöntemler için basit bir mekanizma vb. Gibi).
Jonathan Arbogast

1
bu yüzden bir java karşılaştırma veren CocoaDev sitesine bir göz atın cocoadev.com/index.pl?AbstractSuperClass

2
Barry düşünce sonra olarak bahsedilse de (yanlış okuyorum eğer beni affet), senin aradığınız düşünüyorum Protokolü Amaç C. See, örneğin, bir protokol nedir? .
jww

Yanıtlar:


633

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.


18
Yanıtın en uygun kısmının sadece yöntemleri tanımlamak için @protocol kullanabileceğinizden bahsettiğini düşünüyorum.
Chad Stewart

13
Netleştirmek için: Bunları beyan bir yöntemlerini @protocoltanımı, ama orada yöntemlerini tanımlamak mümkün değil.
Richard

NSException üzerinde bir kategori yöntemi ile + (instancetype)exceptionForCallingAbstractMethod:(SEL)selectorbu çok iyi çalışıyor.
Patrick Pijnappel

Bu benim için işe yaradı, çünkü IMHO, bir istisna atmak, diğer geliştiriciler için bunun daha çok istenen bir davranış olduğu daha açık doesNotRecognizeSelector.
Chris

Soyut sınıfın burada verilen kısmi uygulama / akış mantığına sahip olduğu protokolleri (tamamen soyut sınıf için) veya şablon yöntemi desenini kullanın stackoverflow.com/questions/8146439/… . Cevabımı aşağıda görebilirsiniz.
hadaytullah

267

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.


5
@Chuck, ben aşağı oy vermedi, ama NSObjectreferans 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 :)
Dan Rosenstark

Neden bu örnekte sadece bir protokol kullanmak istemiyorsunuz? Benim için, bu sadece yöntem saplamaları için bilmek güzel, ama bütün bir soyut sınıf için değil. Bunun için kullanım durumu sınırlı görünmektedir.
lewiguez

Bunu küçümsemedim, ancak init yönteminde bir istisnanın ortaya çıkmasına neden olmanız önerisi, bunun en olası nedenidir. Bir alt sınıf için en yaygın biçim, itaatkar bir şekilde bir istisna atacak olan self = [super init] öğesini çağırarak kendi init yöntemini başlatacaktır. Bu biçim çoğu yöntem için iyi çalışır, ancak alt sınıfın süper uygulama olarak adlandırdığı herhangi bir şeyde asla yapmam.
Xono

5
Kesinlikle Objective-C'de soyut sınıflar oluşturabilirsiniz ve bu oldukça yaygındır. Bunu yapan bir dizi Apple çerçeve sınıfı vardır. Apple'ın soyut sınıfları, genellikle NSInvalidAbstractInvocation () öğesini çağırarak belirli istisnalar (NSInvalidArgumentException) atar. Soyut bir yöntem çağırmak bir programlama hatasıdır, bu yüzden bir istisna atar. Soyut fabrikalar genellikle sınıf kümeleri olarak uygulanır.
quellish

2
@quellish: Dediğiniz gibi: soyut bir yöntem çağırmak bir programlama hatasıdır. Çalışma zamanı hata raporlamasına (NSException) dayanmak yerine bu şekilde ele alınmalıdır. Bu bence Obj-C'de soyut araçların "bu türden bir nesneyi somutlaştıramayacağı" diğer dillerden gelen geliştiriciler için en büyük sorun "bu sınıfı somutlaştırdığınızda işler yanlış gidiyor" anlamına geliyor.
Cross_

60

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. NSObjectDokümanlar).


1
Tabii ki, @TomA, umarım makro yapabileceğiniz diğer kodlar için fikir verir. En çok kullandığım makro tek bir tona basit bir referanstır: kod diyor universe.thingama genişliyor [Universe universe].thing. Çok eğlenceli, binlerce kodun kaydedilmesi ...
Dan Rosenstark

Harika. O olsa biraz, Değişen: #define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil].
noamtm

1
@Yar: Sanmıyorum. Her __PRETTY_FUNCTION__yerde, burada önerildiği gibi bir DLog (...) makrosu aracılığıyla kullanıyoruz: stackoverflow.com/a/969291/38557 .
noamtm

1
daha fazla bilgi için -#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
gbk

1
u örneğin bu sınıfın devralır 10 kez daha sonra temel sınıf ve ekleyip sınıfların birinde bu uygulamaya unuttuysanız gibi kalıtsal bir değil u Baz sınıfının adıyla mesajı alacak 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 implementedHomeViewController Üssü'nden miras nereye - bu daha fazla bilgi verecek
gbk

42

Geldiğim çözüm:

  1. "Soyut" sınıfınızda istediğiniz her şey için bir protokol oluşturun
  2. Protokolü uygulayan bir temel sınıf oluşturun (veya buna soyut olarak da adlandırın). İstediğiniz tüm yöntemler için "abstract" .m dosyasında uygulayın, ancak .h dosyasında değil.
  3. Çocuk sınıfınızın temel sınıftan miras almasını ve protokolü uygulamasını sağlayın.

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.


2
+1 Bu gerçekten Java'daki soyut bir sınıfa en yakın çözümdür. Bu yaklaşımı kendim kullandım ve harika çalışıyor. Protokolü temel sınıfla aynı şekilde adlandırmaya bile izin verilir (tıpkı Apple'ın yaptığı gibi 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.
codingFriend1

Ah, ama soyut sınıfım bir protokolün bir kısmını uygular, geri kalanı alt sınıflar tarafından uygulanır.
Roger CS Wernersson

1
yalnızca alt sınıfların protokolü uygulamasını ve üst sınıf yöntemlerini boş yerine bir arada bırakmasını sağlayabilirsiniz. sonra SuperClass <MyProtocol> türünde özelliklere sahip olun. ayrıca, daha fazla esneklik için protokolünüzde yöntemlerinize @optional ile ön ek ekleyebilirsiniz.
dotToString

Bu, Xcode 5.0.2'de çalışmıyor gibi görünüyor; yalnızca "soyut" sınıf için bir uyarı oluşturur. "Abstract" sınıfının genişletilmemesi, uygun derleyici uyarısı oluşturur, ancak açıkça yöntemleri devralmanıza izin vermez.
Topher Fangio

Bu çözümü tercih ederim ama gerçekten sevmiyorum, projede gerçekten iyi bir kod yapısı değil. // bunu alt sınıflar için temel tür olarak kullanmalıdır. typedef BaseClass <BaseClassProtocol> BASECLASS; Bu sadece bir hafta kuralı, hoşuma gitmedi.
Itachi

35

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

1
-DoesNotRecognizeSelector kullanmayı düşünmemiştim: ve bu yaklaşımı bazı açılardan seviyorum. Herhangi bir derleyici sorunu bu şekilde oluşturulan veya bir istisna oluşturarak bir "soyut" yöntem için bir uyarı yapmak için bir yol biliyor mu? Harika olurdu ...
Quinn Taylor

26
DoesNotRecognizeSelector yaklaşımı Apple'ın önerilen self = [super init] desenini önler.
dlinsin

1
@david: Bütün mesele en kısa zamanda bir istisna oluşturmak olduğundan eminim. İdeal olarak derleme zamanında olmalıdır, ancak bunu yapmanın bir yolu olmadığından, çalışma zamanı istisnasına yerleştiler. Bu, ilk etapta asla üretim kodunda dile getirilmemesi gereken bir iddia başarısızlığına benzer. Aslında, bir kod (false) aslında daha iyi olabilir, çünkü bu kodun asla çalıştırılmaması daha açıktır. Kullanıcı düzeltmek için hiçbir şey yapamaz, geliştirici düzeltmelidir. Bu nedenle, burada bir istisna veya iddiayı başarısızlığa uğratmak iyi bir fikir gibi geliyor.
Nisan'da Senseful

2
Alt sınıfların [super init] 'e mükemmel meşru çağrıları olabileceğinden bunun uygun bir çözüm olduğunu düşünmüyorum.
Raffi Khatchadourian

@dlinsin: Soyut sınıfın init ile başlatılmaması gerekiyorsa, tam olarak istediğiniz şey budur. Alt sınıfları muhtemelen hangi süper yöntemi arayacağını bilir, ancak bu "yeni" veya "tahsis / init" yoluyla dikkatsiz çağrıyı durdurur.
gnasher729

21

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.


Bana göre bu, en azından gerçekten "arayüz" anlamına geldiği zaman (C ++ gibi) soyut sınıfı kullanacağınız durumlar için uygun bir yol gibi görünüyor. Bu yaklaşımın gizli bir dezavantajı var mı?
Şubat'ta

1
@febeling, soyut sınıflar - en azından Java'da - sadece arayüzler değildir. Ayrıca bazı (veya çoğu) davranışları tanımlarlar. Yine de bu yaklaşım bazı durumlarda iyi olabilir.
Dan Rosenstark

Benim alt sınıfları tüm paylaşma (çoğaltma kaldırma) belirli bir işlevsellik uygulamak için bir temel sınıf gerekir, ama aynı zamanda baseclass (soyut bölüm) işlemesi gereken diğer yöntemler için bir protokole ihtiyacım var. Bu yüzden her ikisini de kullanmam gerekiyor ve bu, alt sınıflarınızın kendilerini doğru bir şekilde uyguladığından emin olmak için zor olabileceği bir yer.
LightningStryk

19

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, @dynamicCoreData'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:

  1. Temel sınıftaki her soyut yöntemi uygulayın.
  2. Uygulamayı yap [self doesNotRecognizeSelector:_cmd];
  3. … Ardından, __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.
  4. 2. ve 3. adımları bir makroda birleştirin veya o yöntemin orijinal uygulamasını değiştirmek için uygulama olmadan bir kategoride -[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.)

Neden Bu Yöntemi Seçiyorum

  1. İşi verimli ve biraz rahat bir şekilde halledin.
  2. Anlamak oldukça kolay. (Tamam, bu __builtin_unreachable()insanları şaşırtabilir, ama anlaması da kolay.)
  3. Onay derleme makrolarından birine dayanan bir yaklaşımın aksine, diğer derleyici uyarıları veya hataları oluşturmadan sürüm derlemelerinde çıkarılamaz.

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?


Bu benim yeni favori çözümüm - __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.
Alex MDC

12

Kullanılması @propertyve @dynamicayrıca işe yarayabilir. Dinamik bir özellik bildirir ve eşleşen bir yöntem uygulaması vermezseniz, her şey uyarı olmadan derlenir ve unrecognized selectoreriş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.


7

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.

Misal

Temel sınıf @interfaceetiketinde "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.


1
Anlayamadım. Benim taban sınıfı üzerindeki önerinizi uygulanan -init yöntemi ve şimdi Xcode bana bir miras sınıfın örneği ve bir derleme zamanı hatası oluşur oluşturmak izin vermez kullanılamaz ... . Daha fazla açıklar mısınız?
anonim

-initAlt sınıfınızda bir yöntem bulunduğundan emin olun .
Richard Stelling

2
NS_UNAVAILABLE ile aynıdır ve bu özellik, bu özellikle işaretlenen yöntemi her çağırışınızda bir hata tetikler. Soyut sınıfta nasıl kullanılabileceğini anlamıyorum.
Ben Sinclair

7

Sorunun cevabı, zaten verilen cevapların altındaki yorumlarda dağılmıştır. Yani, ben sadece özetliyor ve basitleştiriyorum.

Seçenek1: Protokoller

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

Seçenek2: Şablon Yöntem Kalıbı

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ü?


6

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


Nil'e geri dönmek verimli olur mu? Yoksa bu bir istisna / başka bir hatanın atılmasına neden olur mu?
user2277872

Peki bu sadece doğrudan bir sınıf kullanmak için programcılar yakalamak için, bence bir iddia bir iyi kullanmak için yapar.
bigkm

5

(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.

ipucu örneği


4

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

3

Muhtemelen bu tür durumlar sadece geliştirme zamanında gerçekleşmelidir, bu yüzden bu işe yarayabilir:

- (id)myMethodWithVar:(id)var {
   NSAssert(NO, @"You most override myMethodWithVar:");
   return nil;
}

3

@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.


2

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.


1

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.


Ama o soyut sınıfın türetilmiş sınıfından [süper init] çağırdığınızda hata alıyorum. Bunu nasıl çözebilirim?
Sahil Doshi

@SahilDoshi Sanırım bu yöntemi kullanmanın dezavantajı bu. Bir sınıfın somutlaştırılmasına izin vermek istemediğimde kullanıyorum ve hiçbir şey o sınıftan miras kalmıyor.
mahoukov

evet, anladım. ancak çözümünüz kimsenin init demesini istemediğimiz singleton sınıfı gibi bir şey yaratmanıza yardımcı olacaktır. soyut sınıf durumunda bildiklerime göre bazı sınıflar miras alacaktır. başka soyut sınıf kullanımı nedir.
Sahil Doshi

0

@ DotToString'in yorumunu uygulayarak @redfood'ın önerisini biraz değiştirerek, aslında Instagram'ın IGListKit tarafından benimsenen çözümünüz var .

  1. Temel (soyut) sınıfta tanımlanmasının anlamı olmayan tüm yöntemler için bir protokol oluşturun, yani çocuklarda belirli uygulamalara ihtiyaç duyarlar.
  2. Bir taban (soyut) sınıf oluşturun gelmez bu protokolü uygulamak. Bu sınıfa, ortak bir uygulamaya sahip olmak için anlamlı olan diğer yöntemleri ekleyebilirsiniz.
  3. Projenizdeki her yere, bir çocuğun bir AbstractClassyönteme girmesi veya bir yöntemle çıkması gerekiyorsa, AbstractClass<Protocol>bunun yerine yazın.

Çünkü AbstractClassuygulamıyor Protocol, bir sağlamanın tek yolu AbstractClass<Protocol>örneği subclassing gereğidir. Gibi AbstractClassyalnız projede yerde kullanılamaz, soyut hale gelir.

Tabii ki bu, tavsiye edilmeyen geliştiricilerin basitçe atıfta AbstractClassbulunan 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 .


0

Aslında, Objective-C'nin soyut sınıfları yoktur, ancak aynı etkiyi elde etmek için Protokolleri kullanabilirsiniz . İşte örnek:

CustomProtocol.h

#import <Foundation/Foundation.h>

@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end

TestProtocol.h

#import <Foundation/Foundation.h>
#import "CustomProtocol.h"

@interface TestProtocol : NSObject <CustomProtocol>

@end

TestProtocol.m

#import "TestProtocol.h"

@implementation TestProtocol

- (void)methodA
{
  NSLog(@"methodA...");
}

- (void)methodB
{
  NSLog(@"methodB...");
}
@end

0

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

-3

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.

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.