Dlaczego miałbyś używać ivar?

153

Zwykle widzę to pytanie zadane w inny sposób, na przykład Czy każdy bluszcz musi być własnością? (i podoba mi się odpowiedź bbum na to pytanie).

Używam właściwości prawie wyłącznie w moim kodzie. Jednak co jakiś czas współpracuję z kontrahentem, który od dawna rozwija się na iOS i jest tradycyjnym programistą gier. Pisze kod, który nie deklaruje prawie żadnych właściwości i opiera się na bluszczach. Zakładam, że robi to, ponieważ 1.) przyzwyczaił się do tego, ponieważ właściwości nie zawsze istniały aż do celu C 2.0 (październik 2007) i 2.) dla minimalnego wzrostu wydajności wynikającego z braku przechodzenia przez getter / setter.

Chociaż pisze kod, który nie przecieka, nadal wolałbym, żeby używał właściwości zamiast ivars. Rozmawialiśmy o tym i on mniej więcej nie widzi powodu, aby używać właściwości, ponieważ nie używaliśmy KVO i ma doświadczenie w zajmowaniu się problemami z pamięcią.

Moje pytanie jest bardziej ... Dlaczego miałbyś kiedykolwiek chcieć użyć okresu ivar - doświadczonego lub nie. Czy naprawdę istnieje tak wielka różnica w wydajności, że użycie ivar byłoby uzasadnione?

Również dla wyjaśnienia, zastępuję metody ustawiające i pobierające w razie potrzeby i używam ivar, który koreluje z tą właściwością wewnątrz metody pobierającej / ustawiającej. Jednak poza getter / setter lub init zawsze używam self.myPropertyskładni.


Edytuj 1

Doceniam wszystkie dobre odpowiedzi. Jednym z nich, który wydaje się niepoprawny, jest to, że w przypadku ivar uzyskuje się hermetyzację, podczas gdy w przypadku właściwości nie. Po prostu zdefiniuj właściwość w kontynuacji klasy. To ukryje nieruchomość przed osobami postronnymi. Możesz również zadeklarować właściwość readonly w interfejsie i przedefiniować ją jako readwrite w implementacji, na przykład:

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

i mieć w klasie kontynuację:

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

Aby był całkowicie „prywatny”, zadeklaruj go tylko w kontynuacji klasy.

Sam
źródło
2
zagłosujcie za interesującym pytaniem - dobrze sformułowanym, a także takim, które chciałbym usłyszeć w sprawie ivars, ponieważ brzmi to tak, jakbym nauczył się robić to w sposób Sama.
Damo
2
Należy zauważyć, że automatyczne zliczanie referencji (ARC) stosuje te same korzyści związane z zarządzaniem pamięcią do ivarów, co właściwości, więc w kodzie ARC różnica dotyczy tak naprawdę hermetyzacji.
benzado
1
Twoje pytanie, a zwłaszcza część Edytuj 1, jest w rzeczywistości znacznie bardziej pouczająca niż wybrana odpowiedź.
user523234
1
To Edit1: Myślę, że można odczytać I ZAPISAĆ każdą właściwość, nawet jeśli tylko jedna deklaracja tylko do odczytu w .h, z kodowaniem klucz-wartość, np .: [obiekt setValue: [NSNumber numberWithInt: 20] forKey: @ "propertyname "];
Binarian
1
@Sam do edycji 1: Jeśli używasz właściwości prywatnej i używasz rozszerzenia / kontynuacji klasy w pliku .m, nie jest to widoczne dla podklas. Musisz ponownie napisać kod lub użyć innego .h z rozszerzeniem klasy. Łatwiej z @ protected / default.
Binarian

Odpowiedzi:

100

Kapsułkowanie

Jeśli ivar jest prywatny, inne części programu nie mogą go tak łatwo uzyskać. Mając zadeklarowaną własność, sprytni ludzie mogą łatwo uzyskać dostęp i mutować za pomocą akcesorów.

Występ

Tak, w niektórych przypadkach może to mieć znaczenie. Niektóre programy mają ograniczenia polegające na tym, że nie mogą używać komunikatów objc w niektórych częściach programu (myśląc w czasie rzeczywistym). W innych przypadkach możesz chcieć uzyskać do niego bezpośredni dostęp w celu zwiększenia szybkości. W innych przypadkach dzieje się tak, ponieważ komunikaty objc działają jak zapora optymalizacyjna. Wreszcie, może zmniejszyć liczbę operacji zliczania referencji i zminimalizować szczytowe użycie pamięci (jeśli zostanie wykonane poprawnie).

