Deklarasi / definisi lokasi variabel di ObjectiveC?

113

Sejak mulai mengerjakan aplikasi iOS dan objektif C, saya benar-benar bingung dengan lokasi berbeda di mana seseorang dapat mendeklarasikan dan menentukan variabel. Di satu sisi kami memiliki pendekatan C tradisional, di sisi lain kami memiliki arahan ObjectiveC baru yang menambahkan OO di atasnya. Bisakah kalian membantu saya memahami praktik terbaik dan situasi di mana saya ingin menggunakan lokasi ini untuk variabel saya dan mungkin memperbaiki pemahaman saya saat ini?

Berikut adalah contoh kelas (.h dan .m):

#import <Foundation/Foundation.h>

// 1) What do I declare here?

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

// 3) class-specific method / property declarations

@end

dan

#import "SampleClass.h"

// 4) what goes here?

@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

@implementation SampleClass
{
    // 6) define ivars
}

// 7) define methods and synthesize properties from both public and private
//    interfaces

@end
  • Pemahaman saya tentang 1 dan 4 adalah bahwa itu adalah deklarasi dan definisi berbasis file gaya-C yang tidak memiliki pemahaman apa pun tentang konsep kelas, dan dengan demikian harus digunakan dengan tepat bagaimana mereka akan digunakan di C. Saya telah melihatnya digunakan untuk menerapkan lajang berbasis variabel statis sebelumnya. Apakah ada kegunaan praktis lain yang saya lewatkan?
  • Saya mengambil dari bekerja dengan iOS adalah bahwa ivar telah sepenuhnya dihapus di luar direktif @synthesize dan dengan demikian sebagian besar dapat diabaikan. Apakah itu masalahnya?
  • Mengenai 5: mengapa saya ingin mendeklarasikan metode dalam antarmuka pribadi? Metode kelas privat saya tampaknya dapat dikompilasi dengan baik tanpa deklarasi di antarmuka. Apakah sebagian besar untuk keterbacaan?

Terima kasih banyak, teman-teman!

Alexandr Kurilin
sumber

Jawaban:

154

Saya dapat memahami kebingungan Anda. Terutama karena update terbaru untuk Xcode dan compiler LLVM baru mengubah cara ivars dan properti dapat dideklarasikan.

Sebelum Objective-C "modern" (di Obj-C 2.0 "lama") Anda tidak memiliki banyak pilihan. Variabel instance digunakan untuk dideklarasikan di header antara tanda kurung kurawal { }:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@end

Anda hanya dapat mengakses variabel ini dalam implementasi Anda, tetapi tidak dari kelas lain. Untuk melakukan itu, Anda harus mendeklarasikan metode pengakses, yang terlihat seperti ini:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}

- (int)myVar;
- (void)setMyVar:(int)newVar;

@end


// MyClass.m
@implementation MyClass

- (int)myVar {
   return myVar;
}

- (void)setMyVar:(int)newVar {
   if (newVar != myVar) {
      myVar = newVar;
   }
}

@end

Dengan cara ini Anda juga bisa mendapatkan dan menyetel variabel instance ini dari kelas lain, menggunakan sintaks tanda kurung siku biasa untuk mengirim pesan (metode panggilan):

// OtherClass.m
int v = [myClass myVar];  // assuming myClass is an object of type MyClass.
[myClass setMyVar:v+1];

Karena secara manual mendeklarasikan dan menerapkan setiap metode pengakses cukup mengganggu, @propertydan @synthesizediperkenalkan untuk menghasilkan metode pengakses secara otomatis:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@synthesize myVar;
@end

Hasilnya adalah kode yang jauh lebih jelas dan lebih pendek. Metode pengakses akan diterapkan untuk Anda dan Anda masih dapat menggunakan sintaks tanda kurung seperti sebelumnya. Namun selain itu, Anda juga dapat menggunakan sintaks titik untuk mengakses properti:

// OtherClass.m
int v = myClass.myVar;   // assuming myClass is an object of type MyClass.
myClass.myVar = v+1;

