Cara terbaik untuk mendefinisikan metode pribadi untuk kelas di Objective-C

355

Saya baru saja memulai pemrograman Objective-C dan, memiliki latar belakang di Jawa, bertanya-tanya bagaimana orang menulis program Objective-C berurusan dengan metode pribadi.

Saya mengerti mungkin ada beberapa konvensi dan kebiasaan dan berpikir tentang pertanyaan ini sebagai agregator teknik terbaik yang digunakan orang berurusan dengan metode pribadi di Objective-C.

Harap sertakan argumen untuk pendekatan Anda saat mempostingnya. Mengapa ini bagus? Kekurangan apa yang dimilikinya (yang Anda ketahui) dan bagaimana Anda menghadapinya?


Adapun temuan saya sejauh ini.

Dimungkinkan untuk menggunakan kategori [mis. MyClass (Private)] yang didefinisikan dalam file MyClass.m untuk mengelompokkan metode pribadi.

Pendekatan ini memiliki 2 masalah:

  1. Xcode (dan kompiler?) Tidak memeriksa apakah Anda mendefinisikan semua metode dalam kategori pribadi di blok @ implementasi yang sesuai
  2. Anda harus meletakkan @interface mendeklarasikan kategori pribadi Anda di awal file MyClass.m, jika tidak Xcode mengeluh dengan pesan seperti "self mungkin tidak menanggapi pesan" privateFoo ".

Masalah pertama dapat diselesaikan dengan kategori kosong [mis. MyClass ()].
Yang kedua sangat mengganggu saya. Saya ingin melihat metode pribadi diimplementasikan (dan didefinisikan) di dekat akhir file; Saya tidak tahu apakah itu mungkin.

Yurii Soldak
sumber
1
Orang-orang mungkin menganggap pertanyaan ini menarik: stackoverflow.com/questions/2158660/…
bbum
4
Mengapa tidak mengabaikan deklarasi metode pribadi saja ?
ma11hew28

Jawaban:

435

Tidak ada, seperti yang orang lain katakan, hal seperti metode pribadi di Objective-C. Namun, mulai dari Objective-C 2.0 (artinya Mac OS X Leopard, iPhone OS 2.0, dan yang lebih baru) Anda dapat membuat kategori dengan nama kosong (yaitu @interface MyClass ()) yang disebut Ekstensi Kelas . Apa yang unik tentang ekstensi kelas adalah bahwa implementasi metode harus sama @implementation MyClassdengan metode publik. Jadi saya menyusun kelas saya seperti ini:

Dalam file .h:

@interface MyClass {
    // My Instance Variables
}

- (void)myPublicMethod;

@end

Dan dalam file .m:

@interface MyClass()

- (void)myPrivateMethod;

@end

@implementation MyClass

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

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

@end

Saya pikir keuntungan terbesar dari pendekatan ini adalah memungkinkan Anda untuk mengelompokkan implementasi metode Anda berdasarkan fungsi, bukan oleh perbedaan publik / pribadi (terkadang arbitrer).

Alex
sumber
8
dan itu akan menghasilkan "MYClass mungkin tidak menanggapi '-myPrivateMethod-", bukan pengecualian / kesalahan.
Özgür
2
Ini sebenarnya mulai muncul dalam kode boilerplate Apple. ++
Chris Trahey
75
dengan kompiler LLVM 4 dan seterusnya, Anda bahkan tidak perlu melakukan ini. Anda bisa mendefinisikannya dalam implementasi Anda tanpa harus memasukkannya ke dalam ekstensi kelas.
Abizern
1
Jika Anda mendapatkan peringatan @Comptrol menyebutkan, itu karena Anda telah mendefinisikan metode di bawah ini daripada metode lain yang menyebutnya (lihat jawaban Andy) - dan Anda mengabaikan peringatan ini atas risiko Anda. Saya membuat kesalahan ini dan kompiler kacau melalui baik-baik saja sampai saya bersarang panggilan seperti ini: if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])...Kemudian fWidthCombined selalu datang sebagai 0.
Wienke
6
@ Wienke Tidak perlu khawatir tentang pesanan. LLVM versi terbaru akan menemukan metode ini walaupun itu muncul di bawah tempat namanya.
Steve Waddicor
52

Sebenarnya tidak ada "metode pribadi" di Objective-C, jika runtime dapat menentukan implementasi yang akan digunakan. Tapi itu bukan untuk mengatakan bahwa tidak ada metode yang bukan bagian dari antarmuka yang didokumentasikan. Untuk metode-metode itu, saya pikir suatu kategori baik-baik saja. Daripada meletakkan @interfacedi bagian atas file .m seperti poin 2 Anda, saya akan memasukkannya ke file .h sendiri. Sebuah konvensi yang saya ikuti (dan telah saya lihat di tempat lain, saya pikir ini adalah konvensi Apple karena Xcode sekarang memberikan dukungan otomatis untuknya) adalah memberi nama file seperti itu setelah kelas dan kategorinya dengan tanda + yang memisahkannya, sehingga @interface GLObject (PrivateMethods)dapat ditemukan di GLObject+PrivateMethods.h. Alasan untuk menyediakan file header adalah agar Anda dapat mengimpornya di kelas uji unit Anda :-).