Typy nietrywialne

Przykład: jeśli masz typ C ++, bezpośredni dostęp jest czasem lepszym podejściem. Typu nie można skopiować lub kopiowanie może nie być łatwe.

Wielowątkowość

Wiele z twoich ivars jest współzależnych. Musisz zapewnić integralność danych w kontekście wielowątkowym. Dlatego możesz preferować bezpośredni dostęp do wielu członków w krytycznych sekcjach. Jeśli pozostaniesz przy dostępach do danych współzależnych, Twoje zamki muszą zazwyczaj być ponownie wprowadzane, co często kończy się o wiele więcej przejęć (czasami znacznie więcej).

Poprawność programu

Ponieważ podklasy mogą przesłonić każdą metodę, możesz w końcu zauważyć, że istnieje semantyczna różnica między pisaniem do interfejsu a odpowiednim zarządzaniem stanem. Bezpośredni dostęp do poprawności programu jest szczególnie powszechny w stanach częściowo skonstruowanych - w inicjalizatorach i w deallocprogramie najlepiej jest używać bezpośredniego dostępu. Można również znaleźć to powszechne w implementacjach akcesor, konstruktora wygody, copy, mutableCopyimplementacjach i archiwizacji / serializacji.

Jest to również częstsze, gdy przechodzimy od nastawienia wszystkiego, co ma publiczny dostęp do odczytu i zapisu, do takiego, które dobrze ukrywa szczegóły / dane implementacji. Czasami trzeba poprawnie obejść skutki uboczne, które może wprowadzić zastąpienie podklasy, aby zrobić właściwą rzecz.

Rozmiar binarny

Domyślne zadeklarowanie wszystkiego, co jest readwrite, zwykle skutkuje powstaniem wielu metod pomocniczych, których nigdy nie potrzebujesz, gdy zastanawiasz się przez chwilę nad wykonaniem programu. Więc doda trochę tłuszczu do twojego programu i czasu ładowania.

Minimalizuje złożoność

W niektórych przypadkach po prostu nie jest konieczne dodawanie + typ + utrzymywanie całego tego dodatkowego szkieletu dla prostej zmiennej, takiej jak prywatna bool, która jest zapisywana w jednej metodzie i czytana w innej.


Nie oznacza to wcale, że używanie właściwości lub akcesoriów jest złe - każdy z nich ma ważne zalety i ograniczenia. Podobnie jak w przypadku wielu języków obiektowych i podejść do projektowania, należy również preferować akcesory o odpowiedniej widoczności w ObjC. Będą chwile, kiedy będziesz musiał zboczyć. Z tego powodu myślę, że często najlepiej jest ograniczyć bezpośredni dostęp do implementacji, która deklaruje ivar (np. Zadeklarować @private).


ponownie Edycja 1:

Większość z nas nauczyła się na pamięć dynamicznego wywoływania ukrytego akcesorium (o ile znamy jego nazwę…). W międzyczasie większość z nas nie pamiętała, jak prawidłowo uzyskać dostęp do ivar, które nie są widoczne (poza KVC). Kontynuacja klasy pomaga , ale wprowadza luki w zabezpieczeniach.

To obejście jest oczywiste:

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

Teraz spróbuj tylko z ivar i bez KVC.

Justin
źródło
@Sam dzięki i dobre pytanie! ponownie złożoność: z pewnością działa w obie strony. re encapsulation - aktualizacja
justin
@bbum RE: Niezwykły przykład Chociaż zgadzam się z tobą, że jest to złe rozwiązanie, nie mogę sobie wyobrazić, że wielu doświadczonych deweloperów objc uważa, że ​​tak się nie dzieje; Widziałem to w programach innych osób, a sklepy z aplikacjami posunęły się nawet do zakazania korzystania z prywatnych interfejsów API Apple.
justin
1
Nie możesz uzyskać dostępu do prywatnego ivar za pomocą object-> foo? Nie takie trudne do zapamiętania.
Nick Lockwood
1
Miałem na myśli, że możesz uzyskać do niego dostęp za pomocą deference wskaźnika z obiektu przy użyciu składni C ->. Klasy celu-C są w zasadzie tylko strukturami pod maską, a mając wskaźnik do struktury, składnia C do uzyskiwania dostępu do elementów członkowskich to ->, która działa również dla ivars w obiektywnych klasach C.
Nick Lockwood
1
@NickLockwood jeśli ivar to @private, kompilator powinien zabronić członkom dostępu poza klasą i metodami instancji - czy to nie jest to, co widzisz?
justin
76

