Objective-C'de bir sınıf için özel yöntemler tanımlamanın en iyi yolu


355

Objective-C'yi programlamaya yeni başladım ve Java'da bir arka plana sahip olan Objective-C programları yazan insanların özel yöntemlerle nasıl başa çıktığını merak ediyorum.

Birkaç konvansiyon ve alışkanlık olabileceğini anlıyorum ve bu soruyu, Objective-C'deki özel yöntemlerle uğraşırken kullandıkları en iyi tekniklerin bir toplayıcısı olarak düşünüyorum.

Lütfen gönderirken yaklaşımınız için bir argüman ekleyin. Bu neden iyi? Hangi dezavantajları var (bildiğiniz) ve bunlarla nasıl başa çıkıyorsunuz?


Bulgularıma gelince.

Özel yöntemleri gruplandırmak için MyClass.m dosyasında tanımlanan kategorileri [örneğin MyClass (Private)] kullanmak mümkündür .

Bu yaklaşımın 2 sorunu vardır:

  1. Xcode (ve derleyici?), Özel kategorideki tüm yöntemleri, karşılık gelen @ uygulama bloğunda tanımlayıp tanımlamadığınızı denetlemez
  2. MyClass.m dosyasının başlangıcında özel kategorinizi bildiren @interface yazmanız gerekir, aksi takdirde Xcode "selfFoo" iletisine yanıt vermeyebilir.

İlk sorun boş kategori [örn. Sınıfım ()] ile çözülebilir .
İkincisi beni çok rahatsız ediyor. Dosyanın sonuna yakın uygulanan (ve tanımlanmış) özel yöntemleri görmek istiyorum; Bunun mümkün olup olmadığını bilmiyorum.



Yanıtlar:


435

Diğerlerinin söylediği gibi, Objective-C'de özel bir yöntem diye bir şey yoktur. Ancak, Objective-C 2.0'dan (yani Mac OS X Leopard, iPhone OS 2.0 ve üstü) başlayarak, boş bir ada (yani Sınıf Sınıfı@interface MyClass () ) sahip bir kategori oluşturabilirsiniz . Sınıf uzantısıyla ilgili benzersiz olan, yöntem uygulamalarının genel yöntemlerle aynı olması gerektiğidir . Sınıflarımı şöyle yapılandırdım:@implementation MyClass

.H dosyasında:

@interface MyClass {
    // My Instance Variables
}

- (void)myPublicMethod;

@end

Ve .m dosyasında:

@interface MyClass()

- (void)myPrivateMethod;

@end

@implementation MyClass

- (void)myPublicMethod {
    // Implementation goes here
}

- (void)myPrivateMethod {
    // Implementation goes here
}

@end

Bu yaklaşımın en büyük avantajı, yöntem uygulamalarınızı (bazen keyfi) kamu / özel ayrımına göre değil, işlevselliğe göre gruplandırmanıza izin vermesidir.


8
ve bir "MYClass" -myPrivateMethod- "öğesine yanıt vermeyebilir, bir istisna / hata değil.
Özgür

2
Bu aslında Apple'ın kaynak plakasında görünmeye başlıyor. ++
Chris Trahey

75
LLVM 4 derleyicisi ve sonrasında, bunu yapmanız bile gerekmez. bunları bir sınıf uzantısına koymanıza gerek kalmadan uygulamanız içinde tanımlayabilirsiniz.
Abizern

1
@Comptrol'den söz ettiğiniz uyarıları alırsanız, bunun nedeni, onu çağıran başka bir yöntemin üstünde değil, aşağıda bir yöntem tanımlamanızdır (Andy'nin cevabına bakın) - ve bu uyarıları sizin tehlikenizde görmezden gelirsiniz. Ben bu hatayı yaptım ve derleyici ben böyle bir çağrı iç içe kadar Tamam if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])...karıştı : Sonra fWidthCombined her zaman 0 olarak geliyordu.
Wienke

6
@Wienke Artık sipariş konusunda endişelenmenize gerek yok. LLVM'nin son sürümleri yöntemi, çağrıldığı yerin altında görünse bile bulacaktır.
Steve Waddicor

52

