Neden bir ivar kullanırsın?


153

Genellikle bu sorunun başka bir şekilde sorduğunu görüyorum, her ivar bir mülk olmalı mı? (ve bbum'un bu Q'ya cevabını seviyorum).

Özellikleri neredeyse sadece kodumda kullanıyorum. Bununla birlikte, sık sık iOS'ta uzun süredir gelişmekte olan ve geleneksel bir oyun programcısı olan bir yüklenici ile çalışıyorum. Neredeyse hiç özellik ilan etmeyen kod yazar ve ivarlara yaslanır. Bunu yaptığını varsayıyorum, çünkü 1.) özellikleri alıcının / ayarlayıcıdan geçmemek için minimum performans kazancı için Objective C 2.0 (Oct '07) ve 2.) 'ye kadar her zaman mevcut değildi.

Sızıntı yapmayan bir kod yazarken, yine de ivars üzerindeki özellikleri kullanmasını tercih ederim. Bu konu hakkında konuştuk ve KVO kullanmadığımız ve bellek sorunlarıyla ilgilenme konusunda deneyimli olduğu için, mülkleri kullanmak için bir neden görmüyor.

Sorum daha fazla ... Neden bir ivar dönemini kullanmak istesiniz - deneyimli olsun ya da olmasın. Gerçekten bir ivar kullanmanın haklı çıkacağı kadar büyük bir performans farkı var mı?

Ayrıca bir açıklama noktası olarak, ayarlayıcıları ve alıcıları gerektiği gibi geçersiz kılıyorum ve alıcı / ayarlayıcının içindeki bu özellikle ilişkili olan ivar'ı kullanıyorum. Ancak, bir alıcı / ayarlayıcı veya init dışında her zaman self.myPropertysözdizimini kullanıyorum.


Düzenle 1

Tüm iyi yanıtları takdir ediyorum. Bu yanlış görünüyor ele almak istiyorum bir ivar ile değil bir özelliği ile nerede kapsülleme olsun. Sadece bir sınıf devamında özelliği tanımlayın. Bu, mülkü yabancılardan gizleyecektir. Ayrıca özelliği arabirimde salt okunur olarak bildirebilir ve uygulamada şu şekilde readwrite olarak yeniden tanımlayabilirsiniz:

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

ve sınıfta devam etme:

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

Tamamen “özel” olması için sadece sınıfın devamında ilan edin.


2
ilginç bir soru için oy verin - iyi koydu ve ayrıca ivars için davayı duymak istediğim bir tane var, sanki Sam'in yolu bana öğretildi.
Damo

2
Otomatik Referans Sayımı'nın (ARC) özelliklerle aynı bellek yönetimi avantajlarını uyguladığına dikkat edin, bu yüzden ARC kodunda fark gerçekten kapsülleme ile ilgilidir.
12'de benzado

1
Sorunuz ve özellikle Edit 1 bölümü seçilen cevaptan çok daha bilgilendirici.
user523234

1
Edit1 için: Ben anahtar değeri-kodlama ile, örneğin sadece bir .h salt okunur bildirimi bile, her özellik okumak VE YAZMAK düşünüyorum, örneğin: [object setValue: [NSNumber numberWithInt: 20] forKey: @ "propertyname "];
Binarian

1
Düzenlemenize @ 1 gönderin: Özel bir özellik kullanırsanız ve .m dosyasındaki sınıf uzantısını / sürekliliğini kullanırsanız, alt sınıflar tarafından görülemez. Kodu tekrar yazmanız veya sınıf uzantısıyla başka bir .h kullanmanız gerekir. @ Korumalı / varsayılan ile daha kolay.
Binarian

Yanıtlar:


100

kapsülleme

Ivar özelse, programın diğer bölümleri bu kadar kolay ulaşamaz. Beyan edilen bir mülkle, zeki insanlar erişimciler aracılığıyla kolayca erişebilir ve değişebilir.

Verim

Evet, bu bazı durumlarda bir fark yaratabilir. Bazı programlar, programın belirli bölümlerinde herhangi bir objc mesajını kullanamayacakları kısıtlamalara sahiptir (gerçek zamanlı düşünün). Diğer durumlarda, hız için doğrudan erişmek isteyebilirsiniz. Diğer durumlarda, bunun nedeni objc mesajlaşmasının bir optimizasyon güvenlik duvarı görevi görmesidir. Son olarak, referans sayımı işlemlerinizi azaltabilir ve en yüksek bellek kullanımını en aza indirebilir (doğru şekilde yapılırsa).

