Por que você usaria um ivar?

153

Normalmente, vejo essa pergunta de outra maneira, como Todo ivar deve ser uma propriedade? (e eu gosto da resposta do bbum a este Q).

Eu uso propriedades quase exclusivamente no meu código. De vez em quando, no entanto, trabalho com um empreiteiro que desenvolve o iOS há muito tempo e é um programador de jogos tradicional. Ele escreve um código que declara quase nenhuma propriedade e se apóia em ivars. Suponho que ele faça isso porque 1.) ele está acostumado a isso, pois as propriedades nem sempre existiam até o Objetivo C 2.0 (outubro '07) e 2.) para o ganho de desempenho mínimo de não passar por um getter / setter.

Enquanto ele escreve um código que não vaza, eu ainda prefiro que ele use propriedades sobre ivars. Nós conversamos sobre isso e ele mais ou menos não vê razão para usar propriedades, já que não estávamos usando o KVO e ele tem experiência em cuidar de problemas de memória.

Minha pergunta é mais ... Por que você gostaria de usar um período de ivar - experiente ou não. Existe realmente uma grande diferença de desempenho que justificasse o uso de um ivar?

Também como um ponto de esclarecimento, substituo setters e getters conforme necessário e uso o ivar que se correlaciona com essa propriedade dentro do getter / setter. No entanto, fora de um getter / setter ou init, eu sempre uso a self.myPropertysintaxe.


Editar 1

Agradeço todas as boas respostas. Um que eu gostaria de abordar que parece incorreto é que, com um ivar, você obtém um encapsulamento onde, com uma propriedade que não recebe. Apenas defina a propriedade em uma continuação de classe. Isso ocultará a propriedade de pessoas de fora. Você também pode declarar a propriedade somente leitura na interface e redefini-la como readwrite na implementação, como:

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

e tem na continuação da aula:

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

Tê-lo completamente "privado" apenas o declara na continuação da classe.

Sam
fonte
2
voto positivo para uma pergunta interessante - bem colocada e também uma que eu gostaria de ouvir o caso de ivars, pois parece que me ensinaram a fazê-lo da maneira de Sam.
Damo
2
Observe que a contagem automática de referência (ARC) aplica os mesmos benefícios de gerenciamento de memória aos ivars que as propriedades; portanto, no código ARC, a diferença é realmente sobre encapsulamento.
benzado 31/01
1
Sua pergunta e especialmente a parte Edit 1 são muito mais informativas que a resposta escolhida.
User523234 6/12
1
Para Edit1: Eu acho que é possível ler AND WRITE todas as propriedades, mesmo quando apenas uma declaração de readonly em .h, com Key-Value-Coding, por exemplo: [object setValue: [NSNumber numberWithInt: 20] forKey: @ "propertyname "];
Binarian 26/08/13
1
@ Sam no seu Edit 1: Se você usa uma propriedade privada e usa a extensão / continuação de classe no arquivo .m, não é visível para subclasses. Você precisa escrever o código novamente ou usar outro .h com a extensão de classe. Mais fácil com @ protegido / padrão.
Binarian

Respostas:

100

Encapsulamento

Se o ivar é privado, as outras partes do programa não conseguem chegar com tanta facilidade. Com uma propriedade declarada, as pessoas inteligentes podem acessar e alterar facilmente através dos acessadores.

atuação

Sim, isso pode fazer a diferença em alguns casos. Alguns programas têm restrições nas quais eles não podem usar nenhuma mensagem objc em determinadas partes do programa (pense em tempo real). Em outros casos, convém acessá-lo diretamente para obter velocidade. Em outros casos, é porque as mensagens objc atuam como um firewall de otimização. Por fim, pode reduzir as operações de contagem de referência e minimizar o pico de uso da memória (se feito corretamente).

Tipos não triviais

Exemplo: se você possui um tipo C ++, o acesso direto é a melhor abordagem algumas vezes. O tipo pode não ser copiável ou pode não ser trivial copiar.

Multithreading

Muitos de seus ivars são co-dependentes. Você deve garantir a integridade dos dados no contexto multithread. Portanto, você pode favorecer o acesso direto a vários membros em seções críticas. Se você ficar com os acessadores para obter dados dependentes de código, seus bloqueios normalmente deverão ser reentrantes e, com frequência, você acabará fazendo muito mais aquisições (significativamente mais às vezes).

Correção do programa

Como as subclasses podem substituir qualquer método, você pode eventualmente perceber que há uma diferença semântica entre gravar na interface e gerenciar seu estado adequadamente. O acesso direto à correção do programa é especialmente comum em estados parcialmente construídos - nos inicializadores e no dealloc, é melhor usar o acesso direto. Você também pode encontrar este comuns nas implementações de um assessor, um construtor de conveniência, copy, mutableCopyimplementações, e arquivamento / serialização.

Também é mais frequente à medida que se muda da mentalidade do acessador de readwrite para tudo, para uma que oculta bem os dados / detalhes da implementação. Às vezes, você precisa contornar corretamente os efeitos colaterais que a substituição de uma subclasse pode introduzir para fazer a coisa certa.

Tamanho binário

Declarar tudo como readwrite por padrão geralmente resulta em muitos métodos de acessador dos quais você nunca precisa, quando considera a execução do seu programa por um momento. Por isso, adicionará um pouco de gordura ao seu programa e também aumenta o tempo de carregamento.

Minimiza a complexidade

Em alguns casos, é completamente desnecessário adicionar + type + manter todo esse andaime extra para uma variável simples, como um bool privado que é escrito em um método e lido em outro.


Isso não significa que o uso de propriedades ou acessores seja ruim - cada um possui importantes benefícios e restrições. Como muitas linguagens OO e abordagens de design, você também deve favorecer os acessadores com visibilidade adequada no ObjC. Haverá momentos em que você precisará se desviar. Por esse motivo, acho que geralmente é melhor restringir acessos diretos à implementação que declara o ivar (por exemplo, declare-o @private).


re Editar 1:

Muitos de nós memorizamos como chamar um acessador oculto dinamicamente (desde que conheçamos o nome…). Enquanto isso, a maioria de nós não memorizou como acessar corretamente os ivars que não são visíveis (além do KVC). A continuação da classe ajuda , mas introduz vulnerabilidades.

Esta solução alternativa é óbvia:

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

Agora tente com apenas um ivar e sem KVC.

justin
fonte
@ Sam obrigado, e boa pergunta! re complexidade: certamente vai nos dois sentidos. re encapsulamento - atualizado
justin
@bbum RE: Exemplo ilusório Embora eu concorde com você que é a solução errada, não consigo imaginar que muitos desenvolvedores de objetos experientes acreditem que isso simplesmente não aconteça; Eu já vi isso nos programas de outras pessoas e as App Stores chegaram ao ponto de proibir o uso de APIs privadas da Apple.
justin
1
Você não pode acessar um ivar privado com object-> foo? Não é tão difícil de lembrar.
Nick Lockwood
1
Eu quis dizer que você pode acessá-lo usando uma deferência de ponteiro do objeto usando a sintaxe C ->. As classes Objective-C são basicamente apenas estruturas sob o capô e, dado um ponteiro para uma estrutura, a sintaxe C para acessar membros é ->, que também funciona para ivars nas classes C objetivas.
Nick Lockwood
1
@NickLockwood, se o ivar for @private, o compilador deve proibir o acesso de membros fora dos métodos de classe e instância - não é isso que você vê?
justin
76

Para mim, geralmente é desempenho. Acessar um ivar de um objeto é tão rápido quanto acessar um membro struct em C usando um ponteiro para a memória que contém essa estrutura. De fato, os objetos Objective-C são basicamente estruturas C localizadas na memória alocada dinamicamente. Isso geralmente é o mais rápido que seu código pode obter, nem mesmo o código de montagem otimizado manualmente pode ser mais rápido que isso.

Acessar um ivar através de um getter / configuração envolve uma chamada de método Objective-C, que é muito mais lenta (pelo menos 3-4 vezes) que uma chamada de função C "normal" e até mesmo uma chamada de função C normal já seria várias vezes mais lenta que acessando um membro struct. Dependendo dos atributos de sua propriedade, a implementação do setter / getter gerada pelo compilador pode envolver outra chamada de função C para as funções objc_getProperty/ objc_setProperty, pois elas terão que retain/ copy/ autoreleaseos objetos conforme necessário e, posteriormente, realizarão spinlocking para propriedades atômicas. Isso pode facilmente ficar muito caro e não estou falando de ser 50% mais lento.

Vamos tentar isso:

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

Resultado:

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

Isso é 4,28 vezes mais lento e esse foi um int primitivo não atômico, praticamente o melhor caso ; a maioria dos outros casos é ainda pior (tente um teste atômico).NSString * propriedade !). Portanto, se você pode conviver com o fato de que cada acesso ao ivar é 4-5 vezes mais lento do que poderia ser, o uso de propriedades é bom (pelo menos no que diz respeito ao desempenho), no entanto, existem muitas situações em que essa queda de desempenho é completamente inaceitável.

