-İnit yöntemini Objective-C'de özel yapmak mümkün müdür?


147

-initSınıfımın yöntemini Objective-C'de gizlemem (özel yapmam) gerekiyor .

Bunu nasıl yapabilirim?


3
Aşağıda bu cevapta gösterildiği gibi, bunu başarmak için özel, temiz ve açıklayıcı bir tesis var . Özellikle: NS_UNAVAILABLE. Genel olarak bu yaklaşımı kullanmanızı tavsiye ederim. OP, kabul edilen cevabını gözden geçirmeyi düşünür mü? Buradaki diğer cevaplar pek çok yararlı ayrıntı sağlar, ancak bunu başarmak için tercih edilen yöntem değildir.
Benjohn

Aşağıda diğerlerinin de belirttiği gibi, NS_UNAVAILABLEarayanın initdolaylı olarak aracılığıyla aramasına izin verir new. initGeri dönmeyi basitçe geçersiz kılmak nilher iki durumu da ele alır.
Greg Brown

elbette 'yeni' NS_UNAVAILABLE ve 'init' yapabilirsiniz - ki bu günümüzde yaygın bir uygulamadır.
Motti shneor

Yanıtlar:


88

Smalltalk gibi Objective-C, "özel" ve "genel" yöntemler kavramına sahip değildir. Herhangi bir mesaj herhangi bir zamanda herhangi bir nesneye gönderilebilir.

Ne yapabilirsiniz bir atmak olduğunu NSInternalInconsistencyExceptionda eğer -inityöntem çağrılır:

- (id)init {
    [self release];
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:@"-init is not a valid initializer for the class Foo"
                                 userInfo:nil];
    return nil;
}

Diğer alternatif - muhtemelen pratikte çok daha iyidir - -initmümkünse sınıfınız için mantıklı bir şey yapmaktır .

Bir singleton nesnesinin kullanılmasını "sağlamaya" çalıştığınız için bunu yapmaya çalışıyorsanız, zahmet etmeyin. Özellikle, rahatsız etmeyin "geçersiz kılma +allocWithZone:, -init, -retain, -release" singletons yaratma yöntemi. Neredeyse her zaman gereksizdir ve gerçek bir avantaj sağlamadan sadece karmaşıklık ekler.

Bunun yerine, kodunuzu, +sharedWhateveryönteminiz bir tekil kişiye nasıl erişeceğiniz şeklinde yazın ve bunu, başlığınızdaki tekli örneği almanın yolu olarak belgeleyin. Çoğu durumda ihtiyacınız olan tek şey bu olmalıdır.


2
Burada iade gerçekten gerekli mi?
philsquared

5
Evet, derleyiciyi mutlu etmek için. Aksi takdirde derleyici, geçersiz olmayan dönüşlü bir yöntemden geri dönüş olmadığından şikayet edebilir.
Chris Hanson

Komik, benim için değil. Belki farklı bir derleyici sürümü veya anahtarlar? (Sadece XCode 3.1 ile varsayılan gcc anahtarlarını kullanıyorum)
philsquared

3
Bir modeli takip etmesi için geliştiriciye güvenmek iyi bir fikir değildir. Bir istisna atmak daha iyidir, bu nedenle farklı bir takımdaki geliştiriciler yapmamayı bilir. Ben özel konsept daha iyi olurdu.
Nick Turner

4
"Gerçekten önemli bir avantaj için" . Tamamen yanlış. Önemli avantaj, tekli kalıbı uygulamak istemenizdir . Yeni örneklerin oluşturulmasına izin verirseniz, API'ye aşina olmayan bir geliştirici , doğru sınıfa sahip oldukları, ancak yanlış örneğe sahip oldukları için kodunu kullanabilir allocve inityanlış çalışabilir. Bu, OO'da kapsülleme ilkesinin özüdür . API'lerinizde diğer sınıfların ihtiyaç duymaması veya erişmemesi gereken şeyleri gizlersiniz. Her şeyi sadece halka açık tutmaz ve insanların hepsini takip etmesini beklemezsiniz.
Nate

350

NS_UNAVAILABLE