Önemsiz Türler

Örnek: C ++ türünüz varsa, doğrudan erişim bazen daha iyi bir yaklaşımdır. Tür kopyalanamayabilir veya kopyalamak önemsiz olmayabilir.

Çok iş parçacığı

İvarlarınızın çoğu kod bağımlıdır. Veri bütünlüğünüzü çok iş parçacıklı bağlamda sağlamalısınız. Bu nedenle, kritik bölümlerde birden çok üyeye doğrudan erişimi tercih edebilirsiniz. Kod bağımlı veriler için erişimcilere sadık kalırsanız, kilitlerinizin tipik olarak yeniden girmesi gerekir ve genellikle çok daha fazla edinme yaparsınız (zaman zaman önemli ölçüde daha fazla).

Programın Doğruluğu

Alt sınıflar herhangi bir yöntemi geçersiz kılabileceğinden, sonunda arabirime yazma ile durumunuzu uygun şekilde yönetme arasında anlamsal bir fark olduğunu görebilirsiniz. Program doğruluğu için doğrudan erişim, kısmen yapılandırılmış durumlarda özellikle yaygındır - başlatıcılarınızda ve deallocdoğrudan erişim kullanmak en iyisidir. Ayrıca, bir erişimcisine bir kolaylık yapıcı, uygulamalarında bu ortak bulabilir copy, mutableCopyve arşivleme / seri hale getirme uygulamaları.

Ayrıca, her şeyden herkesin uygulama ayrıntılarını / verilerini iyi gizleyen bir herkese açık okuma erişimci zihniyetine sahip olduğu için daha sık görülür . Bazen doğru şeyi yapmak için bir alt sınıfın geçersiz kılmasının getirebileceği yan etkiler etrafında doğru bir şekilde adım atmanız gerekir.

İkili Boyut

Varsayılan olarak okunan her şeyi bildirmek, programınızın bir an için yürütülmesini düşündüğünüzde genellikle hiç ihtiyacınız olmayan birçok erişimci yöntemi ile sonuçlanır. Bu yüzden programınıza ve yükleme sürelerinize biraz yağ ekleyecektir.

Karmaşıklığı En Aza İndirir

Bazı durumlarda, bir yöntemde yazılmış ve başka bir yöntemde okunan özel bir bool gibi basit bir değişken için tüm bu ekstra iskele + + + + eklemek tamamen gereksizdir.


Bu, özelliklerin veya erişimcilerin kullanımının kötü olduğunu söylemek değildir - her birinin önemli faydaları ve kısıtlamaları vardır. Birçok OO dili ve tasarıma yaklaşım gibi, ObjC'de uygun görünürlük sağlayan erişimcileri de tercih etmelisiniz. Sapmanız gereken zamanlar olacaktır. Bu nedenle, ivar bildiren uygulamaya doğrudan erişimi kısıtlamanın en iyi yol olduğunu düşünüyorum (örneğin beyan edin @private).


Yeniden Düzenleme 1:

Birçoğumuz gizli bir erişimciyi dinamik olarak nasıl arayacağımızı ezberledik (adını bildiğimiz sürece…). Bu arada, çoğumuz sahip değil ezberlemiş nasıl (KVC ötesinde) görünmez düzgün erişim Ivars için. Sınıfın devamı yardımcı olur , ancak güvenlik açıkları getirir.

Bu geçici çözüm açıktır:

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

Şimdi sadece bir ivar ile ve KVC olmadan deneyin.


@Sam teşekkürler ve iyi soru! karmaşıklık yeniden: kesinlikle her iki yönde gider. yeniden kapsülleme - güncellendi
justin

@bbum RE: Specious Örnek Her ne kadar bunun yanlış bir çözüm olduğunu kabul etsem de , bunun gerçekleşmediğine inandığım pek çok deneyimli objc geliştiricisi olduğunu düşünemiyorum; Başkalarının programlarında gördüm ve App Store'lar özel Apple API'lerinin kullanımını yasakladı.
justin

1
Object-> foo ile özel bir ivar'a erişemiyor musunuz? Hatırlaması zor değil.
Nick Lockwood

1
C -> sözdizimini kullanarak nesneden bir işaretçi deference kullanarak erişebilirsiniz. Objective-C sınıfları temelde sadece kaputun altındaki yapılardır ve bir yapıya bir işaretçi verildiğinde, üyelere erişim için C sözdizimi ->, objektif C sınıflarındaki ivarlar için de çalışır.
Nick Lockwood