Dla mnie to zazwyczaj wydajność. Uzyskanie dostępu do ivar obiektu jest tak szybkie, jak uzyskanie dostępu do elementu struktury w języku C przy użyciu wskaźnika do pamięci zawierającej taką strukturę. W rzeczywistości obiekty Objective-C są w zasadzie strukturami C umieszczonymi w dynamicznie przydzielonej pamięci. Zwykle jest to tak szybkie, jak można uzyskać kod, nawet ręcznie zoptymalizowany kod asemblera nie może być szybszy.

Dostęp do ivar poprzez getter / ustawienie wymaga wywołania metody Objective-C, które jest znacznie wolniejsze (co najmniej 3-4 razy) niż „normalne” wywołanie funkcji C, a nawet normalne wywołanie funkcji C byłoby już wielokrotnie wolniejsze niż dostęp do elementu członkowskiego struktury. W zależności od atrybutów twojej właściwości, implementacja ustawiająca / pobierająca generowana przez kompilator może obejmować inne wywołanie funkcji C do funkcji objc_getProperty/ objc_setProperty, ponieważ będą one musiały retain/ copy/ autoreleaseobiekty w razie potrzeby i dalej wykonywać blokowanie spinowe dla właściwości atomowych, jeśli to konieczne. Może to łatwo stać się bardzo kosztowne i nie mówię o 50% wolniejszym.

Spróbujmy tego:

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

Wynik:

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

Jest to 4,28 razy wolniejsze i był to nieatomowy prymitywny int, właściwie najlepszy przypadek ; większość innych przypadków jest jeszcze gorsza (spróbuj NSString *właściwości atomowej !). Jeśli więc możesz żyć z faktem, że każdy dostęp do ivar jest 4-5 razy wolniejszy niż mógłby być, używanie właściwości jest w porządku (przynajmniej jeśli chodzi o wydajność), jednak istnieje wiele sytuacji, w których taki spadek wydajności jest całkowicie niedopuszczalne.

Aktualizacja 2015-10-20

Niektórzy twierdzą, że to nie jest prawdziwy problem świata, powyższy kod jest czysto syntetyczny i nigdy nie zauważysz tego w prawdziwej aplikacji. Okej, spróbujmy więc próbki z prawdziwego świata.

Poniższy kod definiuje Accountobiekty. Konto ma właściwości, które opisują imię i nazwisko ( NSString *), płeć ( enum) i wiek ( unsigned) jego właściciela, a także saldo ( int64_t). Obiekt konta ma initmetodę i compare:metodę. compare:Metoda jest zdefiniowana jako: Kobieta zlecenia przed mężczyzna, nazwy zamówić alfabetycznie młodzi zlecenia przed starym, zamówienia równowagi niskiego do wysokiego.

W rzeczywistości istnieją dwie klasy kont AccountAi AccountB. Jeśli spojrzysz na ich implementację, zauważysz, że są one prawie całkowicie identyczne, z jednym wyjątkiem: compare:metoda. AccountAobiekty uzyskują dostęp do swoich właściwości za pomocą metody (getter), podczas gdy AccountBobiekty uzyskują dostęp do własnych właściwości przez ivar. To naprawdę jedyna różnica! Obaj uzyskują dostęp do właściwości drugiego obiektu do porównania przez getter (dostęp do niego przez ivar nie byłby bezpieczny! A co, jeśli drugi obiekt jest podklasą i przesłonił funkcję pobierającą?). Zauważ również, że dostęp do własnych właściwości jako ivars nie przerywa hermetyzacji (ivars nadal nie są publiczne).

Konfiguracja testu jest naprawdę prosta: utwórz 1 losowe konta Mio, dodaj je do tablicy i posortuj tę tablicę. Otóż ​​to. Oczywiście istnieją dwie tablice, jedna dla AccountAobiektów i jedna dla AccountBobiektów, a obie tablice są wypełnione identycznymi kontami (to samo źródło danych). Sprawdzamy, ile czasu zajmuje sortowanie tablic.

Oto wynik kilku przebiegów, które wykonałem wczoraj:

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

Jak widać, sortowanie tablicy AccountBobiektów jest zawsze znaczące szybciej niż sortowanie tablicy AccountAobiektów.