- (instancetype)init NS_UNAVAILABLE;

Bu, kullanılamayan özniteliğin kısa bir sürümüdür. İlk olarak macOS 10.7 ve iOS 5'te ortaya çıktı . NSObjCRuntime.h'de olarak tanımlanır #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE.

Yöntemi ObjC kodu için değil, yalnızca Swift istemcileri için devre dışı bırakan bir sürüm var :

- (instancetype)init NS_SWIFT_UNAVAILABLE;

unavailable

Herhangi bir init çağrısında unavailablebir derleyici hatası oluşturmak için özniteliği başlığa ekleyin .

-(instancetype) init __attribute__((unavailable("init not available")));  

derleme zamanı hatası

Bir sebebiniz yoksa, sadece yazın __attribute__((unavailable)), hatta __unavailable:

-(instancetype) __unavailable init;  

doesNotRecognizeSelector:

doesNotRecognizeSelector:NSInvalidArgumentException oluşturmak için kullanın . "Çalışma zamanı sistemi, bir nesne yanıt veremediği veya iletemediği bir aSelector mesajı aldığında bu yöntemi çağırır."

- (instancetype) init {
    [self release];
    [super doesNotRecognizeSelector:_cmd];
    return nil;
}

NSAssert

NSAssertNSInternalInconsistencyException'ı atmak ve bir mesaj göstermek için kullanın :

- (instancetype) init {
    [self release];
    NSAssert(false,@"unavailable, use initWithBlah: instead");
    return nil;
}

raise:format:

raise:format:Kendi istisnanızı atmak için kullanın :

- (instancetype) init {
    [self release];
    [NSException raise:NSGenericException 
                format:@"Disabled. Use +[[%@ alloc] %@] instead",
                       NSStringFromClass([self class]),
                       NSStringFromSelector(@selector(initWithStateDictionary:))];
    return nil;
}

[self release]nesneye zaten allocatanmış olduğu için gereklidir . ARC'yi kullanırken derleyici sizin için arayacaktır. Her durumda, infazı kasıtlı olarak durdurmak üzereyken endişelenecek bir şey değil.

objc_designated_initializer

initBelirlenmiş bir başlatıcıyı kullanmaya zorlamak için devre dışı bırakmayı planlıyorsanız , bunun için bir öznitelik vardır:

-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;

Bu, başka herhangi bir başlatıcı yöntemi myOwnInitdahili olarak çağırmadığı sürece bir uyarı oluşturur . Ayrıntılar, bir sonraki Xcode sürümünden sonra Modern Objective-C'de yayınlanacak (sanırım).


Bu, dışındaki yöntemler için iyi olabilir init. Öyleyse, bu yöntem geçersizse, neden bir nesneyi başlatıyorsunuz? Ayrıca, bir istisna atarken init*, geliştiriciye doğru yöntemi bildiren bazı özel mesajlar belirleyebileceksiniz , ancak bu durumda böyle bir seçeneğiniz yok doesNotRecognizeSelector.
Aleks N.

Hiçbir fikrim yok Aleks, bu orada olmamalı :) Cevabı düzenledim.
Jano

Bu garip çünkü sonunda sisteminizin çökmesine neden oldu. Sanırım bir şeyin olmasına izin vermemek daha iyidir ama daha iyi bir yol olup olmadığını merak ediyorum. Diğer geliştiricilerin onu
arayamaması

1
Bunu denedim ve çalışmıyor: - (id) init __attribute __ ((kullanılamıyor ("init mevcut değil"))) {NSAssert (false, @ "Use initWithType"); sıfır dönüş; }
okysabeni

1
@Miraaj Derleyiciniz tarafından desteklenmiyor gibi görünüyor. Xcode 6'da desteklenmektedir. Eğer bir başlatıcı belirlenmiş olanı çağırmazsa, "Başka bir başlatıcıya 'kendi kendine' bir çağrıyı kaçıran Kullanışlı başlatıcıyı" almalısınız.
Jano

101

Apple, init yapıcısını devre dışı bırakmak için başlık dosyalarında aşağıdakileri kullanmaya başladı:

- (instancetype)init NS_UNAVAILABLE;