Eğer çalışma zamanı hangi uygulamayı kullanacağını çözebilirse, Objective-C'de gerçekten "özel bir yöntem" yoktur. Ancak bu, belgelenen arayüzün bir parçası olmayan yöntemler olmadığı anlamına gelmez. Bu yöntemler için bir kategorinin iyi olduğunu düşünüyorum. Aksine koyarak daha @interfacenoktanız 2 gibi .m dosyasının en üstünde, ben kendi .h dosyası içine koyarlardı. Takip ettiğim bir kongre (ve başka bir yerde gördüm, Xcode artık bunun için otomatik destek verdiği için bir Apple konvansiyonu olduğunu düşünüyorum) böyle bir dosyayı sınıfından ve kategorisinden sonra onları + ayırarak adlandırmaktır, bu yüzden @interface GLObject (PrivateMethods)bulunabilir GLObject+PrivateMethods.h. Başlık dosyasını sağlamanın nedeni, birim test sınıflarınıza içe aktarabilmenizdir :-).

Bu arada, .m dosyasının sonuna yakın uygulama / tanımlama yöntemleri söz konusu olduğunda, bunu .m dosyasının altındaki kategoriyi uygulayarak bir kategori ile yapabilirsiniz:

@implementation GLObject(PrivateMethods)
- (void)secretFeature;
@end

veya bir sınıf uzantısıyla ("boş kategori" dediğiniz şey), bu yöntemleri en son tanımlayın. Objective-C yöntemleri uygulamadaki herhangi bir sırada tanımlanabilir ve kullanılabilir, bu nedenle dosyanın sonuna "private" yöntemlerini koymanızı engelleyecek hiçbir şey yoktur.

Sınıf uzantıları ile bile, sık sık ayrı bir üstbilgi ( GLObject+Extension.h) oluşturacağım .

Bu cevap başlangıçta yazıldığından, clang derleyicisi Objective-C yöntemleri için iki geçiş yapmaya başladı. Bu, "özel" yöntemlerinizi tamamen bildirmekten kaçınabileceğiniz anlamına gelir ve bunların, arama sitesinin üstünde veya altında olup olmadığını, derleyici tarafından bulunacağını belirtir.


37

Objective-C uzmanı olmasam da, şahsen sadece sınıfımın uygulanmasındaki yöntemi tanımlıyorum. Kabul edilirse, onu çağıran herhangi bir yöntemden önce (yukarıda) tanımlanmalıdır, ancak kesinlikle en az iş gerektirir.


4
Bu çözüm, sadece bir derleyici uyarısından kaçınmak için gereksiz program yapısının eklenmesini önleme avantajına sahiptir.
Jan Hettich

1
Bunu ben de yapmak eğilimindeyim ama aynı zamanda Objective-C uzmanı değilim. Uzmanlar için, bu şekilde yapılmaması için herhangi bir sebep var mı (yöntem siparişi sorununun yanı sıra)?
Eric Smith

2
Yöntem sıralaması küçük bir sorun gibi görünüyor, ancak kod okunabilirliğine çevirirseniz , özellikle bir ekipte çalışırken oldukça önemli bir sorun haline gelebilir.
borisdiakur

17
Yöntem sırası artık önemli değildir. LLVM'nin son sürümleri, yöntemlerin hangi sırayla uygulandığını umursamıyor. Böylece, ilk önce beyan etmenize gerek kalmadan, sipariş üzerine kendinize uygun olabilirsiniz.
Steve Waddicor

Ayrıca bkz bu yanıtı @justin den
lagweezle

19

Özel yöntemlerinizi @implementationblokta tanımlamak çoğu amaç için idealdir. Clang bunları @implementationbeyan düzeninden bağımsız olarak görecek . Bunları sınıf devamında (sınıf uzantısı olarak adlandırılır) veya adlandırılmış bir kategoride bildirmeye gerek yoktur.

Bazı durumlarda, yöntemi sınıf devamında bildirmeniz gerekir (örneğin, sınıf devamı ve arasında seçici kullanıyorsanız @implementation).

static fonksiyonlar özellikle hassas veya hız açısından kritik özel yöntemler için çok iyidir.

Ön ek adlandırma kuralı, özel yöntemlerin yanlışlıkla geçersiz kılınmasını önlemenize yardımcı olabilir (sınıf adını önek güvenli olarak bulurum).

Adlandırılmış kategoriler (örn. @interface MONObject (PrivateStuff)) Yükleme sırasındaki adlandırma çakışmaları nedeniyle özellikle iyi bir fikir değildir. Gerçekten sadece arkadaş veya korunan yöntemler için yararlıdırlar (nadiren iyi bir seçimdir). Eksik kategori uygulamalarıyla ilgili uyarıldığınızdan emin olmak için gerçekte uygulamalısınız:

