-init
Sınıfımın yöntemini Objective-C'de gizlemem (özel yapmam) gerekiyor .
Bunu nasıl yapabilirim?
-init
Sınıfımın yöntemini Objective-C'de gizlemem (özel yapmam) gerekiyor .
Bunu nasıl yapabilirim?
NS_UNAVAILABLE
arayanın init
dolaylı olarak aracılığıyla aramasına izin verir new
. init
Geri dönmeyi basitçe geçersiz kılmak nil
her iki durumu da ele alır.
Yanıtlar:
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 NSInternalInconsistencyException
da eğer -init
yö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 - -init
mü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, +sharedWhatever
yö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.
alloc
ve init
yanlış ç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.
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 unavailable
bir derleyici hatası oluşturmak için özniteliği başlığa ekleyin .
-(instancetype) init __attribute__((unavailable("init not available")));
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
NSAssert
NSInternalInconsistencyException'ı 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 alloc
atanmış 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
init
Belirlenmiş 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 myOwnInit
dahili 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).
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
.
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).
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).
Bunu başlık dosyasına koy
- (id)init UNAVAILABLE_ATTRIBUTE;
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
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.
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
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.
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.