Bu, Xcode'da bir derleyici hatası olarak doğru şekilde görüntülenir. Özellikle, bu, HealthKit başlık dosyalarının birçoğunda ayarlanır (HKUnit bunlardan biridir).


3
Yine de [MyObject new] ile nesneyi başlatabileceğinizi unutmayın;
José

11
Ayrıca + (örnek türü) yeni NS_UNAVAILABLE da yapabilirsiniz;
sonicfly

@sonicfly Bunu yapmaya çalıştı ama proje hala
derleniyor

3

Varsayılan -init yönteminden bahsediyorsanız, o zaman yapamazsınız. NSObject'ten miras alınır ve her sınıf ona herhangi bir uyarı vermeden yanıt verir.

-İnitMyClass gibi yeni bir yöntem oluşturabilir ve bunu Matt'in önerdiği gibi özel bir kategoriye koyabilirsiniz. Ardından, çağrılırsa bir istisna oluşturmak veya (daha iyi) özel -initMyClass'ınızı bazı varsayılan değerlerle çağırmak için varsayılan -init yöntemini tanımlayın.

İnsanların init'i gizlemek istemesinin ana nedenlerinden biri, tekli nesneler içindir . Eğer durum buysa, o zaman -init'i gizlemenize gerek yoktur, bunun yerine tekli nesneyi döndürmeniz yeterlidir (veya henüz yoksa oluşturun).


Bu, 'init'i tekliğinizde yalnız bırakmaktan ve kullanıcıya' sharedWhatever 'aracılığıyla erişmeleri gerektiğini bildirmek için belgelere güvenmekten daha iyi bir yaklaşım gibi görünüyor. İnsanlar genellikle bir sorunu çözmek için dakikalar harcayana kadar dokümanları okumazlar.
Greg Maletic


3

Kullanılamayacak herhangi bir yöntemi kullanarak beyan edebilirsiniz NS_UNAVAILABLE.

Böylece bu satırları @ arayüzünüzün altına koyabilirsiniz

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

Önek başlığınızda daha da iyi bir makro tanımlayın

#define NO_INIT \
- (instancetype)init NS_UNAVAILABLE; \
+ (instancetype)new NS_UNAVAILABLE;

ve

@interface YourClass : NSObject
NO_INIT

// Your properties and messages

@end

2

Bu, "özel yapmak" ile ne demek istediğine bağlı. Amaç-C'de, bir nesnede bir yöntemi çağırmak, o nesneye bir mesaj göndermek olarak daha iyi tanımlanabilir. Dilde, bir istemcinin bir nesnede belirli bir yöntemi çağırmasını engelleyen hiçbir şey yoktur; yapabileceğiniz en iyi şey, yöntemi başlık dosyasında bildirmemektir. Bir istemci yine de doğru imzayla "özel" yöntemi çağırırsa, yine de çalışma zamanında yürütülür.

Bununla birlikte, Objective-C'de özel bir yöntem oluşturmanın en yaygın yolu , uygulama dosyasında bir Kategori oluşturmak ve oradaki tüm "gizli" yöntemleri bildirmektir. Unutmayın ki, bu çağrıların initçalışmasını gerçekten engellemeyecektir , ancak herhangi biri bunu yapmaya çalışırsa derleyici uyarılar verecektir.

Sınıfım.m

@interface MyClass (PrivateMethods)
- (NSString*) init;
@end

@implementation MyClass

- (NSString*) init
{
    // code...
}

@end

İyi bir var parçacığı bu konu hakkında MacRumors.com üzerinde.


3
Ne yazık ki, bu durumda kategori yaklaşımı gerçekten yardımcı olmayacak. Normalde size yöntemin sınıfta tanımlanmamış olabileceğine dair derleme zamanı uyarıları satın alır. Bununla birlikte, MyClass'ın kök sınıflardan birinden miras alması gerektiğinden ve init'i tanımladıkları için herhangi bir uyarı olmayacaktır.
Barry Wark

2

Peki, bunu neden "özel / görünmez" yapamamanızdaki sorun, init yönteminin YourClass'a değil, id'ye gönderilmesidir

