Почему вы используете ивар?


153

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

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

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

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

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


Редактировать 1

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

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

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

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

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


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

2
Обратите внимание, что автоматический подсчет ссылок (ARC) применяет те же преимущества управления памятью к ivars, что и свойства, поэтому в коде ARC разница действительно заключается в инкапсуляции.
Бензадо

1
Ваш вопрос и особенно часть Edit 1 на самом деле гораздо более информативны, чем выбранный ответ.
user523234

1
Для Edit1: я думаю, что можно прочитать И ЗАПИСАТЬ каждое свойство, даже когда только одно объявление только для чтения в .h, с Key-Value-Coding, например: [object setValue: [NSNumber numberWithInt: 20] forKey: @ "propertyname «];
Бинар

1
@Sam для вашего редактирования 1: Если вы используете личное свойство и используете расширение / продолжение класса в файле .m, оно не отображается для подклассов. Вам нужно снова написать код или использовать другой .h с расширением класса. Проще с @ protected / default.
Бинар

Ответы:


100

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

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

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

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

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

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

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

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

Корректность программы

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

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

Бинарный размер

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

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

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


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


повторно редактировать 1:

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

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

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

Теперь попробуйте это только с помощью ивара и без KVC.


@ Сэм, спасибо, и хороший вопрос! Ре сложность: это, безусловно, идет в обе стороны. Re инкапсуляция - обновлено
Джастин

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

1
Разве вы не можете получить доступ к приватному ivar с помощью object-> foo? Не так сложно запомнить.
Ник Локвуд

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

1
@NickLockwood, если есть ивар @private, компилятор должен запретить доступ к элементам за пределами методов класса и экземпляра - разве это не то, что вы видите?
Джастин

76

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

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

1
Один из ключевых определителей, которые я вижу в вашем посте, - «... из критических путей кода». Дело в том, что используйте то, что делает код легче для чтения / записи, а затем оптимизируйте то, что вы считаете критическими путями. Это добавит сложности, где это необходимо.
Сэнди Чепмен

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

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

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

9

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

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

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


3
+1 Хороший ответ о суммировании прироста «минимальной производительности» и желании видеть все вызовы методов в явном виде. Использование точечного синтаксиса со свойствами определенно маскирует большую работу, выполняемую в пользовательских методах получения / установки (особенно если этот получатель возвращает копию чего-либо каждый раз, когда его вызывают).
Сэм

1
КВО не работает для меня без использования сеттера. Смена ивара напрямую не вызывает наблюдателя, что значение изменилось!
Бинар

2
KVC может получить доступ к ivars. KVO не может обнаруживать изменения в ivars (и вместо этого полагается на вызывающие методы доступа).
Николай Рюэ

9

Семантика

  • Что @propertyможет выразить то, что не могут сделать ивары: nonatomicи copy.
  • Что могут выразить ивары, которые @propertyне могут:
    • @protected: public на подклассах, private снаружи.
    • @package: общедоступный на фреймворках на 64 бита, приватный снаружи. То же, что и @publicна 32 битах. См. 64-битный контроль доступа к классам и экземплярам Apple .
    • Отборочные. Например, массивы сильных ссылок на объекты: id __strong *_objs.

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

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

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

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

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

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

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

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

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

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


Джон.


6

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

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

Производительность Хотя «преждевременная оптимизация» - это плохая вещь, писать плохо работающий код только потому, что вы можете, по крайней мере, так же плохо. Трудно утверждать, что вызов метода дороже, чем загрузка или хранение, а в коде, интенсивно использующем вычислительные ресурсы, стоимость скоро возрастает.

В статическом языке со свойствами, такими как C #, вызовы к установщикам / получателям часто могут быть оптимизированы компилятором. Однако 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.