Sejak Xcode 4.4 Anda tidak perlu mendeklarasikan variabel instance sendiri lagi dan Anda juga dapat melewati @synthesize. Jika Anda tidak mendeklarasikan ivar, kompiler akan menambahkannya untuk Anda dan juga akan menghasilkan metode pengakses tanpa Anda harus menggunakannya @synthesize.

Nama default untuk ivar yang dibuat secara otomatis adalah nama atau properti Anda yang dimulai dengan garis bawah. Anda dapat mengubah nama ivar yang dihasilkan dengan menggunakan@synthesize myVar = iVarName;

// MyClass.h
@interface MyClass : NSObject 
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@end

Ini akan bekerja persis seperti kode di atas. Untuk alasan kompatibilitas, Anda masih bisa mendeklarasikan ivars di header. Tetapi karena satu-satunya alasan mengapa Anda ingin melakukan itu (dan tidak mendeklarasikan properti) adalah untuk membuat variabel privat, Anda sekarang dapat melakukannya di file implementasi juga dan ini adalah cara yang lebih disukai.

Sebuah @interfaceblok dalam file implementasi sebenarnya adalah sebuah Ekstensi dan dapat digunakan untuk meneruskan metode deklarasi (tidak diperlukan lagi) dan untuk (kembali) mendeklarasikan properti. Anda bisa misalnya mendeklarasikan readonlyproperti di header Anda.

@property (nonatomic, readonly) myReadOnlyVar;

dan deklarasikan ulang di file implementasi Anda readwriteagar dapat menyetelnya menggunakan sintaks properti dan tidak hanya melalui akses langsung ke ivar.

Adapun untuk mendeklarasikan variabel sepenuhnya di luar blok @interfaceatau apa pun @implementation, ya itu adalah variabel C biasa dan berfungsi persis sama.

DrummerB
sumber
2
jawaban yang bagus! Perhatikan juga: stackoverflow.com/questions/9859719/…
nycynik
44

Pertama, baca jawaban @ DrummerB. Ini adalah gambaran umum yang baik tentang mengapa dan apa yang biasanya harus Anda lakukan. Dengan mengingat hal itu, untuk pertanyaan spesifik Anda:

#import <Foundation/Foundation.h>

// 1) What do I declare here?

Tidak ada definisi variabel aktual di sini (secara teknis legal untuk melakukannya jika Anda tahu persis apa yang Anda lakukan, tetapi tidak pernah melakukan ini). Anda dapat mendefinisikan beberapa jenis hal lain:

  • typdefs
  • enums
  • eksternal

Eksternal terlihat seperti deklarasi variabel, tetapi itu hanya janji untuk benar-benar mendeklarasikannya di tempat lain. Di ObjC, mereka hanya boleh digunakan untuk mendeklarasikan konstanta, dan umumnya hanya konstanta string. Misalnya:

extern NSString * const MYSomethingHappenedNotification;

Anda kemudian akan .mmendeklarasikan konstanta aktual di file Anda :

NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

Seperti dicatat oleh DrummerB, ini adalah warisan. Jangan taruh apapun di sini.


// 3) class-specific method / property declarations

@end

Ya.


#import "SampleClass.h"

// 4) what goes here?

Konstanta eksternal, seperti dijelaskan di atas. File variabel statis juga dapat masuk ke sini. Ini setara dengan variabel kelas dalam bahasa lain.


@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

Ya


@implementation SampleClass
{
    // 6) define ivars
}

Tapi sangat jarang. Hampir selalu Anda harus mengizinkan clang (Xcode) membuat variabel untuk Anda. Pengecualian biasanya ada di sekitar ivars non-ObjC (seperti objek Core Foundation, dan terutama objek C ++ jika ini adalah kelas ObjC ++), atau ivar yang memiliki semantik penyimpanan aneh (seperti ivars yang tidak cocok dengan properti karena alasan tertentu).


// 7) define methods and synthesize properties from both public and private
//    interfaces

Umumnya Anda tidak boleh @sintesis lagi. Clang (Xcode) akan melakukannya untuk Anda, dan Anda harus membiarkannya.