@implementation MONObject (PrivateStuff)
...HERE...
@end

İşte biraz açıklamalı hile sayfası:

MONObject.h

@interface MONObject : NSObject

// public declaration required for clients' visibility/use.
@property (nonatomic, assign, readwrite) bool publicBool;

// public declaration required for clients' visibility/use.
- (void)publicMethod;

@end

MONObject.m

@interface MONObject ()
@property (nonatomic, assign, readwrite) bool privateBool;

// you can use a convention where the class name prefix is reserved
// for private methods this can reduce accidental overriding:
- (void)MONObject_privateMethod;

@end

// The potentially good thing about functions is that they are truly
// inaccessible; They may not be overridden, accidentally used,
// looked up via the objc runtime, and will often be eliminated from
// backtraces. Unlike methods, they can also be inlined. If unused
// (e.g. diagnostic omitted in release) or every use is inlined,
// they may be removed from the binary:
static void PrivateMethod(MONObject * pObject) {
    pObject.privateBool = true;
}

@implementation MONObject
{
    bool anIvar;
}

static void AnotherPrivateMethod(MONObject * pObject) {
    if (0 == pObject) {
        assert(0 && "invalid parameter");
        return;
    }

    // if declared in the @implementation scope, you *could* access the
    // private ivars directly (although you should rarely do this):
    pObject->anIvar = true;
}

- (void)publicMethod
{
    // declared below -- but clang can see its declaration in this
    // translation:
    [self privateMethod];
}

// no declaration required.
- (void)privateMethod
{
}

- (void)MONObject_privateMethod
{
}

@end

Açık olmayan başka bir yaklaşım: C ++ türü hem çok hızlı olabilir hem de çok daha yüksek bir kontrol derecesi sağlarken, dışa aktarılan ve yüklenen objc yöntemlerinin sayısını en aza indirir.


1
Tam sınıf adını yöntem adı öneki olarak kullanmak için +1! Bir alt çizgiden ve hatta kendi TLA'nızdan çok daha güvenlidir. (Özel yöntem, başka bir projenizde kullandığınız bir kütüphanede bulunuyorsa ve bir yıl veya iki yıl önce adı zaten kullandığınızı unutursanız ...?)
big_m

14

Uygulamanızın altında veya üstünde, örneğinize bir işaretçi getiren statik bir işlev tanımlamayı deneyebilirsiniz. Örnek değişkenlerinizin herhangi birine erişebilecektir.

//.h file
@interface MyClass : Object
{
    int test;
}
- (void) someMethod: anArg;

@end


//.m file    
@implementation MyClass

static void somePrivateMethod (MyClass *myClass, id anArg)
{
    fprintf (stderr, "MyClass (%d) was passed %p", myClass->test, anArg);
}


- (void) someMethod: (id) anArg
{
    somePrivateMethod (self, anArg);
}

@end

1
Apple, adları kendi kullanımları için önde gelen altçizgi ile ayırdı.
Georg Schölly

1
Ya Apple'ın çerçevelerini kullanmazsanız? Sık sık Apple'ın çerçeveleri olmadan Objective-C kodu geliştiriyorum, aslında Linux, Windows ve Mac OS X üzerine inşa ediyorum. Objective-C kodlayan çoğu insanın muhtemelen Mac OS X'de kullandığını düşünerek yine de kaldırdım.
dreamlax

3
Bence bu .m dosyasında gerçekten özel bir yöntem. Diğer sınıf kategorisi yöntemleri aslında özel değildir, çünkü @interface ... @ end block içindeki yöntemlere özel koyamazsınız.
David.Chu.ca

Neden bunu yapasın ki? yöntem tanımının başlangıcına "-" eklerseniz, parametre olarak geçmeden "benlik" e erişeceksiniz.
Guy

1
@Guy: çünkü o zaman yöntem yansıma ile tespit edilebilir ve bu nedenle hiç özel değildir.
dreamlax

3

Bloklar kullanabilir misin?

@implementation MyClass

id (^createTheObject)() = ^(){ return [[NSObject alloc] init];};

NSInteger (^addEm)(NSInteger, NSInteger) =
^(NSInteger a, NSInteger b)
{
    return a + b;
};

//public methods, etc.

