Зачем вам использовать ивара?

Я обычно вижу, что этот вопрос задан другим способом, например Должен ли каждый ivar быть свойством? (и мне нравится bbum ответ на этот вопрос Q).

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

Пока он пишет код, который не течет, я бы предпочел, чтобы он использовал свойства над ivars. Мы говорили об этом, и он более или менее не видит смысла использовать свойства, так как мы не использовали KVO, и он испытывал проблемы с памятью.

Мой вопрос больше... Почему вы когда-нибудь захотите использовать период ivar - опытный или нет. Действительно ли велика разница в производительности, что использование ivar будет оправдано?

Также как точка разъяснения, я переопределяю сеттеры и геттеры по мере необходимости и использую ivar, который коррелирует с этим свойством внутри геттера/сеттера. Однако за пределами getter/setter или init я всегда использую синтаксис self.myProperty.


Изменить 1

Я ценю все хорошие ответы. Тот, который я хотел бы адресовать, кажется неправильным, заключается в том, что с ivar вы получаете инкапсуляцию, где с собственностью вы этого не делаете. Просто определите свойство в продолжении класса. Это скроет собственность от посторонних. Вы также можете объявить свойство readonly в интерфейсе и переопределить его как readwrite в реализации, например:

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

и имеют в продолжении класса:

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

Чтобы он полностью "private" объявлял его только в продолжении класса.

Ответ 1

Инкапсуляция

Если ivar является приватным, другие части программы не могут легко справиться с этим. С объявленным свойством умные люди могут легко и легко получить доступ через аксессуаров.

Производительность

Да, это может иметь значение в некоторых случаях. Некоторые программы имеют ограничения, когда они не могут использовать обмен сообщениями objc в определенных частях программы (подумайте в реальном времени). В других случаях вы можете получить доступ к нему напрямую для скорости. В других случаях это связано с тем, что обмен сообщениями objc выступает в качестве брандмауэра оптимизации. Наконец, он может уменьшить количество операций ссылки и минимизировать использование пиковой памяти (если все сделано правильно).

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

Пример. Если у вас есть тип С++, прямой доступ - это просто лучший подход. Тип не может быть скопирован или не может быть тривиальным для копирования.

Многопоточность

Многие из ваших иваров являются взаимозависимыми. Вы должны обеспечить целостность данных в многопоточном контексте. Таким образом, вы можете поддерживать прямой доступ к нескольким членам в критических разделах. Если вы придерживаетесь доступа к созависимым данным, ваши блокировки обычно должны быть реентерабельными, и вы часто будете получать гораздо больше покупок (значительно чаще).

Коррекция программы

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

Это также более часто, когда вы переходите от того, что все имеет общедоступный подход к восприятию readwrite, к тому, который хорошо скрывает его детали/данные реализации. Иногда вам нужно правильно подойти к побочным эффектам, которые может быть введено переопределением подкласса, чтобы делать правильные вещи.

Двоичный размер

Объявление всего readwrite по умолчанию обычно приводит к множеству методов доступа, которые вам никогда не нужны, когда вы считаете выполнение своей программы на мгновение. Таким образом, он добавит немного жира в вашу программу и время загрузки.

Сведение к минимуму сложности

В некоторых случаях совершенно необязательно добавлять + тип + поддерживать все эти дополнительные леса для простой переменной, такой как частный bool, который написан одним способом и читать в другом.


Это вовсе не означает, что использование свойств или аксессоров является плохим - у каждого есть важные преимущества и ограничения. Как и многие языки OO и подходы к дизайну, вы также должны предлагать аксессурам с соответствующей видимостью в ObjC. Будут моменты, когда вам нужно отклоняться. По этой причине я считаю, что часто лучше ограничивать прямой доступ к реализации, которая объявляет ivar (например, объявляет ее @private).


изменить 1:

Большинство из нас запомнили, как динамически называть скрытый аксессуар (если мы знаем имя...). Между тем, большинство из нас не запомнили, как правильно обращаться к иварам, которые не видны (за пределами KVC). Продолжение класса помогает, но оно создает уязвимости.

Это обходное решение очевидно:

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

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

Ответ 2

Для меня это обычно производительность. Доступ к ivar объекта происходит так же быстро, как доступ к элементу struct в C, используя указатель на память, содержащую такую ​​структуру. Фактически, объекты Objective-C в основном представляют собой структуры C, расположенные в динамически распределенной памяти. Это обычно так же быстро, как ваш код может получить, даже ручной оптимизированный ассемблерный код может быть быстрее.

Доступ к ivar через getter/setting включает вызов метода Objective-C, который намного медленнее (по крайней мере 3-4 раза), чем "обычный" вызов функции C, и даже обычный вызов функции C уже будет кратным раза медленнее, чем доступ к элементу struct. В зависимости от атрибутов вашего свойства реализация setter/getter, сгенерированная компилятором, может включать другой вызов функции 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 раза медленнее, и это был неатомный примитивный int, в значительной степени лучший случай; большинство других случаев еще хуже (попробуйте атомное свойство 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 не будет безопасным! Что, если другой объект является подклассом и переопределил getter?). Также обратите внимание, что доступ к вашим собственным свойствам как ivars не нарушает инкапсуляцию (ivars еще не публично).

