Навіщо використовувати ти івар?


153

Зазвичай я бачу, що це питання задається іншим способом, наприклад, чи повинен кожен ivar бути власністю? (і мені подобається відповідь bbum на це питання).

Я використовую властивості майже виключно у своєму коді. Однак так часто я працюю з підрядником, який давно розвивається на iOS і є традиційним ігровим програмістом. Він пише код, який декларує майже відсутність властивостей і спирається на ivars. Я припускаю, що він робить це тому, що 1.) він звик до цього, оскільки властивості не завжди існували до Objective C 2.0 (Oct '07) та 2.) для мінімального збільшення продуктивності, не проходячи через геттер / сеттер.

Поки він пише код, який не протікає, я все-таки віддав перевагу йому використовувати властивості над ivars. Ми говорили про це, і він більш-менш не бачить причини використовувати властивості, оскільки ми не використовували KVO, і він досвідчений з питаннями пам'яті.

Моє запитання більше ... Чому б вам хотілося використовувати період ivar - досвідчений чи ні. Чи дійсно така велика різниця у продуктивності, що використання ivar було б виправдано?

Крім того, в якості уточнення я переосмислюю сетерів та геттерів за необхідності та використовую ivar, який співвідноситься з цією властивістю всередині геттера / сеттера. Однак, поза геттером / сеттером чи ініціатом, я завжди використовую self.myPropertyсинтаксис.


Редагуйте 1

Я ціную всі хороші відгуки. Я хотів би звернутися до цього, що здається невірним, це те, що за допомогою ivar ви отримуєте інкапсуляцію там, де ви не маєте властивості. Просто визначте властивість у продовженні класу. Це приховає майно від сторонніх людей. Ви також можете оголосити властивість лише заново в інтерфейсі та перевизначити його як readwrite в реалізації, наприклад:

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

і мають у класі продовження:

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

Щоб він був повністю "приватним", оголошуйте це лише у продовженні класу.


2
наголос на цікаве запитання - добре сказано, а також на те, що я хотів би розглянути справу щодо ivars, бо це звучить так, ніби мене вчили робити це так, як Сем.
Дамо

2
Зауважте, що автоматичний підрахунок посилань (ARC) застосовує ті ж переваги управління пам'яттю до ivars, що і властивості, тому в коді ARC різниця насправді стосується інкапсуляції.
бензадо

1
Ваше запитання, особливо частина редагування 1, насправді набагато більш інформативна, ніж обрана відповідь.
користувач523234

1
Для редагування1: Я думаю, що можна прочитати І ЗАписати кожне властивість, навіть коли лише одна декларація зчитування лише в .h, з кодуванням Key-Value, наприклад: [object setValue: [NSNumber numberWithInt: 20] forKey: @ "namename "];
Бінаріан

1
@ Примітка до редагування 1: Якщо ви використовуєте приватну власність і використовуєте розширення / продовження класу у файлі .m, це не видно для підкласів. Вам потрібно ще раз написати код або використати інший .h із розширенням класу. Простіше з @ захищеним / за замовчуванням.
Бінаріан

Відповіді:


100

Інкапсуляція

Якщо ivar приватний, інші частини програми не можуть на нього так легко потрапити. Маючи заявлену власність, розумні люди можуть отримати доступ та мутувати досить легко через аксесуари.

Продуктивність

Так, це може змінити ситуацію в деяких випадках. Деякі програми мають обмеження, коли вони не можуть використовувати будь-які обмінні повідомлення в певних частинах програми (думайте в реальному часі). В інших випадках ви можете отримати доступ до нього безпосередньо для швидкості. В інших випадках це тому, що обмінними повідомленнями виступає брандмауер оптимізації. Нарешті, це може зменшити операції підрахунку опорних даних і мінімізувати пікове використання пам'яті (якщо виконано правильно).

Нетривіальні типи

Приклад: Якщо у вас тип C ++, прямий доступ іноді є лише кращим підходом. Тип може бути неможливим для копіювання або копіювати не може.

Багатопотоковість

Багато ваших іварів залежать від коду. Ви повинні забезпечити цілісність своїх даних у багатопотоковому контексті. Таким чином, ви можете надавати перевагу прямому доступу до кількох членів у критичних розділах. Якщо ви дотримуєтесь аксесуарів для даних, що залежать від коду, ваші блокування, як правило, мають бути рецензованими, і ви часто будете робити набагато більше придбань (часом значно більше).

Правильність програми

Оскільки підкласи можуть перекрити будь-який метод, ви, зрештою, побачите, що між записом в інтерфейс та правильним керуванням вашим станом є семантична різниця. Прямий доступ до коректності програми особливо часто зустрічається в частково побудованих станах - у ваших ініціалізаторах та в dealloc, найкраще використовувати прямий доступ. Ви також можете знайти цю загальні в реалізаціях аксессор, конструктор зручності, copy, mutableCopyвпровадження та архівування / сериализации.

Це також частіше, коли ви переходите від усього, що має загальнодоступний режим мислення доступу до того, який добре приховує деталі / дані про його реалізацію. Іноді вам потрібно правильно обійти побічні ефекти, які може ввести переосмислення підкласу, щоб зробити все правильно.

Бінарний розмір

Оголошення прочитаного запису за замовчуванням зазвичай призводить до багатьох методів доступу, які вам ніколи не знадобляться, коли ви враховуєте виконання програми на мить. Таким чином, це додасть трохи жиру у вашу програму та час завантаження.

Мінімізує складність

У деяких випадках додавати + type + підтримувати всі додаткові риштування для простої змінної, наприклад приватного bool, що записується в одному методі та читати в іншому, просто зовсім зайвим.


Це зовсім не означає, що використання властивостей або аксесуарів є поганим - кожен має важливі переваги та обмеження. Як і багато мов OO та підходів до проектування, ви також повинні надавати перевагу аксесуарам із відповідною видимістю у ObjC. Будуть випадки, коли потрібно відхилитися. З цієї причини, я думаю, що часто краще обмежувати прямий доступ до реалізації, яка декларує ivar (наприклад, оголосити його @private).


змінити 1:

Більшість із нас запам'ятовували, як динамічно викликати прихований аксесуар (поки ми знаємо ім'я…). Тим часом, більшість із нас не запам'ятала, як правильно отримати доступ до інвалідів, які не видно (за межами KVC). Продовження класу допомагає , але вводить вразливості.