Ktokolwiek twierdzi, że różnice w czasie wykonywania do 1,32 sekundy nie mają znaczenia, lepiej nigdy nie programować interfejsu użytkownika. Jeśli chcę na przykład zmienić kolejność sortowania dużej tabeli, takie różnice czasu mają ogromne znaczenie dla użytkownika (różnica między akceptowalnym a powolnym interfejsem użytkownika).

Również w tym przypadku przykładowy kod jest jedyną prawdziwą pracą wykonywaną tutaj, ale jak często twój kod jest tylko małą przekładnią skomplikowanego mechanizmu zegarowego? A jeśli każdy bieg spowalnia cały proces w ten sposób, co to ostatecznie oznacza dla szybkości całego mechanizmu zegarowego? Zwłaszcza jeśli jeden etap pracy zależy od wydajności innego, co oznacza, że ​​wszystkie nieefektywności będą się sumować. Większość nieefektywności sama w sobie nie stanowi problemu, to sama ich suma staje się problemem dla całego procesu. A taki problem to nic, co programista łatwo pokaże, ponieważ profiler służy do znajdowania krytycznych punktów zapalnych, ale żadna z tych nieefektywności nie jest sama w sobie gorącymi punktami. Czas procesora jest po prostu średnio rozłożony między nimi, ale każdy z nich ma tylko tak malutki ułamek, że wydaje się całkowitą stratą czasu na jego optymalizację. I to prawda,

A nawet jeśli nie myślisz w kategoriach czasu procesora, ponieważ uważasz, że marnowanie czasu procesora jest całkowicie dopuszczalne, w końcu „to za darmo”, to co z kosztami hostingu serwera spowodowanymi zużyciem energii? A co z czasem pracy baterii urządzeń mobilnych? Gdybyś napisał dwukrotnie tę samą aplikację mobilną (np. Własną mobilną przeglądarkę internetową), raz wersja, w której wszystkie klasy uzyskują dostęp do własnych właściwości tylko przez gettery, a raz, w której wszystkie klasy uzyskują do nich dostęp tylko przez ivars, używanie pierwszej z nich na pewno będzie wyczerpane Bateria jest znacznie szybsza niż ta druga, mimo że są one funkcjonalne, a użytkownik może nawet poczuć się nieco szybszy.

Oto kod twojego main.mpliku (kod opiera się na włączeniu ARC i pamiętaj, aby użyć optymalizacji podczas kompilacji, aby zobaczyć pełny efekt):

#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
Mecki
źródło
3
Niezwykle pouczające i praktyczne wyjaśnienie. Głos za przykładem kodu
Philip007
1
Jednym z kluczowych kwalifikacji, które widzę w Twoim poście, jest „… z krytycznych ścieżek kodu”. Chodzi o to, aby użyć tego, co ułatwia odczyt / zapis kodu, a następnie zoptymalizować to, co uważasz za ścieżki krytyczne. To zwiększy złożoność tam, gdzie jest to potrzebne.
Sandy Chapman
1
@ViktorLexington W moim kodzie ustawiałem to, unsigned intktóre nigdy nie jest zachowywane / publikowane, niezależnie od tego, czy używasz ARC, czy nie. Samo utrzymanie / zwolnienie jest kosztowne, więc różnica będzie mniejsza, ponieważ zarządzanie utrzymaniem dodaje statyczny narzut, który zawsze istnieje, bezpośrednio używając setter / getter lub ivar; jednak nadal zaoszczędzisz na jednym dodatkowym wywołaniu metody, jeśli uzyskasz bezpośredni dostęp do ivar. W większości przypadków nic wielkiego, chyba że robisz to kilka tysięcy razy na sekundę. Apple mówi, że domyślnie używaj getters / setters, chyba że jesteś w metodzie init / dealloc lub masz wąskie gardło.
Mecki
1
@Fogmeister Dodano przykład kodu, który pokazuje, jak łatwo może to spowodować ogromną różnicę w bardzo prostym przykładzie ze świata rzeczywistego. I ten przykład nie ma nic wspólnego z super komputerem wykonującym biliony obliczeń, chodzi bardziej o sortowanie naprawdę prostej tabeli danych (dość powszechny przypadek wśród milionów aplikacji).
Mecki
2
@malhal Właściwość oznaczona jako NIEcopy utworzy kopii jej wartości za każdym razem, gdy uzyskasz do niej dostęp. Funkcja pobierająca właściwość jest podobna do metody pobierającej właściwość / . Zasadniczo jest to kod . Tylko seter kopiuje wartość i będzie to mniej więcej tak wyglądało , podczas gdy seter / wygląda tak:copystrongretainreturn [[self->value retain] autorelease];[self->value autorelease]; self->value = [newValue copy];strongretain[self->value autorelease]; self->value = [newValue retain];
Mecki
9