1
@NickLockwood ivar ise @private, derleyici üye erişimi sınıf ve örnek yöntemleri dışında yasaklamalıdır - gördüğünüz şey bu değil mi?
justin

76

Benim için genellikle performans. Bir nesnenin bir ivarına erişmek, böyle bir yapıyı içeren belleğe bir işaretçi kullanarak C'deki bir yapı üyesine erişmek kadar hızlıdır. Aslında, Objective-C nesneleri temel olarak dinamik olarak ayrılmış bellekte bulunan C yapılarıdır. Bu genellikle kodunuzun alabileceği kadar hızlıdır, elle optimize edilmiş montaj kodu bile bundan daha hızlı olamaz.

Bir alıcıya / ayara göre bir ivar'a erişmek, "normal" C işlev çağrısından çok daha yavaş (en az 3-4 kez) ve hatta normal bir C işlev çağrısından daha yavaş olan bir Objective-C yöntem çağrısı içerir. bir yapı üyesine erişme. Mülkünüze niteliklerini bağlı olarak, derleyici tarafından oluşturulan ayarlayıcı / alıcı uygulama fonksiyonlarına başka C işlev çağrısını içerebilir objc_getProperty/ objc_setPropertybu olacak diye retain/ copy/ autoreleaseolarak nesnelerin gerekli ve gerektiğinde daha ileri atomik özellikleri için spinlocking gerçekleştirin. Bu kolayca çok pahalı olabilir ve% 50 daha yavaş olmaktan bahsetmiyorum.

Hadi bunu deneyelim:

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);

Çıktı:

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

Bu 4,28 kat daha yavaş ve bu atomik olmayan ilkel bir int, neredeyse en iyi durumdu ; diğer çoğu vaka daha da kötüdür (bir atomik NSString *özellik deneyin !). Dolayısıyla, her bir ivar erişiminin olabileceğinden 4-5 kat daha yavaş olduğu gerçeğiyle yaşayabiliyorsanız, özellikleri kullanmak iyidir (en azından performans söz konusu olduğunda), ancak böyle bir performans düşüşünün olduğu birçok durum vardır. tamamen kabul edilemez.

2015-10-20 Güncellemesi

Bazı insanlar bunun gerçek bir dünya sorunu olmadığını, yukarıdaki kodun tamamen sentetik olduğunu ve gerçek bir uygulamada bunu asla fark etmeyeceğinizi iddia ediyor. Tamam o zaman gerçek bir dünya örneği deneyelim.

Aşağıdaki kod Accountnesneleri tanımlar . Bir hesap, sahibinin adını ( NSString *), cinsiyetini ( enum) ve yaşını ( unsigned) ve bir bakiye ( int64_t) tanımlayan özelliklere sahiptir . Bir hesap nesnesinin bir inityöntemi ve compare:yöntemi vardır. compare:Olarak yöntem tanımlanır: Kadın siparişler erkek önce isimleri alfabetik sipariş, yüksek seviyesine eski, denge siparişler düşük önce genç emirleri.

Aslında iki hesap sınıfı vardır AccountAve AccountB. Uygulamalarına bakarsanız, bir istisna dışında neredeyse tamamen aynı olduklarını fark edeceksiniz: compare:Yöntem. AccountAnesneler kendi özelliklerine yöntem (getter) ile erişirken, AccountBnesneler kendi özelliklerine ivar ile erişir . Gerçekten tek fark bu! Her ikisi de diğer nesnenin özelliklerine getter ile karşılaştırmak için erişir (ivar ile erişmek güvenli olmaz! Diğer nesne bir alt sınıfsa ve alıcıyı geçersiz kılmışsa?). Ayrıca ivarlar olarak kendi mülklerinize erişimin kapsüllemeyi bozmadığını unutmayın (ivarlar hala halka açık değildir).

Test kurulumu gerçekten basittir: 1 Mio rasgele hesap oluşturun, bunları bir diziye ekleyin ve diziyi sıralayın. Bu kadar. Tabii ki, biri AccountAnesneler için diğeri nesneler için olmak üzere iki dizi vardır AccountBve her iki dizi de aynı hesaplarla doldurulur (aynı veri kaynağı). Dizileri sıralamanın ne kadar zaman aldığını biz belirliyoruz.

Dün yaptığım birkaç çalışmanın çıktısı:

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

Gördüğünüz gibi, AccountBnesne dizisini sıralamak her zamanAccountA nesne dizisini sıralamaktan daha hızlıdır .