Настройка теста очень проста: создайте 1 Mio случайные учетные записи, добавьте их в массив и отсортируйте этот массив. Это. Конечно, есть два массива: один для объектов 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

Самая важная причина - скрытие информации о ООП: если вы раскрываете все через свойства и, таким образом, позволяете внешним объектам заглядывать в другие объекты, то вы будете использовать эти внутренние и, таким образом, усложнить изменение реализации.

"Минимальная производительность" может быстро подвести итог, а затем стать проблемой. Я знаю по опыту; Я работаю над приложением, которое действительно делает iDevices до предела, и поэтому нам нужно избегать ненужных вызовов методов (конечно, только там, где это возможно). Чтобы помочь с этой целью, мы также избегаем точечного синтаксиса, так как это затрудняет просмотр количества вызовов методов с первого взгляда: например, сколько вызовов методов вызывает выражение self.image.size.width триггер? В отличие от этого, вы можете сразу сказать с помощью [[self image] size].width.

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

Ответ 4

Семантика

  • Что @property может выразить, что ivars не может: nonatomic и copy.
  • Что может показаться ivars, что @property не может:

Производительность

Рассказ: ivars быстрее, но для большинства целей это не имеет значения. nonatomic свойства не используют блокировки, но прямой ivar быстрее, потому что он пропускает вызов доступа. Подробнее читайте в email на странице lists.apple.com.

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

Свойства влияют на производительность множеством способов:

  • Как уже говорилось, отправка сообщения для загрузки/хранения медленнее, чем просто загрузка/сохранение встроенного.

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

  • Отправка сообщения заставляет элемент для этого селектора храниться в кеше , и эта память, как правило, D-кэш. Это увеличивает время запуска, увеличивает статическую память использование вашего приложения и делает контекстные переключатели более болезненными. Поскольку метод cache специфичен для динамического класса для объекта, этот проблема увеличивается, тем больше вы используете KVO на ней.

  • Отправка сообщения заставляет все значения в функции, которые будут пролиты в стек (или хранится в регистре сохранения сообщений, что просто означает разлив в другое время).

  • Отправка сообщения может иметь произвольные побочные эффекты и, следовательно,

    • заставляет компилятор reset все его предположения о нелокальной памяти
    • нельзя поднять, потопить, переупорядочить, объединить или устранить.

  • В ARC результат отправки сообщения всегда будет сохраняться либо вызывающим, либо вызывающим, даже для +0 возвратов: даже если метод не сохраняет/автоопределяет его результат, вызывающий не знает это и должно попытаться принять меры, чтобы предотвратить получение результата autoreleased. Это никогда не может быть устранено, поскольку сообщения отправляются не статически анализируемый.

  • В ARC, поскольку метод setter обычно принимает свой аргумент в +0, нет способа "переносить" сохранение этого объекта (который, как обсуждалось выше, ARC обычно имеет) в ivar, поэтому значение как правило, нужно дважды удерживать/отпускать.

Ничто из этого не означает, что они всегда плохие, конечно - есть много хороших причин для использования свойств. Просто имейте в виду, что, как многие другие функции языка, они не являются бесплатными.


Джон.

Ответ 5

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

Ответ 6

Свойства vs. переменные экземпляра являются компромиссом, в конце концов выбор сводится к приложению.

Скрывание инкапсуляции/информации Это хорошая вещь (TM) с точки зрения дизайна, узкие интерфейсы и минимальная связь - это то, что делает программное обеспечение удобным и понятным. В Obj-C довольно сложно скрыть что-либо, но переменные экземпляра, объявленные в реализации, приходят как можно ближе.

Производительность В то время как "преждевременная оптимизация" - это плохая вещь (TM), запись плохого кода только потому, что вы можете, по крайней мере, так же плохо. Его трудно утверждать, что вызов метода более дорогой, чем загрузка или хранилище, а в вычислительном интенсивном коде стоимость вскоре складывается.

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

Абстракция Аргумент против переменных экземпляра в Obj-C традиционно является управлением памятью. С переменными экземпляра MRC требуется, чтобы вызовы сохранения/выпуска/авторекламы распространялись по всему коду, свойства (синтезированные или нет) сохраняли код MRC в одном месте - принцип абстракции, который является хорошей вещью (TM). Однако с GC или ARC этот аргумент уходит, поэтому абстракция для управления памятью больше не является аргументом против переменных экземпляра.

Ответ 7

Свойства выставляют ваши переменные другим классам. Если вам нужна только переменная, относящаяся только к классу, который вы создаете, используйте переменную экземпляра. Вот небольшой пример: классы XML для синтаксического анализа RSS и подобных циклов через кучу делегированных методов и т.д. Практически иметь экземпляр NSMutableString для хранения результата каждого разного анализа. Нет причин, по которым внешний класс должен был бы когда-либо обращаться к этой строке или манипулировать ею. Таким образом, вы просто объявляете его в заголовке или конфиденциально и получаете доступ к нему по всему классу. Установка свойства для него может быть полезной только для того, чтобы убедиться, что нет проблем с памятью, используя self.mutableString для вызова получателей/сеттеров.