Najważniejszym powodem jest koncepcja OOP polegająca na ukrywaniu informacji : jeśli ujawnisz wszystko za pomocą właściwości, a tym samym pozwolisz zewnętrznym obiektom na podglądanie wewnętrznych elementów innego obiektu, wykorzystasz te wewnętrzne, a tym samym skomplikujesz zmianę implementacji.

Wzrost „minimalnej wydajności” może szybko się podsumować i stać się problemem. Wiem z doświadczenia; Pracuję nad aplikacją, która naprawdę ogranicza iDevices do granic możliwości i dlatego musimy unikać niepotrzebnych wywołań metod (oczywiście tylko wtedy, gdy jest to rozsądnie możliwe). Aby pomóc w osiągnięciu tego celu, unikamy również składni z kropką, ponieważ utrudnia ona sprawdzenie liczby wywołań metod na pierwszy rzut oka: na przykład, ile wywołań metod wywołuje wyrażenie self.image.size.width? Z drugiej strony możesz od razu to stwierdzić [[self image] size].width.

Ponadto, przy prawidłowym nazewnictwie ivar, KVO jest możliwe bez właściwości (IIRC, nie jestem ekspertem KVO).

DarkDust
źródło
3
+1 Dobra odpowiedź o "minimalnym wzroście wydajności" sumuje się i chce jawnie widzieć wszystkie wywołania metod. Używanie składni kropkowej z właściwościami zdecydowanie maskuje wiele pracy wykonywanej w niestandardowych metodach pobierających / ustawiających (szczególnie jeśli ten getter zwraca kopię czegoś za każdym razem, gdy jest wywoływana).
Sam
1
KVO nie działa u mnie bez setera. Bezpośrednia zmiana ivar nie wywołuje u obserwatora, że ​​wartość się zmieniła!
Binarian
2
KVC może uzyskać dostęp do ivarów. KVO nie może wykryć zmian w ivarach (zamiast tego polega na wywołaniu akcesorów).
Nikolai Ruhe
9

Semantyka

  • Co @propertymoże wyrazić, że ivars nie mogą: nonatomici copy.
  • Co Ivars może wyrazić, @propertyczego nie można:
    • @protected: publiczne w podklasach, prywatne na zewnątrz.
    • @package: publiczny w frameworkach na 64 bitach, prywatny na zewnątrz. Tak samo jak @publicw przypadku 32 bitów. Zobacz 64-bitową kontrolę dostępu dla klas i instancji firmy Apple .
    • Kwalifikatory. Na przykład, tablice silnymi odniesieniami obiektu: id __strong *_objs.

Występ

Krótka historia: ivary są szybsze, ale nie ma to znaczenia dla większości zastosowań. nonatomicwłaściwości nie używają blokad, ale direct ivar jest szybszy, ponieważ pomija wywołanie akcesorów. Aby uzyskać szczegółowe informacje, przeczytaj następujący e-mail z 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

Właściwości wpływają na wydajność na wiele sposobów:

  1. Jak już wspomniano, wysyłanie wiadomości do załadowania / zapisania jest wolniejsze niż zwykłe ładowanie / przechowywanie w linii .

  2. Wysyłanie wiadomości w celu załadowania / zapisania jest również trochę więcej kodu, który musi być przechowywany w i-cache: nawet jeśli getter / setter dodałby zero dodatkowych instrukcji poza tylko ładowaniem / przechowywaniem, byłaby solidna połowa tuzin dodatkowych instrukcji w dzwoniącym, aby ustawić wysyłanie wiadomości i obsłużyć wynik.

  3. Wysłanie wiadomości wymusza zachowanie wpisu dla tego selektora w pliku pamięci podręcznej metody , a ta pamięć zazwyczaj w d-cache. Zwiększa to czas uruchamiania, zwiększa użycie pamięci statycznej aplikacji i sprawia, że ​​przełączanie kontekstu jest bardziej bolesne. Ponieważ pamięć podręczna metod jest specyficzna dla klasy dynamicznej obiektu, ten problem rośnie, im częściej używasz na niej KVO.

  4. Wysłanie wiadomości wymusza przelanie wszystkich wartości funkcji na stos (lub umieszczenie ich w rejestrach callee-save, co oznacza po prostu rozlanie w innym czasie).

  5. Wysłanie wiadomości może mieć dowolne skutki uboczne i dlatego

    • wymusza na kompilatorze zresetowanie wszystkich swoich założeń dotyczących pamięci nielokalnej
    • nie mogą być podnoszone, zatapiane, zmieniane w kolejności, łączone ani eliminowane.

  6. W ARC, wynik wysłania wiadomości zawsze zostanie zachowany , czy to przez wywołującego, czy wywołującego, nawet dla +0 zwrotów: nawet jeśli metoda nie zachowuje / autoodpowiada swojego wyniku, wywołujący nie wie o tym i ma spróbować podjąć działania, aby zapobiec automatycznemu udostępnianiu wyniku. Nie można tego nigdy wyeliminować, ponieważ wysyłanych wiadomości nie można analizować statycznie.

  7. W ARC, ponieważ metoda ustawiająca zwykle przyjmuje swój argument na +0, nie ma możliwości "przeniesienia" zachowania tego obiektu (który, jak omówiono powyżej, zwykle ma ARC) do ivar, więc wartość generalnie musi zostać pobrana zachować / zwolnić dwukrotnie .