Це рішення очевидно:

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

Тепер спробуйте лише з ivar та без KVC.


@ Сам дякую, і гарне запитання! повторна складність: це, безумовно, йде обома напрямками. повторна інкапсуляція - оновлено
Justin

@bbum RE: Прекрасний приклад Хоча я згоден з вами, що це неправильне рішення, я не можу уявити, що багато досвідчених розробників objc вважають, що це просто не відбувається; Я бачив це в інших програмах, а магазини додатків зайшли заборонити використання приватних API Apple.
Джастін

1
Ви не можете отримати доступ до приватного ivar за допомогою об’єкта-> foo? Не так важко запам'ятати.
Нік Локвуд

1
Я мав на увазі, що ви можете отримати доступ до нього, використовуючи затримку вказівника від об'єкта, використовуючи синтаксис C ->. Класи Objective-C - це, як правило, лише структури під кришкою, і, даючи вказівник на структуру, синтаксис C для доступу до членів ->, який працює для ivars і в об'єктивних класах C.
Нік Локвуд

1
@NickLockwood, якщо є ivar @private, компілятор повинен заборонити членам доступ поза класом та методами екземплярів - це не те, що ви бачите?
Джастін

76

Для мене це зазвичай вистава. Доступ до ivar об’єкта такий же швидкий, як доступ до члена структури в C за допомогою вказівника на пам'ять, що містить таку структуру. Насправді об’єкти Objective-C - це в основному структури C, розташовані в динамічно розподіленій пам'яті. Зазвичай це так швидко, як ваш код може отримати, навіть навіть оптимізований вручну код складання не може бути швидшим за це.

Доступ до ivar через getter / налаштування передбачає виклик методу Objective-C, який набагато повільніше (принаймні в 3-4 рази), ніж "звичайний" виклик функції C, і навіть звичайний виклик функції C вже буде в кілька разів повільніше, ніж доступ до члена структури. Залежно від атрибутів вашої власності, реалізація сеттера / геттера, що генерується компілятором, може включати інший виклик функції C до функцій objc_getProperty/ objc_setProperty, оскільки для цього доведеться retain/ copy/ autoreleaseоб'єкти за необхідності та надалі виконувати спінінг для атомних властивостей, де це необхідно. Це може бути дуже дорогим, і я не кажу про те, щоб бути на 50% повільніше.

Спробуємо це:

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

Вихід:

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