By the way, sejauh menerapkan / mendefinisikan metode di dekat akhir file .m yang bersangkutan, Anda dapat melakukannya dengan kategori dengan menerapkan kategori di bagian bawah file .m:

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

atau dengan ekstensi kelas (hal yang Anda sebut "kategori kosong"), cukup tentukan metode tersebut sebagai yang terakhir. Metode Objective-C dapat didefinisikan dan digunakan dalam urutan apa pun dalam implementasi, jadi tidak ada yang menghentikan Anda menempatkan metode "pribadi" di akhir file.

Bahkan dengan ekstensi kelas saya akan sering membuat header terpisah ( GLObject+Extension.h) sehingga saya dapat menggunakan metode tersebut jika diperlukan, meniru visibilitas "teman" atau "dilindungi".

Karena jawaban ini awalnya ditulis, compiler dentang telah mulai melakukan dua lintasan untuk metode Objective-C. Ini berarti Anda dapat menghindari mendeklarasikan metode "pribadi" Anda sepenuhnya, dan apakah itu di atas atau di bawah situs panggilan, mereka akan ditemukan oleh kompiler.


sumber
37

Walaupun saya bukan ahli Objective-C, saya pribadi hanya mendefinisikan metode dalam implementasi kelas saya. Memang, itu harus didefinisikan sebelum (di atas) metode apa pun yang memanggilnya, tetapi pasti membutuhkan sedikit pekerjaan untuk dilakukan.

Andy
sumber
4
Solusi ini memiliki keuntungan bahwa ia menghindari penambahan struktur program yang berlebihan hanya untuk menghindari peringatan kompiler.
Jan Hettich
1
Saya cenderung melakukan ini juga, tetapi juga bukan ahli Objective-C. Untuk para ahli, apakah ada alasan untuk tidak melakukannya dengan cara ini (selain dari masalah pemesanan metode)?
Eric Smith
2
Metode pemesanan tampaknya menjadi masalah kecil, tetapi jika Anda menerjemahkannya ke dalam keterbacaan kode, itu bisa menjadi masalah yang cukup penting terutama ketika bekerja dalam tim.
borisdiakur
17
Metode pemesanan tidak lagi penting. LLVM versi terbaru tidak peduli urutan metode apa yang diterapkan. Jadi Anda bisa menyesuaikan diri dengan pesanan, tanpa harus menyatakan terlebih dahulu.
Steve Waddicor
Lihat juga tanggapan ini dari @justin
lagweezle
19

Menentukan metode pribadi Anda di @implementationblok sangat ideal untuk sebagian besar tujuan. Dentang akan melihat ini di dalam @implementation, terlepas dari perintah deklarasi. Tidak perlu mendeklarasikan mereka dalam kelanjutan kelas (alias ekstensi kelas) atau kategori bernama.

Dalam beberapa kasus, Anda perlu mendeklarasikan metode dalam kelanjutan kelas (misalnya jika menggunakan pemilih antara kelanjutan kelas dan @implementation).

static fungsi sangat baik untuk metode pribadi yang sangat sensitif atau kecepatan kritis.

Suatu konvensi untuk penamaan awalan dapat membantu Anda menghindari override metode pribadi (saya menemukan nama kelas sebagai awalan aman).

Kategori yang diberi nama (mis. @interface MONObject (PrivateStuff)) Bukan ide yang sangat baik karena potensi tabrakan penamaan saat memuat. Mereka benar-benar hanya berguna untuk teman atau metode yang dilindungi (yang jarang merupakan pilihan yang baik). Untuk memastikan Anda diperingatkan tentang implementasi kategori yang tidak lengkap, Anda harus benar-benar mengimplementasikannya:

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

Inilah sedikit cheat sheet beranotasi:

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

Pendekatan lain yang mungkin tidak jelas: tipe C ++ bisa sangat cepat dan memberikan tingkat kontrol yang jauh lebih tinggi, sambil meminimalkan jumlah metode objek yang diekspor dan dimuat.

justin
sumber
1
+1 untuk menggunakan nama kelas penuh sebagai awalan nama metode! Ini jauh lebih aman daripada hanya garis bawah atau bahkan TLA Anda sendiri. (Bagaimana jika metode pribadi ada di perpustakaan yang Anda gunakan di proyek Anda yang lain dan Anda lupa bahwa Anda sudah menggunakan namanya, sekitar satu atau dua tahun yang lalu ...?)
big_m
14

Anda bisa mencoba mendefinisikan fungsi statis di bawah atau di atas implementasi Anda yang membawa pointer ke instance Anda. Ini akan dapat mengakses variabel instance Anda.