1.32 saniyeye kadar olan çalışma zamanı farklarının hiçbir fark yaratmadığını iddia eden kişi, hiçbir zaman UI programlama yapmamalı. Örneğin, büyük bir tablonun sıralama düzenini değiştirmek istersem, bunlar gibi zaman farkları kullanıcı için büyük bir fark yaratır (kabul edilebilir ve durgun bir kullanıcı arayüzü arasındaki fark).

Ayrıca bu durumda örnek kod burada yapılan tek gerçek çalışmadır, ancak kodunuz ne kadar sıklıkla karmaşık bir saat çalışmasının küçük bir aracıdır? Ve eğer her vites tüm süreci bu şekilde yavaşlatırsa, bu sonuçta tüm saat hızının hızı için ne anlama geliyor? Özellikle bir iş adımı diğerinin çıktısına bağlıysa, bu da tüm verimsizliklerin toplanacağı anlamına gelir. Çoğu verimsizlik kendi başına bir sorun değildir, tüm süreç için bir sorun haline gelen onların toplamıdır. Ve böyle bir sorun, bir profil oluşturucunun kolayca göstereceği bir şey değildir, çünkü bir profilci kritik sıcak noktaları bulmakla ilgilidir, ancak bu verimsizliklerin hiçbiri kendi başına sıcak noktalar değildir. CPU zamanı sadece ortalama olarak aralarına yayılmıştır, ancak her birinin sadece küçük bir kısmı vardır, optimize etmek için toplam zaman kaybı gibi görünüyor. Ve bu doğru,

Ve CPU zamanı açısından düşünmese bile, CPU zamanını boşa harcamanın tamamen kabul edilebilir olduğuna inanıyorsunuz, sonuçta "ücretsiz", o zaman güç tüketiminden kaynaklanan sunucu barındırma maliyetleri ne olacak? Mobil cihazların pil ömrü ne olacak? Aynı mobil uygulamayı iki kez (örneğin kendi mobil web tarayıcısı) yazarsanız, bir kez tüm sınıfların kendi özelliklerine yalnızca alıcılar tarafından eriştiği ve tüm sınıfların sadece ivars tarafından eriştiği bir sürüm, ilkini kullanmak sürekli olarak boşalır pili, ikincisini kullanmaktan çok daha hızlıdır, işlevsel eşdeğer olmalarına rağmen ve ikincisi, muhtemelen biraz daha hızlı hissedecektir.