Selama beberapa tahun terakhir, banyak hal menjadi lebih sederhana secara dramatis. Efek sampingnya adalah sekarang ada tiga era yang berbeda (ABI Fragile, ABI Non-rapuh, Non-fragile ABI + sintesis otomatis). Jadi, ketika Anda melihat kode yang lebih lama, itu bisa sedikit membingungkan. Dengan demikian kebingungan yang timbul dari kesederhanaan: D

Rob Napier
sumber
Hanya ingin tahu, tetapi mengapa kita tidak secara eksplisit mensintesis? Saya melakukannya karena saya menemukan kode saya lebih mudah dipahami, terutama ketika beberapa properti telah mensintesis pengakses dan beberapa memiliki implementasi khusus, karena saya terbiasa mensintesis. Apakah ada kekurangan untuk sintesis eksplisit?
Metabble
Masalah dengan menggunakannya sebagai dokumentasi adalah tidak benar-benar mendokumentasikan apa pun. Meskipun menggunakan sintesis, Anda mungkin telah mengganti salah satu atau kedua pengakses. Tidak ada cara untuk membedakan dari baris sintesis apa pun yang benar-benar berguna. Satu-satunya hal yang lebih buruk daripada tidak ada dokumentasi adalah dokumentasi yang menyesatkan. Biarkan saja.
Rob Napier
3
Mengapa # 6 langka? Bukankah ini cara termudah untuk mendapatkan variabel privat?
pfrank
Cara termudah dan terbaik untuk mendapatkan properti pribadi adalah # 5.
Rob Napier
1
@RobNapier Terkadang masih perlu menggunakan @ synthesize (mis. Jika properti hanya-baca yang pengaksesnya diganti)
Andy
6

Saya juga cukup baru, jadi semoga saya tidak mengacaukan apa pun.

1 & 4: Variabel global gaya-C: mereka memiliki cakupan file yang luas. Perbedaan antara keduanya adalah, karena lebar file, yang pertama akan tersedia bagi siapa saja yang mengimpor header sedangkan yang kedua tidak.

2: variabel instan. Sebagian besar variabel instance disintesis dan diambil / disetel melalui pengakses menggunakan properti karena hal itu membuat manajemen memori bagus dan sederhana, serta memberi Anda notasi titik yang mudah dipahami.

6: Penerapan ivars agak baru. Ini adalah tempat yang baik untuk meletakkan ivar pribadi, karena Anda hanya ingin mengekspos apa yang dibutuhkan di header publik, tetapi subclass tidak mewarisinya AFAIK.

3 & 7: Metode publik dan deklarasi properti, lalu implementasi.

5: Antarmuka pribadi. Saya selalu menggunakan antarmuka pribadi kapan pun saya bisa untuk menjaga kebersihan dan menciptakan semacam efek kotak hitam. Jika mereka tidak perlu mengetahuinya, taruh di sana. Saya juga melakukannya agar terbaca, tidak tahu apakah ada alasan lain.

Metabble
sumber
1
Jangan berpikir Anda mengacaukan apa pun :) Beberapa komentar - # 1 & # 4 esp dengan # 4 sering kali Anda melihat variabel penyimpanan statis. # 1 Seringkali Anda akan melihat penyimpanan eksternal ditentukan dan kemudian penyimpanan sebenarnya dialokasikan di # 4. # 2) biasanya hanya jika sebuah subclass membutuhkannya karena alasan apa pun. # 5 tidak perlu meneruskan mendeklarasikan metode privat lagi.
Carl Veazey
Ya, saya sendiri baru saja memeriksa deklarasi ke depan. Itu digunakan untuk memberi peringatan jika satu metode pribadi memanggil metode lain yang ditentukan setelahnya tanpa deklarasi maju, bukan? Saya agak terkejut ketika itu tidak memperingatkan saya.
Metabble
Ya itu bagian baru dari kompilator. Mereka benar-benar membuat banyak kemajuan akhir-akhir ini.
Carl Veazey
6

Ini adalah contoh dari semua jenis variabel yang dideklarasikan di Objective-C. Nama variabel menunjukkan aksesnya.

File: Animal.h

@interface Animal : NSObject
{
    NSObject *iProtected;
@package
    NSObject *iPackage;
@private
    NSObject *iPrivate;
@protected
    NSObject *iProtected2; // default access. Only visible to subclasses.
@public
    NSObject *iPublic;
}