Derleyicinin (denetleyici) noktasından, bir id'nin şimdiye kadar yazılan herhangi bir şeye potansiyel olarak yanıt verebileceğini unutmayın (çalışma zamanında kimliğe gerçekten neyin girdiğini kontrol edemez), bu nedenle init'i yalnızca hiçbir yerde hiçbir şey yapmadığında gizleyebilirsiniz (publicly = in üstbilgi) bir yöntem init kullanın, derlemenin bildiğinden daha fazla, id'nin init'e yanıt vermesinin bir yolu yoktur, çünkü hiçbir yerde init yoktur (kaynağınızda, tüm kitaplıklar vb.)

bu nedenle kullanıcının init'i geçmesini ve derleyici tarafından parçalanmasını yasaklayamazsınız ... ancak yapabileceğiniz şey, kullanıcının bir init'i çağırarak gerçek bir örnek almasını önlemektir.

basitçe, nil döndüren ve başka birinin alamayacağı bir (özel / görünmez) başlatıcıya sahip olan init'i uygulayarak (initOnce, initWithSpecial ...)

static SomeClass * SInstance = nil;

- (id)init
{
    // possibly throw smth. here
    return nil;
}

- (id)initOnce
{
    self = [super init];
    if (self) {
        return self;
    }
    return nil;
}

+ (SomeClass *) shared 
{
    if (nil == SInstance) {
        SInstance = [[SomeClass alloc] initOnce];
    }
    return SInstance;
}

Not: birileri bunu yapabilirdi

SomeClass * c = [[SomeClass alloc] initOnce];

ve aslında yeni bir örnek döndürürdü, ancak initOnce projemizin hiçbir yerinde kamuya açık olarak (başlıkta) ilan edilmezse, bir uyarı oluşturur (id yanıt vermeyebilir ...) ve yine de bunu kullanan kişi gerçek başlatıcının initOnce olduğunu tam olarak bilmek

bunu daha da önleyebilirdik ama buna gerek yok


0

Alt sınıftaki yöntemleri gizlemek için iddialar yerleştirmenin ve istisnaları yükseltmenin iyi niyetli olanlar için kötü bir tuzağı olduğunu belirtmeliyim.

Jano'nun ilk örneği için açıkladığı__unavailable gibi kullanmanızı tavsiye ederim .

Alt sınıflarda yöntemler geçersiz kılınabilir. Bu, üst sınıftaki bir yöntem alt sınıfta bir istisna oluşturan bir yöntem kullanıyorsa, muhtemelen amaçlandığı gibi çalışmayacağı anlamına gelir. Başka bir deyişle, eskiden işe yarayan şeyi bozdunuz. Bu, başlatma yöntemleri için de geçerlidir. İşte böyle oldukça yaygın bir uygulamaya bir örnek:

- (SuperClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
    ...bla bla...
    return self;
}

- (SuperClass *)initWithLessParameters:(Type1 *)arg1
{
    self = [self initWithParameters:arg1 optional:DEFAULT_ARG2];
    return self;
}

Bunu alt sınıfta yaparsam -initWithLessParameters'a ne olacağını hayal edin:

- (SubClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
    [self release];
    [super doesNotRecognizeSelector:_cmd];
    return nil;
}

Bu, yöntemlerin geçersiz kılınmasını planlamadığınız sürece, özellikle başlatma yöntemlerinde özel (gizli) yöntemleri kullanma eğiliminde olmanız gerektiği anlamına gelir. Ancak bu başka bir konudur, çünkü süper sınıfın uygulanmasında her zaman tam kontrole sahip değilsiniz. (Bu, derinlemesine kullanmamış olsam da __attribute ((objc_designated_initializer)) 'ın kötü uygulama olarak kullanılmasını sorgulamama neden oluyor.)

Ayrıca, alt sınıflarda geçersiz kılınması gereken yöntemlerde iddiaları ve istisnaları kullanabileceğiniz anlamına da gelir. ( Objective-C'de soyut bir sınıf oluşturmada olduğu gibi "soyut" yöntemler )

Ve + yeni sınıf yöntemini de unutmayın.

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.