Mengapa Anda menggunakan ivar?


153

Saya biasanya melihat pertanyaan ini ditanyakan dengan cara lain, seperti Haruskah setiap ivar menjadi properti?(dan saya suka jawaban bbum untuk Q ini).

Saya menggunakan properti hampir secara eksklusif dalam kode saya. Namun, sering kali, saya bekerja dengan kontraktor yang telah lama mengembangkan iOS di dan merupakan programmer game tradisional. Dia menulis kode yang menyatakan hampir tidak ada properti apa pun dan bersandar pada ivars. Saya berasumsi dia melakukan ini karena 1.) dia terbiasa sejak properti tidak selalu ada sampai Objective C 2.0 (Oct '07) dan 2.) untuk keuntungan kinerja minimal tidak melalui pengambil / penyetel.

Sementara dia menulis kode yang tidak bocor, saya masih lebih suka dia menggunakan properti lebih dari ivars. Kami membicarakannya dan dia kira-kira tidak melihat alasan untuk menggunakan properti karena kami tidak menggunakan KVO dan dia berpengalaman menangani masalah memori.

Pertanyaan saya lebih lanjut ... Mengapa Anda ingin menggunakan periode ivar - berpengalaman atau tidak. Adakah perbedaan kinerja yang begitu besar sehingga menggunakan ivar dapat dibenarkan?

Juga sebagai titik klarifikasi, saya menimpa setter dan getter sesuai kebutuhan dan menggunakan ivar yang berkorelasi dengan properti di dalam pengambil / setter. Namun, di luar dari pengambil / penyetel atau init, saya selalu menggunakan self.myPropertysintaks.


Edit 1

Saya menghargai semua tanggapan yang baik. Salah satu yang saya ingin alamat yang tampaknya salah adalah bahwa dengan ivar Anda mendapatkan enkapsulasi di mana dengan properti Anda tidak. Cukup tentukan properti dalam kelanjutan kelas. Ini akan menyembunyikan properti dari orang luar. Anda juga dapat mendeklarasikan properti yang hanya bisa dibaca di antarmuka dan mendefinisikannya kembali sebagai readwrite dalam implementasi seperti:

// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;

dan ada di kelanjutan kelas:

// readwrite within this file
@property (nonatomic, copy) NSString * name;

Untuk membuatnya benar-benar "pribadi" hanya menyatakannya dalam kelanjutan kelas.


2
pilih untuk pertanyaan menarik - baik dimasukkan dan juga yang saya ingin mendengar kasus untuk ivars karena sepertinya saya telah diajarkan untuk melakukannya dengan cara Sam.
Damo

2
Perhatikan bahwa Penghitungan Referensi Otomatis (ARC) menerapkan manfaat manajemen memori yang sama untuk ivars sebagai properti, sehingga dalam kode ARC perbedaannya adalah tentang enkapsulasi.
benzado

1
Pertanyaan Anda dan terutama bagian Edit 1 sebenarnya jauh lebih informatif daripada jawaban yang dipilih.
user523234

1
Untuk Edit1: Saya pikir adalah mungkin untuk membaca DAN MENULIS setiap properti, bahkan ketika hanya satu deklarasi readonly di .h, dengan Key-Value-Coding, misalnya: [objek setValue: [NSNumber numberWithInt: 20] forKey: @ "propertyname "];
Binarian

1
@ Sam ke Sunting 1 Anda: Jika Anda menggunakan properti pribadi dan menggunakan ekstensi / kelanjutan kelas dalam file .m itu tidak terlihat untuk subclass. Anda perlu menulis kode lagi atau menggunakan .h lain dengan ekstensi kelas. Lebih mudah dengan @ protected / default.
Binarian

Jawaban:


100

Enkapsulasi

Jika ivar bersifat pribadi, bagian lain dari program tidak bisa melakukannya dengan mudah. Dengan properti yang dideklarasikan, orang pintar dapat mengakses dan bermutasi dengan mudah melalui accessor.

Performa

Ya, ini dapat membuat perbedaan dalam beberapa kasus. Beberapa program memiliki kendala di mana mereka tidak dapat menggunakan pesan obrolan apa pun di bagian tertentu dari program (pikirkan waktu nyata). Dalam kasus lain, Anda mungkin ingin mengaksesnya secara langsung untuk kecepatan. Dalam kasus lain, itu karena pesan objc bertindak sebagai firewall optimasi. Akhirnya, ini dapat mengurangi operasi penghitungan referensi Anda dan meminimalkan penggunaan memori puncak (jika dilakukan dengan benar).

Jenis Nontrivial

Contoh: Jika Anda memiliki tipe C ++, akses langsung terkadang merupakan pendekatan yang lebih baik. Jenisnya mungkin tidak dapat disalin, atau mungkin tidak mudah untuk disalin.

Multithreading

Banyak dari ivar Anda tergantung pada kode. Anda harus memastikan integritas data Anda dalam konteks multithreaded. Dengan demikian, Anda dapat memilih akses langsung ke banyak anggota di bagian-bagian penting. Jika Anda tetap menggunakan accessor untuk data yang tergantung pada kode, kunci Anda biasanya harus reentrant dan Anda akan sering membuat lebih banyak akuisisi (kadang-kadang jauh lebih banyak).

Kebenaran Program

Karena subclass dapat mengesampingkan metode apa pun, pada akhirnya Anda mungkin melihat ada perbedaan semantik antara menulis ke antarmuka dibandingkan mengelola negara Anda dengan tepat. Akses langsung untuk kebenaran program sangat umum di negara bagian yang dibangun sebagian - di inisialisasi Anda dan dealloc, yang terbaik adalah menggunakan akses langsung. Anda juga dapat menemukan ini umum dalam implementasi accessor, constructor kenyamanan copy,,mutableCopy implementasi, dan pengarsipan / serialisasi.

Ini juga lebih sering terjadi karena seseorang bergerak dari semuanya memiliki pola pikir accessor readwrite publik menjadi yang menyembunyikan rincian / data implementasinya dengan baik. Kadang-kadang Anda perlu melangkah dengan benar tentang efek samping yang mungkin ditimpa oleh subclass untuk melakukan hal yang benar.

Ukuran Biner

Mendeklarasikan semua readwrite secara default biasanya menghasilkan banyak metode accessor yang tidak pernah Anda butuhkan, ketika Anda mempertimbangkan eksekusi program Anda sejenak. Jadi itu akan menambah sedikit lemak untuk program Anda dan memuat waktu juga.

Meminimalkan Kompleksitas

Dalam beberapa kasus, itu sama sekali tidak perlu untuk menambahkan + type + mempertahankan semua perancah ekstra untuk variabel sederhana seperti bool pribadi yang ditulis dalam satu metode dan dibaca di yang lain.


Itu sama sekali tidak mengatakan menggunakan properti atau aksesor buruk - masing-masing memiliki manfaat dan batasan penting. Seperti banyak bahasa OO dan pendekatan desain, Anda juga harus mendukung pengakses dengan visibilitas yang sesuai di ObjC. Akan ada waktu yang Anda butuhkan untuk menyimpang. Untuk alasan itu, saya pikir sering kali terbaik untuk membatasi akses langsung ke implementasi yang mendeklarasikan ivar (mis. Mendeklarasikannya @private).


Sunting kembali 1:

Sebagian besar dari kita telah hafal bagaimana memanggil accessor tersembunyi secara dinamis (selama kita tahu namanya ...). Sementara itu, sebagian besar dari kita belum hafal cara mengakses ivar dengan benar yang tidak terlihat (di luar KVC). Kelanjutan kelas membantu , tetapi itu memang memperkenalkan kerentanan.

Solusi ini sudah jelas:

if ([obj respondsToSelector:(@selector(setName:)])
  [(id)obj setName:@"Al Paca"];

Sekarang coba saja dengan ivar saja, dan tanpa KVC.


@Sam, terima kasih, dan pertanyaan bagus! kembali kompleksitas: itu pasti berjalan dua arah. enkapsulasi ulang - diperbarui
justin

@bbum RE: Contoh Khusus Meskipun saya setuju dengan Anda bahwa itu adalah solusi yang salah, saya tidak bisa membayangkan ada banyak pengembang yang berpengalaman percaya itu tidak terjadi; Saya telah melihatnya di program orang lain dan App Store telah melarang penggunaan API Apple pribadi.
justin

1
Tidak bisakah kamu mengakses ivar pribadi dengan object-> foo? Tidak terlalu sulit untuk diingat.
Nick Lockwood

1
Maksud saya Anda dapat mengaksesnya menggunakan penunjuk pointer dari objek dengan menggunakan sintaks C ->. Kelas Objective-C pada dasarnya hanya struct di bawah tenda, dan diberi pointer ke struct, sintaks C untuk mengakses anggota adalah ->, yang bekerja untuk ivars di kelas C objektif juga.
Nick Lockwood

1
@NickLockwood jika ivar @private , kompiler harus melarang akses anggota di luar kelas dan metode contoh - bukankah itu yang Anda lihat?
justin

76

Bagi saya biasanya kinerja. Mengakses ivar suatu objek sama cepatnya dengan mengakses anggota struct di C menggunakan pointer ke memori yang mengandung struct tersebut. Bahkan, objek Objective-C pada dasarnya adalah struct C yang terletak di memori yang dialokasikan secara dinamis. Ini biasanya secepat kode Anda bisa, kode perakitan bahkan tidak dioptimalkan dapat lebih cepat dari itu.

Mengakses ivar melalui pengambil / pengaturan melibatkan pemanggilan metode Objective-C, yang jauh lebih lambat (setidaknya 3-4 kali) daripada panggilan fungsi C "normal" dan bahkan panggilan fungsi C normal akan beberapa kali lebih lambat daripada mengakses anggota struct. Bergantung pada atribut properti Anda, implementasi penyetel / pengambil yang dihasilkan oleh kompiler dapat melibatkan pemanggilan fungsi C lain ke fungsi objc_getProperty/ objc_setProperty, karena ini harus ke retain/ copy/ autoreleaseobjek sesuai kebutuhan dan selanjutnya melakukan pemintalan untuk properti atom jika diperlukan. Ini bisa dengan mudah menjadi sangat mahal dan saya tidak berbicara tentang menjadi 50% lebih lambat.

Mari kita coba ini:

CFAbsoluteTime cft;
unsigned const kRuns = 1000 * 1000 * 1000;

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    testIVar = i;
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"1: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    [self setTestIVar:i];
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"2: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

Keluaran:

1: 23.0 picoseconds/run
2: 98.4 picoseconds/run

Ini 4,28 kali lebih lambat dan ini adalah int primitif non-atom, cukup banyak kasus terbaik ; kebanyakan kasus lain bahkan lebih buruk (coba NSString *properti atom !). Jadi, jika Anda dapat hidup dengan fakta bahwa setiap akses ivar lebih lambat 4-5 kali dari yang seharusnya, menggunakan properti baik-baik saja (setidaknya ketika datang ke kinerja), namun, ada banyak situasi di mana penurunan kinerja seperti itu adalah benar-benar tidak dapat diterima.

Perbarui 2015-10-20

Beberapa orang berpendapat, bahwa ini bukan masalah dunia nyata, kode di atas adalah murni sintetis dan Anda tidak akan pernah melihat bahwa dalam aplikasi nyata. Baiklah kalau begitu, mari kita coba sampel dunia nyata.

Kode berikut di bawah ini mendefinisikan Accountobjek. Akun memiliki properti yang menggambarkan nama (NSString * ), jenis kelamin ( enum), dan usia ( unsigned) pemiliknya, serta saldo ( int64_t). Objek akun memiliki initmetode dan compare:metode. The compare:Metode didefinisikan sebagai: perintah Perempuan sebelum laki-laki, nama memesan abjad, perintah muda sebelum tua, perintah keseimbangan rendah ke tinggi.

Sebenarnya ada dua kelas akun, AccountAdan AccountB. Jika Anda melihat implementasinya, Anda akan melihat bahwa mereka hampir seluruhnya identik, dengan satu pengecualian: compare:Metode. AccountAobjek mengakses propertinya sendiri dengan metode (pengambil), sedangkan AccountBobjek mengakses properti mereka sendiri dengan ivar. Itulah satu-satunya perbedaan! Mereka berdua mengakses properti objek lain untuk dibandingkan dengan pengambil (mengaksesnya dengan ivar tidak akan aman! Bagaimana jika objek lainnya adalah subkelas dan telah menimpa pengambil?). Perhatikan juga bahwa mengakses properti Anda sendiri sebagai ivars tidak merusak enkapsulasi (ivars masih belum umum).

Pengaturan pengujian sangat sederhana: Buat 1 akun acak Mio, tambahkan ke array dan urutkan array itu. Itu dia. Tentu saja, ada dua array, satu untuk AccountAobjek dan satu untuk AccountBobjek dan kedua array diisi dengan akun yang identik (sumber data yang sama). Kami menghitung berapa lama waktu yang dibutuhkan untuk mengurutkan array.

Inilah output dari beberapa run yang saya lakukan kemarin:

runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076

Seperti yang Anda lihat, mengurutkan array AccountBobjek adalah selalu signifikan lebih cepat daripada mengurutkan array AccountAobjek.

Siapa pun yang mengklaim bahwa perbedaan runtime hingga 1,32 detik tidak membuat perbedaan sebaiknya tidak pernah melakukan pemrograman UI. Jika saya ingin mengubah urutan penyortiran tabel besar, misalnya, perbedaan waktu seperti ini memang membuat perbedaan besar bagi pengguna (perbedaan antara UI yang dapat diterima dan yang lamban).

Juga dalam hal ini kode sampel adalah satu-satunya pekerjaan nyata yang dilakukan di sini, tetapi seberapa sering kode Anda hanya berupa roda kecil dari jarum jam yang rumit? Dan jika setiap gigi memperlambat seluruh proses seperti ini, apa artinya bagi kecepatan seluruh jarum jam pada akhirnya? Terutama jika satu langkah kerja tergantung pada output yang lain, yang berarti semua inefisiensi akan meringkas. Kebanyakan inefisiensi bukanlah masalah mereka sendiri, itu adalah jumlah mereka yang menjadi masalah bagi keseluruhan proses. Dan masalah seperti itu bukanlah apa-apa yang akan dengan mudah ditampilkan oleh seorang profiler karena seorang profiler adalah tentang menemukan titik-titik kritis, tetapi tidak satupun dari ketidakefisienan ini adalah titik-titik panas itu sendiri. Waktu CPU rata-rata hanya menyebar di antara mereka, namun masing-masing dari mereka hanya memiliki sebagian kecil dari itu, tampaknya total buang waktu untuk mengoptimalkannya. Dan itu benar,

Dan bahkan jika Anda tidak berpikir dalam hal waktu CPU, karena Anda percaya membuang-buang waktu CPU benar-benar dapat diterima, setelah semua "ini gratis", lalu bagaimana dengan biaya server hosting yang disebabkan oleh konsumsi daya? Bagaimana dengan runtime baterai perangkat seluler? Jika Anda akan menulis aplikasi seluler yang sama dua kali (mis. Peramban web seluler sendiri), sekali versi di mana semua kelas mengakses properti mereka sendiri hanya dengan getter dan sekali di mana semua kelas mengaksesnya hanya dengan ivar, menggunakan yang pertama secara konstan pasti akan menguras baterai jauh lebih cepat daripada menggunakan yang kedua, meskipun mereka setara fungsional dan bagi pengguna yang kedua bahkan mungkin akan terasa sedikit lebih cepat.

Sekarang inilah kode untuk main.mfile Anda (kode bergantung pada ARC yang diaktifkan dan pastikan untuk menggunakan pengoptimalan saat kompilasi untuk melihat efek penuh):

#import <Foundation/Foundation.h>

typedef NS_ENUM(int, Gender) {
    GenderMale,
    GenderFemale
};


@interface AccountA : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountA *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


@interface AccountB : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountB *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


static
NSMutableArray * allAcocuntsA;

static
NSMutableArray * allAccountsB;

static
int64_t getRandom ( const uint64_t min, const uint64_t max ) {
    assert(min <= max);
    uint64_t rnd = arc4random(); // arc4random() returns a 32 bit value only
    rnd = (rnd << 32) | arc4random();
    rnd = rnd % ((max + 1) - min); // Trim it to range
    return (rnd + min); // Lift it up to min value
}

static
void createAccounts ( const NSUInteger ammount ) {
    NSArray *const maleNames = @[
        @"Noah", @"Liam", @"Mason", @"Jacob", @"William",
        @"Ethan", @"Michael", @"Alexander", @"James", @"Daniel"
    ];
    NSArray *const femaleNames = @[
        @"Emma", @"Olivia", @"Sophia", @"Isabella", @"Ava",
        @"Mia", @"Emily", @"Abigail", @"Madison", @"Charlotte"
    ];
    const NSUInteger nameCount = maleNames.count;
    assert(maleNames.count == femaleNames.count); // Better be safe than sorry

    allAcocuntsA = [NSMutableArray arrayWithCapacity:ammount];
    allAccountsB = [NSMutableArray arrayWithCapacity:ammount];

    for (uint64_t i = 0; i < ammount; i++) {
        const Gender g = (getRandom(0, 1) == 0 ? GenderMale : GenderFemale);
        const unsigned age = (unsigned)getRandom(18, 120);
        const int64_t balance = (int64_t)getRandom(0, 200000000) - 100000000;

        NSArray *const nameArray = (g == GenderMale ? maleNames : femaleNames);
        const NSUInteger nameIndex = (NSUInteger)getRandom(0, nameCount - 1);
        NSString *const name = nameArray[nameIndex];

        AccountA *const accountA = [[AccountA alloc]
            initWithName:name age:age gender:g balance:balance
        ];
        AccountB *const accountB = [[AccountB alloc]
            initWithName:name age:age gender:g balance:balance
        ];

        [allAcocuntsA addObject:accountA];
        [allAccountsB addObject:accountB];
    }
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        @autoreleasepool {
            NSUInteger ammount = 1000000; // 1 Million;
            if (argc > 1) {
                unsigned long long temp = 0;
                if (1 == sscanf(argv[1], "%llu", &temp)) {
                    // NSUIntegerMax may just be UINT32_MAX!
                    ammount = (NSUInteger)MIN(temp, NSUIntegerMax);
                }
            }
            createAccounts(ammount);
        }

        // Sort A and take time
        const CFAbsoluteTime startTime1 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAcocuntsA sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime1 = CFAbsoluteTimeGetCurrent() - startTime1;

        // Sort B and take time
        const CFAbsoluteTime startTime2 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAccountsB sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime2 = CFAbsoluteTimeGetCurrent() - startTime2;

        NSLog(@"runTime 1: %f", runTime1);
        NSLog(@"runTime 2: %f", runTime2);
    }
    return 0;
}



@implementation AccountA
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (self.gender != account.gender) {
            if (self.gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![self.name isEqualToString:account.name]) {
            return [self.name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (self.age != account.age) {
            if (self.age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (self.balance != account.balance) {
            if (self.balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end


@implementation AccountB
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (_gender != account.gender) {
            if (_gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![_name isEqualToString:account.name]) {
            return [_name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (_age != account.age) {
            if (_age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (_balance != account.balance) {
            if (_balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end

3
Penjelasan yang sangat informatif dan sederhana. Suara positif untuk sampel kode
Philip007

1
Salah satu kualifikasi utama yang saya lihat di posting Anda adalah "... dari jalur kode kritis." Intinya adalah menggunakan apa yang membuat kode lebih mudah dibaca / ditulis dan kemudian mengoptimalkan apa yang Anda temukan sebagai jalur kritis. Ini akan menambah kompleksitas di tempat yang dibutuhkan.
Sandy Chapman

1
@ ViktorLexington Dalam kode saya, saya menetapkan unsigned intyang tidak pernah disimpan / dirilis, apakah Anda menggunakan ARC atau tidak. Retain / release itu sendiri mahal, jadi perbedaannya akan lebih sedikit karena manajemen retain menambahkan overhead statis yang selalu ada, menggunakan setter / pengambil atau ivar secara langsung; namun Anda masih akan menyimpan overhead satu panggilan metode tambahan jika Anda mengakses ivar secara langsung. Bukan masalah besar dalam kebanyakan kasus, kecuali jika Anda melakukannya beberapa ribu kali per detik. Apple mengatakan menggunakan getter / setter secara default, kecuali jika Anda menggunakan metode init / dealloc atau melihat ada hambatan.
Mecki

1
@Fogmeister Menambahkan contoh kode yang menunjukkan betapa mudahnya ini dapat membuat perbedaan besar dalam contoh dunia nyata yang sangat sederhana. Dan contoh ini tidak ada hubungannya dengan komputer super yang melakukan triliunan kalkulasi, ini lebih tentang menyortir tabel data yang sangat sederhana (kasus yang cukup umum di antara jutaan aplikasi).
Mecki

2
@malhal Properti yang ditandai TIDAKcopy akan membuat salinan nilainya setiap kali Anda mengaksesnya. Pengambil dari properti adalah seperti pengambil dari / properti. Kode dasarnya . Hanya setter yang menyalin nilainya dan kira-kira akan terlihat seperti ini , sedangkan a / setter terlihat seperti ini:copystrongretainreturn [[self->value retain] autorelease];[self->value autorelease]; self->value = [newValue copy];strongretain[self->value autorelease]; self->value = [newValue retain];
Mecki

9

Alasan paling penting adalah konsep OOP menyembunyikan informasi : Jika Anda mengekspos segala sesuatu melalui properti dan dengan demikian memungkinkan objek eksternal untuk mengintip internal objek lain maka Anda akan menggunakan internal ini dan dengan demikian mempersulit mengubah implementasi.

Keuntungan "kinerja minimal" dapat dengan cepat meringkas dan kemudian menjadi masalah. Saya tahu dari pengalaman; Saya bekerja pada aplikasi yang benar-benar membawa iDevices ke batas mereka dan oleh karena itu kita perlu menghindari panggilan metode yang tidak perlu (tentu saja hanya jika memungkinkan). Untuk membantu dengan tujuan ini, kami juga menghindari sintaksis titik karena mempersulit untuk melihat jumlah pemanggilan metode pada pandangan pertama: misalnya, berapa banyak pemanggilan metode yang self.image.size.widthdipicu oleh ekspresi ? Sebaliknya, Anda dapat langsung memberi tahu[[self image] size].width .

Juga, dengan penamaan ivar yang benar, KVO dimungkinkan tanpa properti (IIRC, saya bukan ahli KVO).


3
+1 Respons yang baik tentang penambahan "kinerja minimal" bertambah dan ingin melihat semua panggilan metode secara eksplisit. Menggunakan sintaks dot dengan properti pasti menutupi banyak pekerjaan yang berlangsung di custom getter / setter (terutama jika pengambil itu mengembalikan salinan sesuatu setiap kali disebut).
Sam

1
KVO tidak berfungsi untuk saya tanpa menggunakan setter. Mengubah ivar secara langsung tidak memanggil pengamat bahwa nilainya telah berubah!
Binarian

2
KVC dapat mengakses ivars. KVO tidak dapat mendeteksi perubahan pada ivars (dan sebaliknya mengandalkan accessors untuk dipanggil).
Nikolai Ruhe

9

Semantik

  • Apa yang @propertybisa menyatakan bahwa ivars tidak bisa: nonatomicdan copy.
  • Apa yang bisa diungkapkan oleh para vars yang @propertytidak bisa:
    • @protected: publik di subclass, pribadi di luar.
    • @package: public on frameworks pada 64 bit, private outside. Sama seperti @publicpada 32 bit. Lihat Apple 64-bit Class dan Kontrol Akses Variabel Instans .
    • Kualifikasi. Misalnya, array referensi objek yang kuat:id __strong *_objs .

Performa

Cerita pendek: ivars lebih cepat, tetapi itu tidak masalah untuk sebagian besar kegunaan. nonatomicproperti tidak menggunakan kunci, tetapi langsung ivar lebih cepat karena melewatkan panggilan pengakses. Untuk detail, baca email berikut dari lists.apple.com.

Subject: Re: when do you use properties vs. ivars?
From: John McCall <email@hidden>
Date: Sun, 17 Mar 2013 15:10:46 -0700

Properti memengaruhi kinerja dalam banyak hal:

  1. Seperti yang sudah dibahas, mengirim pesan untuk melakukan load / store adalah lebih lambat daripada sekadar melakukan load / store inline .

  2. Mengirim pesan untuk melakukan load / store juga sedikit lebih banyak kode yang perlu disimpan di i-cache: bahkan jika pengambil / penyetel menambahkan nol instruksi tambahan di luar hanya memuat / menyimpan, akan ada setengah padat -Lakukan instruksi tambahan pada penelepon untuk mengatur pengiriman pesan dan menangani hasilnya.

  3. Mengirim pesan memaksa entri untuk pemilih itu disimpan dalam cache metode , dan bahwa memori umumnya menempel di d-cache. Ini meningkatkan waktu peluncuran, meningkatkan penggunaan memori statis aplikasi Anda, dan membuat pergantian konteks lebih menyakitkan. Karena metode cache khusus untuk kelas dinamis untuk objek, masalah ini meningkatkan semakin banyak Anda menggunakan KVO di atasnya.

  4. Mengirim pesan memaksa semua nilai dalam fungsi agar tumpah ke tumpukan (atau disimpan di register callee-save, yang artinya tumpah pada waktu yang berbeda).

  5. Mengirim pesan dapat memiliki efek samping yang sewenang-wenang dan karenanya

    • memaksa kompiler untuk mengatur ulang semua asumsi tentang memori non-lokal
    • tidak dapat diangkat, ditenggelamkan, dipesan ulang, disatukan, atau dihilangkan.

  6. Di ARC, hasil dari pengiriman pesan akan selalu dipertahankan , baik oleh callee atau pemanggil, bahkan untuk pengembalian +0: bahkan jika metode tidak mempertahankan / melepaskan hasilnya, pemanggil tidak tahu itu dan memiliki untuk mencoba mengambil tindakan untuk mencegah hasil dari autoreleased. Ini tidak pernah dapat dihilangkan karena pengiriman pesan tidak dapat dianalisis secara statis.

  7. Di ARC, karena metode setter umumnya mengambil argumennya di +0, tidak ada cara untuk "mentransfer" penahan objek itu (yang, seperti dibahas di atas, ARC biasanya memiliki) ke dalam ivar, sehingga nilai umumnya harus mendapatkan pertahankan / dirilis dua kali .

Semua ini tidak berarti bahwa mereka selalu buruk, tentu saja - ada banyak alasan bagus untuk menggunakan properti. Perlu diingat bahwa, seperti banyak fitur bahasa lainnya, mereka tidak gratis.


John


6

Properti vs variabel instance adalah trade-off, pada akhirnya pilihan turun ke aplikasi.

Enkapsulasi / Penyembunyian Informasi Ini adalah Good Thing (TM) dari perspektif desain, antarmuka yang sempit dan hubungan minimal adalah yang membuat perangkat lunak dapat dipelihara dan dimengerti. Cukup sulit di Obj-C untuk menyembunyikan apa pun, tetapi variabel instan yang dideklarasikan dalam implementasi sedekat yang Anda dapatkan.

Performa Sementara "optimasi prematur" adalah hal yang buruk (TM), menulis kode berkinerja buruk hanya karena Anda dapat setidaknya sama buruknya. Sulit untuk membantah panggilan metode yang lebih mahal daripada beban atau toko, dan dalam kode intensif komputasi biaya segera bertambah.

Dalam bahasa statis dengan properti, seperti C #, panggilan ke setter / getter sering dapat dioptimalkan jauh oleh kompiler. Namun Obj-C dinamis dan menghapus panggilan seperti itu jauh lebih sulit.

Abstraksi Argumen terhadap variabel instan dalam Obj-C secara tradisional adalah manajemen memori. Dengan MRC instance variabel memerlukan panggilan untuk mempertahankan / melepaskan / autorelease untuk disebarkan ke seluruh kode, properti (disintesis atau tidak) menyimpan kode MRC di satu tempat - prinsip abstraksi yang merupakan Good Thing (TM). Namun dengan GC atau ARC argumen ini hilang, jadi abstraksi untuk manajemen memori tidak lagi menjadi argumen terhadap variabel instan.


5

Properti mengekspos variabel Anda ke kelas lain. Jika Anda hanya perlu variabel yang relatif terhadap kelas yang Anda buat, gunakan variabel instan. Berikut adalah contoh kecil: kelas XML untuk parsing RSS dan sejenisnya melalui sekelompok metode delegasi dan semacamnya. Praktis untuk memiliki instance NSMutableString untuk menyimpan hasil dari setiap lintasan parse yang berbeda. Tidak ada alasan mengapa kelas luar perlu mengakses atau memanipulasi string itu. Jadi, Anda cukup mendeklarasikannya di header atau secara pribadi dan mengaksesnya di seluruh kelas. Mengatur properti untuk itu mungkin hanya berguna untuk memastikan tidak ada masalah memori, menggunakan self.mutableString untuk memanggil pengambil / penentu.


5

Kompatibilitas mundur adalah faktor bagi saya. Saya tidak dapat menggunakan fitur Objective-C 2.0 karena saya sedang mengembangkan perangkat lunak dan driver printer yang harus bekerja pada Mac OS X 10.3 sebagai bagian dari persyaratan. Saya tahu pertanyaan Anda sepertinya ditargetkan di iOS, tetapi saya pikir saya masih akan membagikan alasan saya untuk tidak menggunakan properti.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.