Це в 4,28 рази повільніше, і це був неатомний примітивний інт, майже найкращий випадок ; більшість інших випадків ще гірші (спробуйте атомну NSString *властивість!). Тож якщо ви можете жити з тим, що кожен доступ до ivar у 4-5 разів повільніше, ніж міг би бути, використання властивостей добре (принаймні, коли мова йде про продуктивність), проте існує маса ситуацій, коли таке зниження продуктивності абсолютно неприйнятний.

Оновлення 2015-10-20

Деякі люди стверджують, що це не реальна світова проблема, наведений вище код є чисто синтетичним, і ви цього ніколи не помітите в реальній програмі. Гаразд, спробуємо справжній зразок світу.

Наступний код визначає Accountоб'єкти. В обліковому записі є властивості, які описують ім’я ( NSString *), стать ( enum) та вік ( unsigned) його власника, а також баланс ( int64_t). Об'єкт рахунку має initметод і compare:метод. compare:Метод визначається як: Жіночий замовлення , перш ніж чоловік, імена замовлення по алфавітом, молоді замовлення до старого, замовлення балансу від низького до високого.

Насправді існує два класи облікових записів, AccountAі AccountB. Якщо ви подивитесь на їх реалізацію, то помітите, що вони майже повністю однакові, за винятком: compare:Метод. AccountAоб'єкти отримують доступ до власних властивостей методом (getter), тоді як AccountBоб’єкти отримують доступ до власних властивостей за допомогою ivar. Це справді єдина різниця! Вони обидва отримують доступ до властивостей іншого об'єкта для порівняння з користувачем getter (доступ до нього через ivar не був би безпечним! Що робити, якщо інший об’єкт є підкласом і перекрив гетьтер?). Також зауважте, що доступ до власних властивостей як ivars не порушує інкапсуляцію (івари досі не є загальнодоступними).

Тестова настройка дійсно проста: Створіть 1 Міо випадкових облікових записів, додайте їх до масиву та сортуйте цей масив. Це воно. Звичайно, є два масиви, один для AccountAоб’єктів і один для AccountBоб'єктів, і обидва масиви заповнені однаковими рахунками (одне і те ж джерело даних). Ми час, скільки часу займає сортування масивів.

Ось результат декількох запусків, які я зробив вчора:

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

Як бачимо, сортування масиву AccountBоб’єктів завжди важливіше швидше, ніж сортування масиву AccountAоб’єктів.

Той, хто стверджує, що різниця часу виконання до 1,32 секунди не має жодної різниці, краще ніколи не займатися програмуванням інтерфейсу. Якщо я хочу змінити порядок сортування великої таблиці, наприклад, різниці в часі, подібні цій, роблять величезну різницю для користувача (різниця між прийнятним та млявим інтерфейсом).

Також у цьому випадку зразок коду є єдиною реальною роботою, яка виконується тут, але як часто ваш код є лише невеликою передачею складної годинникової роботи? І якщо кожна передача сповільнює весь цей процес так, що це означає для швидкості всієї годинникової роботи? Особливо, якщо один робочий крок залежить від результату іншого, а це означає, що всі неефективність підсумовуються. Більшість неефективності не є проблемою самостійно, саме їхня сума стає проблемою для всього процесу. І така проблема - нічого, що профілер легко покаже, тому що профайлер - це пошук критичних гарячих точок, але жодна з цих неефективностей не є гарячими точками самостійно. Час процесора просто середньо поширюється серед них, але кожен з них має лише таку крихітну його частину, що здається загальною тратою часу на його оптимізацію. І це правда,

І навіть якщо ви не думаєте з точки зору часу процесора, оскільки ви вважаєте, що витрачати час на процесор цілком прийнятно, адже "це безкоштовно", то як щодо витрат на хостинг сервера, викликаних споживанням енергії? А як щодо часу роботи акумуляторів мобільних пристроїв? Якщо ви два рази писали б один і той же мобільний додаток (наприклад, власний веб-браузер для мобільних пристроїв), один раз версія, де всі класи отримують доступ до власних властивостей лише гетьтерами, і один раз, коли всі класи отримують доступ до них лише ivars, використання першого постійно вичерпується акумулятор набагато швидше, ніж використання другого, хоча вони функціонально еквівалентні, а користувачеві другий навіть, мабуть, навіть відчує себе трохи швидше.