Atualização 20/10/2015

Algumas pessoas argumentam que esse não é um problema do mundo real, o código acima é puramente sintético e você nunca notará isso em um aplicativo real. Ok, então, vamos tentar uma amostra do mundo real.

O código a seguir define Accountobjetos. Uma conta possui propriedades que descrevem o nome ( NSString *), sexo ( enum) e idade ( unsigned) de seu proprietário, além de um saldo ( int64_t). Um objeto de conta tem um initmétodo e um compare:método. O compare:método é definido como: pedidos femininos antes do masculino, nomes ordenados alfabeticamente, pedidos jovens antes do antigo, saldo de pedidos baixo para alto.

Na verdade, existem duas classes de conta, AccountAe AccountB. Se você observar a implementação deles, notará que eles são quase totalmente idênticos, com uma exceção: o compare:método. AccountAos objetos acessam suas próprias propriedades pelo método (getter), enquanto os AccountBobjetos acessam suas próprias propriedades pelo ivar. Essa é realmente a única diferença! Ambos acessam as propriedades do outro objeto para comparar com o getter (acessá-lo pelo ivar não seria seguro! E se o outro objeto for uma subclasse e tiver substituído o getter?). Observe também que acessar suas próprias propriedades como ivars não quebra o encapsulamento (os ivars ainda não são públicos).