//.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
dreamlax
sumber
1
Nama cadangan Apple dengan garis bawah terkemuka untuk penggunaannya sendiri.
Georg Schölly
1
Dan bagaimana jika Anda tidak menggunakan kerangka kerja Apple? Saya sering mengembangkan kode Objective-C tanpa kerangka kerja Apple, pada kenyataannya saya membangun di Linux, Windows dan Mac OS X. Saya tetap menghapusnya mengingat kebanyakan orang yang kode di Objective-C mungkin menggunakannya di Mac OS X.
dreamlax
3
Saya pikir ini adalah metode pribadi dalam file .m. Metode kategori kelas lainnya sebenarnya tidak pribadi karena Anda tidak bisa menempatkan pribadi untuk metode di @interface ... @ blok akhir.
David.Chu.ca
Kenapa kamu ingin melakukan itu? jika Anda cukup menambahkan "-" di awal definisi metode, Anda akan mengakses "diri" tanpa meneruskan sebagai parameter.
Guy
1
@ Beli: karena metode ini dapat dideteksi oleh refleksi, dan karenanya tidak bersifat pribadi sama sekali.
dreamlax
3

Anda bisa menggunakan blok?

@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

Saya sadar ini adalah pertanyaan lama, tetapi ini adalah salah satu pertanyaan pertama yang saya temukan ketika saya mencari jawaban untuk pertanyaan ini. Saya belum melihat solusi ini dibahas di tempat lain, jadi beri tahu saya jika ada sesuatu yang bodoh dalam melakukan ini.

FellowMD
sumber
1
Apa yang Anda lakukan di sini adalah membuat variabel tipe blok global, yang sebenarnya tidak lebih baik daripada fungsi (dan bahkan tidak benar-benar pribadi, karena tidak dinyatakan static). Tapi saya telah bereksperimen dengan menetapkan blok ke guci pribadi (dari metode init) - agak gaya JavaScript - yang juga memungkinkan akses ke guci pribadi, sesuatu yang tidak mungkin dari fungsi statis. Belum yakin mana yang saya sukai.
big_m
3

setiap objek di Objective C sesuai dengan protokol NSObject, yang menampung metode performSelector : . Saya sebelumnya juga mencari cara untuk membuat beberapa metode "pembantu atau pribadi" yang saya tidak perlu diekspos di tingkat publik. Jika Anda ingin membuat metode pribadi tanpa overhead dan tidak harus mendefinisikannya di file header Anda, maka coba ini ...

tentukan metode Anda dengan tanda tangan yang mirip dengan kode di bawah ini ...

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

maka ketika Anda perlu referensi metode sebut saja sebagai pemilih ...

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

baris kode ini akan memanggil metode yang Anda buat dan tidak memiliki peringatan yang mengganggu tentang tidak mendefinisikannya dalam file header.

Zack Sheppard
sumber
6
Dengan cara ini Anda tidak memiliki cara untuk melewati parameter ketiga.
Li Fumin
2

Jika Anda ingin menghindari @interfaceblok di atas, Anda selalu dapat menempatkan deklarasi pribadi di file lain MyClassPrivate.htidak ideal tetapi tidak mengacaukan implementasi.

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
rebelzach
sumber
2

Satu hal lagi yang belum saya lihat disebutkan di sini - Xcode mendukung file .h dengan "_private" pada namanya. Katakanlah Anda memiliki kelas MyClass - Anda memiliki MyClass.m dan MyClass.h dan sekarang Anda juga dapat memiliki MyClass_private.h. Xcode akan mengenali ini dan memasukkannya dalam daftar "Rekan" di Asisten Editor.

//MyClass.m
#import "MyClass.h"
#import "MyClass_private.h"
Rich Schonthal
sumber
1

Tidak ada cara untuk mengatasi masalah # 2. Itulah cara kompiler C (dan karenanya kompiler Objective-C) bekerja. Jika Anda menggunakan editor XCode, fungsi popup akan membuatnya mudah untuk dinavigasi @interfacedan @implementationmemblokir file.

Barry Wark
sumber
1

Ada manfaat dari tidak adanya metode pribadi. Anda bisa memindahkan logika yang ingin Anda sembunyikan ke kelas yang terpisah dan menggunakannya sebagai delegasi. Dalam hal ini Anda dapat menandai objek yang didelegasikan sebagai pribadi dan tidak akan terlihat dari luar. Memindahkan logika ke kelas yang terpisah (mungkin beberapa) membuat desain proyek Anda lebih baik. Karena kelas Anda menjadi lebih sederhana dan metode Anda dikelompokkan dalam kelas dengan nama yang tepat.

Sneg
sumber
0

Seperti yang orang lain katakan, mendefinisikan metode pribadi dalam @implementationblok adalah OK untuk sebagian besar tujuan.

Pada topik organisasi kode - Saya ingin mereka tetap bersama di bawah pragma mark privateuntuk navigasi yang lebih mudah di Xcode

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

@end
Milan
sumber