Nie oznacza to oczywiście, że zawsze są złe - istnieje wiele dobrych powodów, aby korzystać z właściwości. Pamiętaj tylko, że podobnie jak wiele innych funkcji językowych, nie są one bezpłatne.


Jan.

Jano
źródło
6

Właściwości a zmienne instancji to kompromis, ostatecznie wybór sprowadza się do aplikacji.

Hermetyzacja / ukrywanie informacji To jest dobra rzecz (TM) z punktu widzenia projektowania, wąskie interfejsy i minimalne powiązania sprawiają, że oprogramowanie jest łatwe w utrzymaniu i zrozumiałe. W Obj-C dość trudno jest ukryć cokolwiek, ale zmienne instancji zadeklarowane w implementacji są tak blisko, jak to tylko możliwe.

Wydajność Chociaż „przedwczesna optymalizacja” jest złą rzeczą (TM), pisanie źle działającego kodu tylko dlatego, że można, jest co najmniej równie złe. Trudno jest argumentować, że wywołanie metody jest droższe niż ładowanie lub przechowywanie, aw kodzie wymagającym dużej mocy obliczeniowej koszt szybko się sumuje.

W języku statycznym z właściwościami, takimi jak C #, wywołania ustawiające / pobierające często mogą być zoptymalizowane przez kompilator. Jednak Obj-C jest dynamiczny i usuwanie takich wywołań jest znacznie trudniejsze.

Abstrakcja Argumentem przeciwko zmiennym instancji w Obj-C było tradycyjnie zarządzanie pamięcią. Zmienne instancji MRC wymagają, aby wywołania zatrzymania / zwolnienia / automatycznego wydania były rozproszone w całym kodzie, właściwości (syntetyzowane lub nie) utrzymują kod MRC w jednym miejscu - zasada abstrakcji, która jest Dobrą Rzeczą (TM). Jednak w przypadku GC lub ARC ten argument znika, więc abstrakcja zarządzania pamięcią nie jest już argumentem przeciwko zmiennym instancji.

CRD
źródło
5

Właściwości udostępniają zmienne innym klasom. Jeśli potrzebujesz zmiennej, która jest tylko względna w stosunku do tworzonej klasy, użyj zmiennej instancji. Oto mały przykład: klasy XML do analizowania RSS i tym podobnych przechodzą przez kilka metod delegatów i tym podobne. Praktyczne jest posiadanie wystąpienia NSMutableString do przechowywania wyników każdego innego przebiegu analizy. Nie ma powodu, dla którego klasa zewnętrzna musiałaby kiedykolwiek uzyskać dostęp do tego ciągu lub manipulować nim. Więc po prostu zadeklaruj to w nagłówku lub prywatnie i uzyskaj do niego dostęp w całej klasie. Ustawienie dla niej właściwości może być przydatne tylko w celu upewnienia się, że nie ma problemów z pamięcią, używając self.mutableString do wywołania metody pobierającej / ustawiającej.

Justin
źródło
5

Kompatybilność wsteczna była dla mnie czynnikiem. Nie mogłem używać żadnych funkcji Objective-C 2.0, ponieważ tworzyłem oprogramowanie i sterowniki drukarek, które musiały działać w systemie Mac OS X 10.3 jako część wymagań. Wiem, że Twoje pytanie wydawało się być skierowane na iOS, ale pomyślałem, że nadal będę podawać powody, dla których nie używam właściwości.

dreamlax
źródło