- (NSObject) thePublicOne
{
    return createTheObject();
}

@end

Bunun eski bir soru olduğunun farkındayım, ama bu soruya bir cevap ararken ilk bulduğum sorulardan biri. Bu çözümün başka hiçbir yerde tartışılmadığını görmedim, bu yüzden bunu yapmayla ilgili aptalca bir şey olup olmadığını bana bildirin.


1
Burada yaptığınız şey, gerçekten bir işlevden daha iyi olmayan (ve gerçekten özel olmadığı, bildirilmediğinden static) global bir blok tipi değişken oluşturmaktır . Ama özel ivarlara bloklar atamayı deniyorum (init yönteminden) - JavaScript tarzı - özel ivarlara erişime izin veriyor, statik işlevlerden mümkün olmayan bir şey. Hangisini tercih ettiğimden henüz emin değilim.
big_m

3

Objective C'deki her nesne, performSelector: yöntemine dayanan NSObject protokolüne uygundur . Ayrıca daha önce kamuya açıkta ihtiyaç duymadığım bazı "yardımcı veya özel" yöntemler oluşturmanın bir yolunu arıyordum. Herhangi bir ek yükü olmayan ve başlık dosyanızda tanımlamak zorunda kalmadan özel bir yöntem oluşturmak istiyorsanız, bunu deneyin ...

yönteminizi aşağıdaki kodla benzer bir imzayla tanımlayın ...

-(void)myHelperMethod: (id) sender{
     // code here...
}

o zaman yönteme başvurmanız gerektiğinde onu seçici olarak adlandırın ...

[self performSelector:@selector(myHelperMethod:)];

bu kod satırı, oluşturduğunuz yöntemi çağırır ve üstbilgi dosyasında tanımlanmamasına ilişkin can sıkıcı bir uyarı almaz.


6
Bu şekilde üçüncü bir parametreyi geçirmenin yolu yoktur.
Li Fumin

2

@interfaceEn üstteki bloktan kaçınmak istiyorsanız, her zaman özel bildirimleri MyClassPrivate.hideal olmayan başka bir dosyaya koyabilirsiniz, ancak uygulamayı karıştırmaz.

MyClass.h

interface MyClass : NSObject {
 @private
  BOOL publicIvar_;
  BOOL privateIvar_;
}

@property (nonatomic, assign) BOOL publicIvar;
//any other public methods. etc
@end

MyClassPrivate.h

@interface MyClass ()

@property (nonatomic, assign) BOOL privateIvar;
//any other private methods etc.
@end

MyClass.m

#import "MyClass.h"
#import "MyClassPrivate.h"
@implementation MyClass

@synthesize privateIvar = privateIvar_;
@synthesize publicIvar = publicIvar_;

@end

2

Burada bahsetmediğim bir şey daha var - Xcode, adında "_private" olan .h dosyalarını destekler. Diyelim ki bir sınıf MyClass'ınız var - MyClass.m ve MyClass.h'niz var ve şimdi de MyClass_private.h'e sahip olabilirsiniz. Xcode bunu tanıyacak ve Yardımcı Editör'deki "Muadiller" listesine ekleyecektir.

//MyClass.m
#import "MyClass.h"
#import "MyClass_private.h"

1

2. sayıyı çözmenin bir yolu yok. C derleyicisinin (ve bu nedenle Objective-C derleyicisinin) çalışma şekli budur. XCode düzenleyicisini kullanırsanız, işlev açılır penceresi dosyada @interfaceve @implementationbloklarda gezinmeyi kolaylaştırmalıdır .


1

Özel yöntemlerin yokluğunun bir yararı vardır. Gizlemek istediğiniz mantığı ayrı sınıfa taşıyabilir ve temsilci olarak kullanabilirsiniz. Bu durumda, temsilci nesnesini özel olarak işaretleyebilirsiniz ve dışarıdan görünmez. Mantığı ayrı sınıfa (belki birkaçına) taşımak projenizi daha iyi tasarlar. Çünkü sınıflarınız daha basit hale geliyor ve yöntemleriniz uygun adlara sahip sınıflarda gruplanıyor.


0

Diğer insanların söylediği gibi, @implementationblokta özel yöntemler tanımlamak çoğu amaç için uygundur.

Başlığı altında kod organizasyonu - Ben altında onları bir arada tutmak ister pragma mark privateXcode kolay navigasyon için

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

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