Şimdi main.mdosyanızın kodu (kod, ARC'nin etkinleştirilmesine dayanıyor ve tam efekti görmek için derlerken optimizasyonu kullandığınızdan emin olun):

#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
Son derece bilgilendirici ve yeryüzüne bakış. Kod örneği için upvote
Philip007

1
Yayınınızda gördüğüm anahtar niteleyicilerden biri "... kritik kod yollarından". Mesele, kodun okunmasını / yazılmasını kolaylaştıran şeyin kullanılması ve daha sonra kritik yollar olarak bulduğunuz şeyi optimize etmesidir. Bu, gerektiğinde karmaşıklığı artıracaktır.
Sandy Chapman

1
@ViktorLexington Kodumda unsigned int, ARC kullansanız da kullanmasanız da, hiçbir zaman tutulmayan / bırakılmayan bir ayar yapıyordum . Alıkoyma / bırakmanın kendisi pahalıdır, bu nedenle alıkoyma yönetimi doğrudan setter / getter veya ivar kullanarak statik bir ek yük eklediğinden fark daha az olacaktır; yine de ivar'a doğrudan erişirseniz, ek bir yöntem çağrısının yükünü yine de kaydedersiniz. Saniyede birkaç bin kez yapmadığınız sürece çoğu durumda büyük bir sorun değil. Apple, bir init / dealloc yönteminde değilseniz veya bir darboğaz tespit etmedikçe varsayılan olarak getters / setters kullan dedi.
Mecki

1
@Fogmeister Çok basit bir gerçek dünya örneğinde bunun ne kadar kolay bir fark yaratabileceğini gösteren bir kod örneği eklendi. Ve bu örneğin, trilyonlarca hesaplama yapan süper bir bilgisayarla hiçbir ilgisi yok, daha çok gerçekten basit bir veri tablosunu sıralamakla ilgilidir (milyon uygulama arasında oldukça yaygın bir durum).
Mecki

2
olarak işaretlenmiş @malhal bir özellik copyolacak DEĞİL bunu her eriştiğinizde değeri bir kopyasını yapmak. Bir alıcı copyözelliği a getter gibidirstrong / retainmülk. Kodu temelde return [[self->value retain] autorelease];. Yalnızca ayarlayıcı değeri kopyalar ve kabaca şu şekilde görünür [self->value autorelease]; self->value = [newValue copy];, oysa strong/ retainayarlayıcı şöyle görünür:[self->value autorelease]; self->value = [newValue retain];
Mecki

9

Bunun en önemli nedeni OOP bilgi gizleme konseptidir : Her şeyi özellikler yoluyla ortaya koyarsanız ve böylece harici nesnelerin başka bir nesnenin iç kısımlarına göz atmasına izin verirseniz, bu dahili olanlardan yararlanacak ve uygulamayı değiştirmeyi zorlaştıracaksınız.

"Minimum performans" kazancı hızlı bir şekilde özetlenip sorun haline gelebilir. Deneyimden biliyorum; İDevices'i gerçekten sınırlarına taşıyan bir uygulama üzerinde çalışıyorum ve bu nedenle gereksiz yöntem çağrılarından kaçınmamız gerekiyor (elbette sadece makul olan yerlerde). Bu hedefe yardımcı olmak için nokta sözdiziminden de kaçınıyoruz, çünkü ilk bakışta yöntem çağrısı sayısını görmeyi zorlaştırıyor: örneğin, ifade kaç yöntem çağrısını self.image.size.widthtetikliyor? Bunun aksine, hemen söyleyebilirsiniz [[self image] size].width.

Ayrıca, doğru ivar adlandırma ile, KVO özellikleri olmadan mümkündür (IIRC, ben bir KVO uzmanı değilim).


3
+1 "Minimum performans" ile ilgili iyi yanıt toplanması ve tüm yöntem çağrılarını açıkça görmek istemesi. Nokta sözdizimini özelliklerle birlikte kullanmak, özel alıcılarda / ayarlayıcılarda devam eden birçok işi kesinlikle maskeler (özellikle bu alıcı her çağrıldığında bir şeyin kopyasını döndürürse).
Sam

1
Bir ayarlayıcı kullanmadan KVO benim için çalışmıyor. İvar'ı doğrudan değiştirmek gözlemciye değerin değiştiği anlamına gelmez!
Binarian

2
KVC ivarlara erişebilir. KVO, ivarlardaki değişiklikleri algılayamaz (ve bunun yerine çağrılacak erişimcilere güvenir).
Nikolai Ruhe

9

semantik

  • @propertyİvarların yapamayacağı şey ne ifade edebilir:nonatomic ve copy.
  • İvarların neler ifade @propertyedebileceği:

Verim

Kısa öykü: ivarlar daha hızlıdır, ancak çoğu kullanım için önemli değildir. nonatomicözellikleri kilit kullanmaz, ancak doğrudan ivar daha hızlıdır çünkü erişimcilerin çağrısını atlar. Ayrıntılar için lists.apple.com adresinden aşağıdaki e-postayı okuyun .

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

Özellikler performansı birçok şekilde etkiler:

  1. Daha önce tartışıldığı gibi, bir yük / depo yapmak için mesaj göndermek sadece yük / depoyu satır içi yapmaktan daha yavaştır .

  2. Bir yük / mağaza yapmak için bir mesaj göndermek de i-cache'de saklanması gereken biraz daha koddur : alıcı / ayarlayıcı sadece yükün / mağazanın ötesinde sıfır ekstra talimat eklese bile, sağlam bir yarı olurdu -eğer mesaj göndermek ve sonucu işlemek için arayan ekstra bir düzine talimatlar.

  3. Mesaj göndermek, söz konusu seçicinin girişini yöntem önbelleğinde tutmasını sağlar ve bu bellek genellikle d-önbellekte . Bu, başlatma süresini artırır, uygulamanızın statik bellek kullanımını artırır ve içerik anahtarlarını daha acı verici hale getirir. Yöntem önbelleği, bir nesnenin dinamik sınıfına özgü olduğundan, bu sorun, KVO üzerinde daha fazla kullandığınızda artar.

  4. Bir mesaj göndermek , fonksiyondaki tüm değerleri yığına dökülmeye zorlar (veya callee-save kayıtlarında tutulur, bu da sadece farklı bir zamanda dökülme anlamına gelir).

  5. Mesaj göndermenin keyfi yan etkileri olabilir ve bu nedenle

    • derleyiciyi yerel olmayan bellekle ilgili tüm varsayımlarını sıfırlamaya zorlar
    • kaldırılamaz, batırılamaz, yeniden sipariş edilemez, birleştirilemez veya ortadan kaldırılamaz.

  6. ARC'de, mesaj gönderme sonucu her zaman callee veya arayan tarafından +0 döndürmeler için bile korunur : yöntem sonucunu tutmasa / otomatik olarak serbest bırakmasa bile, arayan bunu bilmez ve sonucun otomatik olarak yayınlanmasını önlemek için harekete geçmeye çalışmak. Mesaj gönderimleri statik olarak analiz edilemediğinden bu hiçbir zaman ortadan kaldırılamaz.

  7. ARC'de, bir ayarlayıcı yöntemi genellikle +0'da argümanını aldığından, bu nesnenin bir tutucusunu (yukarıda tartışıldığı gibi ARC genellikle vardır) ivar'a "aktarmanın" bir yolu yoktur, bu nedenle değer genellikle iki kez saklayın / serbest bırakın .

Bunların hiçbiri elbette her zaman kötü oldukları anlamına gelmez - özellikleri kullanmak için birçok iyi neden vardır. Diğer birçok dil özelliği gibi, bunların ücretsiz olmadığını unutmayın.


John.


6

Özellikler ve örnek değişkenler arasında bir değiş tokuş, sonuçta seçim uygulamaya iner.

Kapsülleme / Bilgi Gizleme Bu, tasarım açısından bakıldığında iyi bir şeydir (TM), dar arayüzler ve minimum bağlantı, yazılımın bakımını ve anlaşılır olmasını sağlar. Obj-C'de bir şeyi gizlemek oldukça zordur, ancak uygulamada bildirilen örnek değişkenler alacağınız kadar yakındır.

Performans "Erken optimizasyon" bir Kötü Şey (TM) olsa da, sadece yapabildiğiniz için kötü performans gösteren kod yazmak en azından kötüdür. Bir yöntem çağrısına karşı koymak zor bir yük veya mağaza daha pahalı ve hesaplama yoğun kodda maliyet yakında ekler.

C # gibi özelliklere sahip statik bir dilde, ayarlayıcılara / alıcılara yapılan çağrılar genellikle derleyici tarafından optimize edilebilir. Ancak Obj-C dinamiktir ve bu tür çağrıları kaldırmak çok daha zordur.

Soyutlama Obj-C'deki örnek değişkenlere karşı bir argüman geleneksel olarak bellek yönetimi olmuştur. MRC örnek değişkenleri ile kod boyunca yayılma / serbest bırakma / otomatik serbest bırakma çağrıları gerektirir, özellikler (sentezlenir veya edilmez) MRC kodunu tek bir yerde tutar - İyi Bir Şey (TM) olan soyutlama ilkesi. Ancak GC veya ARC ile bu argüman ortadan kalkar, bu nedenle bellek yönetimi için soyutlama artık örnek değişkenlere karşı bir argüman değildir .


5

Özellikler, değişkenlerinizi diğer sınıflara maruz bırakır. Yalnızca oluşturduğunuz sınıfa göre bir değişkene ihtiyacınız varsa bir örnek değişkeni kullanın. İşte küçük bir örnek: RSS ve benzeri döngüyü bir grup temsilci yöntem ve benzeriyle ayrıştırmak için XML sınıfları. Ayrıştırma işleminin her farklı geçişinin sonucunu saklamak için bir NSMutableString örneğine sahip olmak pratiktir. Bir dış sınıfın bu dizgiye erişmesi veya bu dizeyi değiştirmesi için hiçbir neden yoktur. Yani, sadece başlıkta veya özel olarak beyan edersiniz ve sınıf boyunca erişirsiniz. Bunun için bir özellik ayarlamak, alıcı / ayarlayıcıları çağırmak için self.mutableString komutunu kullanarak bellek sorunu olmadığından emin olmak için yararlı olabilir.


5

Geriye dönük uyumluluk benim için bir faktördü. Herhangi bir Objective-C 2.0 özelliğini kullanamadım çünkü Mac OS X 10.3 üzerinde bir gereksinimin parçası olarak çalışmak zorunda olan yazılım ve yazıcı sürücüleri geliştiriyordum. Sorunuzun iOS'u hedef aldığını biliyorum, ancak mülk kullanmama nedenlerimi hala paylaşacağımı düşündüm.

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.