Тепер ось код вашого main.mфайлу (код покладається на включення ARC і обов'язково використовуйте оптимізацію під час компіляції, щоб побачити повний ефект):

#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
Надзвичайно інформативне та непрофесійне пояснення. Заявка на зразок коду
Philip007

1
Один із ключових класифікаторів, які я бачу у вашій публікації, - "... з критичних кодових шляхів". Сенс у тому, що використовувати те, що полегшує код для читання / запису, а потім оптимізуйте те, що вважається критичним. Це додасть складності там, де це потрібно.
Сенді Чапман

1
@ViktorLexington У своєму коді я встановлював, unsigned intякий ніколи не зберігається / не випускається, використовуєте ви ARC чи ні. Сам режим збереження / випуску є дорогим, тому різниця буде меншою, оскільки управління збереженням додає статичні накладні витрати, які завжди існують, безпосередньо використовуючи setter / getter або ivar; але ви все одно збережете накладні витрати на один додатковий виклик методу, якщо матимете доступ до ivar безпосередньо. У більшості випадків це не велика справа, якщо ви не робите це кілька тисяч разів на секунду. Apple каже, що за замовчуванням використовуйте геттерів / сетерів, якщо ви не використовуєте метод init / dealloc або не знайдете вузького місця.
Mecki

1
@Fogmeister Додано зразок коду, який показує, як легко це може зробити величезну зміну в дуже простому прикладі реального світу. І цей приклад не має нічого спільного з тим, що суперкомп'ютер робить трильйони обчислень, це більше про сортування дійсно простої таблиці даних (досить поширений випадок серед мільйонів додатків).
Мецьки

2
@malhal Властивість, позначена як copy, НЕ буде робити копію її значення кожного разу, коли ви отримуєте доступ до неї. Поглинач по copyвласності, як геттер про наявність strong/ retainвласності. Це в основному код return [[self->value retain] autorelease];. Тільки сетер копіює значення, і воно буде приблизно таким чином [self->value autorelease]; self->value = [newValue copy];, тоді як a strong/ retainsetter виглядає так:[self->value autorelease]; self->value = [newValue retain];
Mecki

9

Найважливішою причиною є концепція OOP про приховування інформації : Якщо ви відкриєте все за допомогою властивостей і таким чином дозволите зовнішнім об'єктам зазирнути до внутрішніх приміщень іншого об'єкта, ви скористаєтесь цим внутрішнім і тим самим ускладните зміну реалізації.

Приріст "мінімальної продуктивності" може швидко підсумовуватись, а потім стати проблемою. Я знаю з досвіду; Я працюю над додатком, який дійсно забирає iDevices до своїх меж, і тому нам потрібно уникати зайвих дзвінків методів (звичайно, лише де це можливо). Для досягнення цієї мети ми також уникаємо синтаксису крапок, оскільки важко побачити кількість викликів методів з першого погляду: наприклад, скільки викликів методу викликає вираз self.image.size.width? Навпаки, ви можете негайно розказати з [[self image] size].width.

Крім того, при правильному іменуванні ivar KVO можливий без властивостей (IIRC, я не експерт KVO).


3
+1 Хороша відповідь про підсилення "мінімальної продуктивності" та бажання чітко переглядати всі виклики методів. Використання крапкового синтаксису з властивостями, безумовно, маскує велику роботу, яка триває у користувальницьких програмах / сетерах (особливо, якщо цей отримувач повертає копію чогось кожного разу, коли він називається).
Сем

1
KVO не працює для мене без використання сетера. Зміна ivar безпосередньо не закликає спостерігача, щоб значення змінилося!
Бінаріан

2
KVC може отримати доступ до ivars. KVO не може виявити зміни в ivars (а натомість покладається на виклики аксесуарів).
Микола Рухе

9

Семантика

  • Що @propertyможе виразити, що івари не можуть: nonatomicі copy.
  • Що виразити ivars, що @propertyне може:
    • @protected: загальнодоступний для підкласів, приватний зовні.
    • @package: загальнодоступна в рамках на 64 бітах, приватна зовні. Те саме, що @publicна 32 біті. Див. 64-бітний клас Apple і контроль змінного доступу до Apple .
    • Кваліфікатори. Наприклад, масиви сильних посилань на об'єкти: id __strong *_objs.

Продуктивність

Коротка історія: ivars швидше, але це не має значення для більшості застосувань. nonatomicвластивості не використовують блокування, але прямий ivar швидше, оскільки він пропускає виклик доступу. Для детальної інформації прочитайте наступний електронний лист від list.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

Властивості впливають на продуктивність багатьма способами:

  1. Як уже обговорювалося, надсилання повідомлення для завантаження / зберігання відбувається повільніше, ніж просто виконувати завантаження / зберігати вбудований .

  2. Надсилання повідомлення для завантаження / зберігання - це також трохи більше коду, який потрібно зберігати в i-кеші: навіть якщо геттер / сеттер додав нульові додаткові інструкції, крім просто завантаження / зберігання, було б міцною половиною -додаткові додаткові інструкції у абонента для налаштування відправки повідомлення та обробки результату.

  3. Надсилання повідомлення змушує запис цього селектора зберігатись у кеші методу , і ця пам'ять, як правило, зберігається в d-кеші. Це збільшує час запуску, збільшує статичне використання пам’яті у вашому додатку та робить переключення контексту більш болючими. Оскільки кеш методу є специфічним для динамічного класу для об'єкта, ця проблема збільшується, чим більше ви використовуєте на ньому KVO.

  4. Відправлення повідомлення змушує всі значення функції переливатися на стек (або зберігатись у реєстрах збереження виклику, що просто означає розплив в інший час).

  5. Відправлення повідомлення може мати довільні побічні ефекти, а отже

    • змушує компілятор скинути всі свої припущення щодо нелокальної пам'яті
    • не може бути піднято, затоплено, переупорядковано, з'єднано або усунено.

  6. В ARC результат відправки повідомлення завжди буде зберігатися або позивачем, або абонентом, навіть для повернень +0: навіть якщо метод не зберігає / не випускає результат, абонент не знає цього і має намагатися вжити заходів, щоб результат не вийшов автоматично. Це ніколи не можна усунути, оскільки надсилання повідомлень не піддається статистиці.

  7. У ARC, оскільки метод сеттера зазвичай приймає свій аргумент на рівні +0, немає можливості "перенести" збереження цього об'єкта (який, як обговорювалося вище, зазвичай в ARC), в ivar, тому значення, як правило, має отримати утримувати / звільняти двічі .

Ніщо з цього не означає, що вони завжди погані, звичайно - є багато вагомих причин використовувати властивості. Просто пам’ятайте, що, як і багато інших мовних функцій, вони не безкоштовні.


Джон.


6

Змінні властивості та екземпляри є компромісним, зрештою вибір зводиться до програми.

Інкапсуляція / приховування інформації Це хороша річ (TM) з точки зору дизайну, вузькі інтерфейси і мінімальна зв'язок - це те, що робить програмне забезпечення доступним і зрозумілим. У Obj-C досить важко ховати що-небудь, але змінні екземплярів, оголошені в реалізації, наближаються так само, як ви отримаєте.

Продуктивність Хоча "передчасна оптимізація" - це погана річ (TM), писати неякісний код просто тому, що ви можете принаймні настільки ж погано. Важко сперечатися з тим, що виклик методу коштує дорожче, ніж завантаження або магазин, а в обчислювально інтенсивному коді вартість незабаром збільшується.

Статичною мовою з властивостями, такими як C #, виклики сеттерам / getters часто можуть бути оптимізовані компілятором. Однак Obj-C динамічний, і видаляти такі дзвінки набагато складніше.

Абстракція Аргументом проти змінних екземплярів у Obj-C традиційно є управління пам'яттю. Із змінними екземплярів MRC потрібні виклики збереження / випуску / автоматичного випуску для розповсюдження по всьому коду, властивості (синтезовані чи ні) зберігають код MRC в одному місці - принцип абстракції, який є доброю річчю (TM). Однак при GC або ARC цей аргумент відходить, тому абстракція для управління пам'яттю вже не є аргументом проти змінних екземплярів.


5

Властивості експонують ваші змінні інші класи. Якщо вам просто потрібна змінна, що стосується лише класу, який ви створюєте, використовуйте змінну примірника. Ось невеликий приклад: класи XML для аналізу RSS та подібних циклів через купу делегатних методів тощо. Практично мати примірник NSMutableString, щоб зберігати результат кожного різного проходу синтаксичного аналізу. Немає жодних причин, чому сторонній клас не повинен мати доступ до цього рядка чи маніпулювати ним. Отже, ви просто оголосите це у заголовку чи приватно та отримаєте доступ до нього протягом усього класу. Встановлення властивості для нього може бути корисним лише для того, щоб упевнитись, що немає проблем з пам'яттю, використовуючи self.mutableString для виклику геттера / сеттера.


5

Зворотна сумісність була для мене фактором. Я не міг використовувати жодні функції Objective-C 2.0, оскільки розробляв драйвери програмного забезпечення та принтерів, які повинні були працювати на Mac OS X 10.3 як частина вимоги. Я знаю, що ваше запитання здавалося націленим на iOS, але я подумав, що все-таки поділюся своїми причинами не використовувати властивості.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.