A configuração do teste é realmente simples: crie contas aleatórias de 1 milhão, adicione-as a uma matriz e classifique-a. É isso aí. Obviamente, existem duas matrizes, uma para AccountAobjetos e outra para AccountBobjetos, e ambas são preenchidas com contas idênticas (mesma fonte de dados). Cronometramos quanto tempo leva para classificar as matrizes.

Aqui está a saída de várias execuções que fiz ontem:

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

Como você pode ver, classificar a matriz de AccountBobjetos é sempre significativamente mais rápido do que classificar a matriz de AccountAobjetos.

Quem quer que afirme que diferenças de tempo de execução de até 1,32 segundos não fazem diferença, nunca deve fazer a programação da interface do usuário. Se eu quiser alterar a ordem de classificação de uma tabela grande, por exemplo, diferenças de horário como essas fazem uma enorme diferença para o usuário (a diferença entre uma interface do usuário aceitável e uma interface lenta).

Também nesse caso, o código de exemplo é o único trabalho real realizado aqui, mas com que frequência seu código é apenas uma pequena engrenagem de um relógio complicado? E se todo equipamento atrasar todo o processo como este, o que isso significa para a velocidade de todo o relógio no final? Especialmente se uma etapa do trabalho depende da saída de outra, o que significa que todas as ineficiências serão somadas. A maioria das ineficiências não é um problema por si só, é a soma total que se torna um problema para todo o processo. E esse problema não é nada que um criador de perfil mostre facilmente, porque ele trata de encontrar pontos críticos críticos, mas nenhuma dessas ineficiências são pontos críticos por si só. O tempo da CPU é distribuído apenas entre eles, mas cada um deles possui apenas uma fração tão pequena que parece uma perda total de tempo para otimizá-lo. E é verdade,

E mesmo que você não pense em termos de tempo de CPU, porque acredita que desperdiçar tempo de CPU é totalmente aceitável, afinal "é de graça", e quanto aos custos de hospedagem de servidores causados ​​pelo consumo de energia? E quanto ao tempo de execução da bateria de dispositivos móveis? Se você escrever o mesmo aplicativo móvel duas vezes (por exemplo, um navegador da web móvel próprio), uma vez em que todas as classes acessem suas próprias propriedades apenas por getters e uma vez em que todas as classes os acessem apenas por ivars, usar o primeiro constantemente irá drenar definitivamente a bateria é muito mais rápida do que usar a segunda, mesmo que seja equivalente funcional e, para o usuário, a segunda provavelmente se sentiria um pouco mais rápida.