@property (nonatomic,strong) NSObject *iPublic2;

@end

File: Animal.m

#import "Animal.h"

// Same behaviour for categories (x) than for class extensions ().
@interface Animal(){
@public
    NSString *iNotVisible;
}
@property (nonatomic,strong) NSObject *iNotVisible2;
@end

@implementation Animal {
@public
    NSString *iNotVisible3;
}

-(id) init {
    self = [super init];
    if (self){
        iProtected  = @"iProtected";
        iPackage    = @"iPackage";
        iPrivate    = @"iPrivate";
        iProtected2 = @"iProtected2";
        iPublic     = @"iPublic";
        _iPublic2    = @"iPublic2";

        iNotVisible   = @"iNotVisible";
        _iNotVisible2 = @"iNotVisible2";
        iNotVisible3  = @"iNotVisible3";
    }
    return self;
}

@end

Perhatikan bahwa variabel iNotVisible tidak terlihat dari kelas lain. Ini adalah masalah visibilitas, jadi nyatakan dengan @propertyatau @publictidak mengubahnya.

Di dalam konstruktor, praktik yang baik adalah mengakses variabel yang dideklarasikan dengan @propertymenggunakan garis bawah sebagai gantinya selfuntuk menghindari efek samping.

Mari kita coba mengakses variabel.

File: Cow.h

#import "Animal.h"
@interface Cow : Animal
@end

File: Cow.m

#import "Cow.h"
#include <objc/runtime.h>

@implementation Cow

-(id)init {
    self=[super init];
    if (self){
        iProtected    = @"iProtected";
        iPackage      = @"iPackage";
        //iPrivate    = @"iPrivate"; // compiler error: variable is private
        iProtected2   = @"iProtected2";
        iPublic       = @"iPublic";
        self.iPublic2 = @"iPublic2"; // using self because the backing ivar is private

        //iNotVisible   = @"iNotVisible";  // compiler error: undeclared identifier
        //_iNotVisible2 = @"iNotVisible2"; // compiler error: undeclared identifier
        //iNotVisible3  = @"iNotVisible3"; // compiler error: undeclared identifier
    }
    return self;
}
@end

Kami masih dapat mengakses variabel yang tidak terlihat menggunakan runtime.

File: Cow.m (bagian 2)

@implementation Cow(blindAcess)

- (void) setIvar:(NSString*)name value:(id)value {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    object_setIvar(self, ivar, value);
}

- (id) getIvar:(NSString*)name {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    id thing = object_getIvar(self, ivar);
    return thing;
}

-(void) blindAccess {
    [self setIvar:@"iNotVisible"  value:@"iMadeVisible"];
    [self setIvar:@"_iNotVisible2" value:@"iMadeVisible2"];
    [self setIvar:@"iNotVisible3" value:@"iMadeVisible3"];
    NSLog(@"\n%@ \n%@ \n%@",
          [self getIvar:@"iNotVisible"],
          [self getIvar:@"_iNotVisible2"],
          [self getIvar:@"iNotVisible3"]);
}

@end

Mari kita coba mengakses variabel yang tidak terlihat.

File: main.m

#import "Cow.h"
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
    @autoreleasepool {
        Cow *cow = [Cow new];
        [cow performSelector:@selector(blindAccess)];
    }
}

Ini cetakan

iMadeVisible 
iMadeVisible2 
iMadeVisible3

Perhatikan bahwa saya dapat mengakses backing ivar _iNotVisible2yang bersifat pribadi untuk subclass. Di Objective-C semua variabel dapat dibaca atau disetel, bahkan yang diberi tanda @private, tanpa pengecualian.

Saya tidak menyertakan objek terkait atau variabel C karena mereka adalah burung yang berbeda. Adapun variabel C, variabel apa pun yang ditentukan di luar @interface X{}atau @implementation X{}merupakan variabel C dengan ruang lingkup file dan penyimpanan statis.

Saya tidak membahas atribut manajemen memori, atau atribut readonly / readwrite, getter / setter.

Jano
sumber