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


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

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

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

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

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


изменить 1

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

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

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

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

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

7 143

7 ответов:

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

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

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

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

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

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

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

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

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

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

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

Двоичном Размере

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

Минимизирует Сложности

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


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


re Edit 1:

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

этот обходной путь очевиден:

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

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

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

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

тестовая установка очень проста: создайте 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 file (код полагается на то, что 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

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

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

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

семантика

  • что @property может выразить, что Иварс не может:nonatomic и copy.
  • что ивары могут выразить это @property Не могу:

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

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

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

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

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

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

  4. отправка заставляет все значения в функции быть разлитыми в стек (или хранится в регистрах callee-save, что просто означает разлив в другое время).

  5. отправка сообщение может иметь произвольные побочные эффекты и поэтому

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

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

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

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


Джон.

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

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

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

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

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

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

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