Agora, aqui está o código do seu main.marquivo (o código depende da ativação do ARC e use a otimização ao compilar para ver o efeito completo):

#import <Foundation/Foundation.h>

typedef NS_ENUM(int, Gender) {
    GenderMale,
    GenderFemale
};


@interface AccountA : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountA *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


@interface AccountB : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountB *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


static
NSMutableArray * allAcocuntsA;

static
NSMutableArray * allAccountsB;

static
int64_t getRandom ( const uint64_t min, const uint64_t max ) {
    assert(min <= max);
    uint64_t rnd = arc4random(); // arc4random() returns a 32 bit value only
    rnd = (rnd << 32) | arc4random();
    rnd = rnd % ((max + 1) - min); // Trim it to range
    return (rnd + min); // Lift it up to min value
}

static
void createAccounts ( const NSUInteger ammount ) {
    NSArray *const maleNames = @[
        @"Noah", @"Liam", @"Mason", @"Jacob", @"William",
        @"Ethan", @"Michael", @"Alexander", @"James", @"Daniel"
    ];
    NSArray *const femaleNames = @[
        @"Emma", @"Olivia", @"Sophia", @"Isabella", @"Ava",
        @"Mia", @"Emily", @"Abigail", @"Madison", @"Charlotte"
    ];
    const NSUInteger nameCount = maleNames.count;
    assert(maleNames.count == femaleNames.count); // Better be safe than sorry

    allAcocuntsA = [NSMutableArray arrayWithCapacity:ammount];
    allAccountsB = [NSMutableArray arrayWithCapacity:ammount];

    for (uint64_t i = 0; i < ammount; i++) {
        const Gender g = (getRandom(0, 1) == 0 ? GenderMale : GenderFemale);
        const unsigned age = (unsigned)getRandom(18, 120);
        const int64_t balance = (int64_t)getRandom(0, 200000000) - 100000000;

        NSArray *const nameArray = (g == GenderMale ? maleNames : femaleNames);
        const NSUInteger nameIndex = (NSUInteger)getRandom(0, nameCount - 1);
        NSString *const name = nameArray[nameIndex];

        AccountA *const accountA = [[AccountA alloc]
            initWithName:name age:age gender:g balance:balance
        ];
        AccountB *const accountB = [[AccountB alloc]
            initWithName:name age:age gender:g balance:balance
        ];

        [allAcocuntsA addObject:accountA];
        [allAccountsB addObject:accountB];
    }
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        @autoreleasepool {
            NSUInteger ammount = 1000000; // 1 Million;
            if (argc > 1) {
                unsigned long long temp = 0;
                if (1 == sscanf(argv[1], "%llu", &temp)) {
                    // NSUIntegerMax may just be UINT32_MAX!
                    ammount = (NSUInteger)MIN(temp, NSUIntegerMax);
                }
            }
            createAccounts(ammount);
        }

        // Sort A and take time
        const CFAbsoluteTime startTime1 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAcocuntsA sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime1 = CFAbsoluteTimeGetCurrent() - startTime1;

        // Sort B and take time
        const CFAbsoluteTime startTime2 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAccountsB sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime2 = CFAbsoluteTimeGetCurrent() - startTime2;

        NSLog(@"runTime 1: %f", runTime1);
        NSLog(@"runTime 2: %f", runTime2);
    }
    return 0;
}



@implementation AccountA
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (self.gender != account.gender) {
            if (self.gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![self.name isEqualToString:account.name]) {
            return [self.name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (self.age != account.age) {
            if (self.age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (self.balance != account.balance) {
            if (self.balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end


@implementation AccountB
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (_gender != account.gender) {
            if (_gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![_name isEqualToString:account.name]) {
            return [_name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (_age != account.age) {
            if (_age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (_balance != account.balance) {
            if (_balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end
Mecki
fonte
3
Explicação extremamente informativa e prática. Upvote para a amostra de código
Philip007
1
Um dos principais qualificadores que vejo na sua postagem é "... dos caminhos críticos do código". O ponto é que use o que facilita a leitura / gravação do código e, em seguida, otimize o que você considera os caminhos críticos. Isso adicionará a complexidade onde for necessário.
Sandy Chapman
1
@ViktorLexington No meu código, eu estava definindo um unsigned intque nunca é retido / liberado, se você usa o ARC ou não. A retenção / liberação em si é cara, portanto a diferença será menor, pois o gerenciamento de retenção adiciona uma sobrecarga estática que sempre existe, usando o setter / getter ou ivar diretamente; no entanto, você ainda salvará a sobrecarga de uma chamada de método extra se acessar o ivar diretamente. Na maioria dos casos, não é grande coisa, a menos que você esteja fazendo isso milhares de vezes por segundo. A Apple diz que usa getters / setters por padrão, a menos que você esteja em um método init / dealloc ou tenha um gargalo.
Mecki
1
@Fogmeister Adicionado um exemplo de código que mostra com que facilidade isso pode fazer uma enorme diferença em um exemplo muito simples do mundo real. E este exemplo não tem nada a ver com um super computador fazendo trilhões de cálculos, é mais sobre a classificação de uma tabela de dados realmente simples (um caso bastante comum entre milhões de aplicativos).
Mecki
2
@malhal A propriedade marcada como copyvai NÃO fazer uma cópia de seu valor cada vez que você acessá-lo. O getter de copypropriedade é como o getter de um strong/ retainproperty. Seu código é basicamente return [[self->value retain] autorelease];. Somente o setter copia o valor e ele ficará mais ou menos assim [self->value autorelease]; self->value = [newValue copy];, enquanto um strong/ retainsetter fica assim:[self->value autorelease]; self->value = [newValue retain];
Mecki 5/16
9

O motivo mais importante é o conceito OOP de ocultação de informações : se você expõe tudo por meio de propriedades e permite que objetos externos espiem as partes internas de outro objeto, você as utilizará internas e, portanto, complicará a alteração da implementação.

O ganho de "desempenho mínimo" pode resumir rapidamente e se tornar um problema. Eu sei por experiência; Trabalho em um aplicativo que realmente leva os iDevices a seus limites e, portanto, precisamos evitar chamadas de métodos desnecessárias (é claro, apenas quando razoavelmente possível). Para ajudar nesse objetivo, também estamos evitando a sintaxe do ponto, pois dificulta a visualização do número de chamadas de método à primeira vista: por exemplo, quantas chamadas de método a expressão self.image.size.widthdispara? Por outro lado, você pode contar imediatamente com [[self image] size].width.

Além disso, com a nomeação correta de ivar, o KVO é possível sem propriedades (IIRC, eu não sou especialista em KVO).

DarkDust
fonte
3
+1 Boa resposta sobre o "desempenho mínimo", aumentando e desejando ver todas as chamadas de método explicitamente. O uso da sintaxe de pontos com propriedades definitivamente mascara muito trabalho que ocorre em getters / setters personalizados (especialmente se esse getter retornar uma cópia de algo toda vez que for chamado).
Sam
1
O KVO não funciona para mim sem usar um setter. Mudar o ivar diretamente não chama o observador de que o valor mudou!
Binarian
2
KVC pode acessar ivars. O KVO não pode detectar alterações nos ivars (e depende dos acessadores a serem chamados).
Nikolai Ruhe
9

Semântica

  • O que @propertypode expressar que os ivars não podem: nonatomice copy.
  • O que os ivars podem expressar que @propertynão podem:
    • @protected: público em subclasses, privado fora.
    • @package: público em estruturas em 64 bits, privado fora. O mesmo que @publicem 32 bits. Consulte Controle de acesso variável de classe e instância de 64 bits da Apple .
    • Qualificadores. Por exemplo, matrizes de fortes referências de objeto: id __strong *_objs.

atuação

Resumindo: os ivars são mais rápidos, mas isso não importa para a maioria dos usos. nonatomicAs propriedades não usam bloqueios, mas o ivar direto é mais rápido porque ignora a chamada dos acessadores. Para detalhes, leia o seguinte email em 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

As propriedades afetam o desempenho de várias maneiras:

  1. Como já discutido, o envio de uma mensagem para carregar / armazenar é mais lento do que fazer o carregamento / armazenar em linha .

  2. Enviar uma mensagem para fazer um carregamento / armazenamento também é um pouco mais de código que precisa ser mantido no i-cache: mesmo que o getter / setter tenha adicionado zero instruções adicionais além do carregamento / armazenamento, haveria uma metade sólida -dúzia de instruções extras no chamador para configurar a mensagem, enviar e manipular o resultado.

  3. O envio de uma mensagem força uma entrada para esse seletor a ser mantida no cache do método , e essa memória geralmente permanece no d-cache. Isso aumenta o tempo de inicialização, aumenta o uso de memória estática do seu aplicativo e torna as alternâncias de contexto mais dolorosas. Como o cache do método é específico da classe dinâmica de um objeto, esse problema aumenta à medida que você usa o KVO nele.

  4. O envio de uma mensagem força todos os valores da função a serem derramados na pilha (ou mantidos em registros de salvamento de chamadas, o que significa apenas derramamento em um horário diferente).

  5. O envio de uma mensagem pode ter efeitos colaterais arbitrários e, portanto,

    • força o compilador a redefinir todas as suas suposições sobre memória não local
    • não pode ser içado, afundado, reordenado, coalescido ou eliminado.

  6. No ARC, o resultado de um envio de mensagem sempre será retido , pelo destinatário ou pelo chamador, mesmo para +0 retornos: mesmo que o método não retenha / libere automaticamente o resultado, o chamador não sabe disso e para tentar executar uma ação para impedir que o resultado seja liberado automaticamente. Isso nunca pode ser eliminado porque os envios de mensagens não são estaticamente analisáveis.

  7. No ARC, como um método setter geralmente leva seu argumento em +0, não há como "transferir" uma retenção desse objeto (que, como discutido acima, o ARC geralmente possui) no ivar, portanto o valor geralmente precisa ser obtido. reter / liberar duas vezes .

Nada disso significa que eles sempre são ruins, é claro - existem muitas boas razões para usar propriedades. Lembre-se de que, como muitos outros recursos de idioma, eles não são gratuitos.


John.

Jano
fonte
6

Propriedades versus variáveis ​​de instância é uma troca, no final, a escolha se resume ao aplicativo.

Encapsulamento / ocultação de informações Isso é uma coisa boa (TM) do ponto de vista do design, interfaces estreitas e ligação mínima é o que torna o software sustentável e compreensível. É muito difícil no Obj-C ocultar qualquer coisa, mas as variáveis ​​de instância declaradas na implementação chegam o mais perto possível.

Desempenho Embora a "otimização prematura" seja uma coisa ruim (TM), escrever código com desempenho ruim apenas porque você pode ser pelo menos tão ruim. É difícil argumentar que uma chamada de método é mais cara que uma carga ou loja e, em código intensivo de computação, o custo logo aumenta.

Em uma linguagem estática com propriedades, como C #, as chamadas para setters / getters geralmente podem ser otimizadas pelo compilador. No entanto, o Obj-C é dinâmico e a remoção dessas chamadas é muito mais difícil.

Abstração Um argumento contra variáveis ​​de instância no Obj-C é tradicionalmente o gerenciamento de memória. Com a instância do MRC, as variáveis ​​exigem que as chamadas de retenção / liberação / liberação automática sejam espalhadas por todo o código, as propriedades (sintetizadas ou não) mantêm o código MRC em um único local - o princípio da abstração que é uma Good Thing (TM). No entanto, com GC ou ARC esse argumento desaparece, portanto a abstração para gerenciamento de memória não é mais um argumento contra variáveis ​​de instância.

CRD
fonte
5

Propriedades expõem suas variáveis ​​a outras classes. Se você só precisa de uma variável que seja apenas relativa à classe que está criando, use uma variável de instância. Aqui está um pequeno exemplo: as classes XML para analisar RSS e similares passam por vários métodos delegados e coisas do tipo. É prático ter uma instância do NSMutableString para armazenar o resultado de cada passo diferente da análise. Não há razão para que uma classe externa precise acessar ou manipular essa string. Então, você apenas a declara no cabeçalho ou em particular e a acessa por toda a classe. Definir uma propriedade para ela pode ser útil apenas para garantir que não haja problemas de memória, usando self.mutableString para chamar o getter / setters.

Justin
fonte
5

A compatibilidade com versões anteriores foi um fator para mim. Não pude usar nenhum recurso do Objective-C 2.0 porque estava desenvolvendo softwares e drivers de impressora que precisavam funcionar no Mac OS X 10.3 como parte de um requisito. Sei que sua pergunta parecia direcionada ao iOS, mas pensei em compartilhar meus motivos para não usar propriedades.

dreamlax
fonte