Eu examinei um recurso de linguagem que a maioria dos desenvolvedores de Objective C esquece: declarações compostas. A ideia é simples: instanciar preguiçosamente uma variável de instância sem repetir a variável de instância indefinidamente. Agora eu escrevo um código parecido com este:
- (NSMutableDictionary *) dictionary {
return WSM_LAZY(_dictionary, ({
NSMutableDictionary *initDictionary = @{}.mutableCopy;
initDictionary[@"starterKey"] = @"starterObject";
NSLog(@"Lazy instantiated dictionary: %@", initDictionary);
initDictionary;
}));
}
Como eu cheguei aqui?
No início havia – (instancetype) init
Uma das primeiras coisas que você aprende como um desenvolvedor Objective C é como alocar-init um novo objeto. Existem várias maneiras elegantes de inicializar um objeto. No entanto, duas variações chamaram minha atenção por como pareciam sintaticamente semelhantes, mas como funcionavam de maneira diferente em um nível fundamental.
Exemplo 1: Init em sua forma mais básica
- (instancetype) init { //Start using instancetype ASAP
self = [super init];
if (self) {
// Init instance variables... (actually don’t, we’ll get to that later).
}
return self;
}
Exemplo 2: Funciona, mas simplesmente não parece certo e pode causar problemas.
- (instancetype) init {
if (self = [super init]) { //If you do this often, you’re going to get caught.
}
return self;
}
Exemplo 3: Isso é o que você pretendia fazer.
- (instancetype) init {
if ((self = [super init])) {
// General init stuff here.
}
return self;
}
No Exemplo 3, o parêntese extra na instrução init retorna o valor da expressão interna. Apesar de usar isso em quase todas as instruções de inicialização, essa propriedade de linguagem não é usada com a vantagem máxima na maioria dos códigos Objective C. Isso me fez pensar: até onde podemos levar esse padrão? Iremos revisitar isso em breve, mas primeiro …
Ternário: Isso ou Aquilo
Como mencionei acima: não use a instrução init para instanciar variáveis. Em vez disso, use a instanciação preguiçosa. Vários métodos init podem ser chamados e, se você não tomar cuidado, pode acabar enviando mensagens para uma propriedade nil. A forma mais básica de instanciação lenta (usando literais) tem a seguinte aparência:
Exemplo 1: Instanciação lenta básica.
- (NSMutableDictionary *) dictionary {
if (!_dictionary) {
_dictionary = @{}.mutableCopy;
}
return _dictionary;
}
Se você tem muitas propriedades, pode acabar fazendo muito isso. Uma alternativa é usar o operador ternário de formato longo para simplesmente:
Exemplo 2: instanciação lenta com o operador ternário de forma longa
- (NSMutableDictionary *) dictionary {
_dictionary = _dictionary ? _dictionary : @{}.mutableCopy;
return _dictionary;
}
Isso é legal, mas um pouco repetitivo. Combinando o estilo init do Exemplo 2 e o operador ternário de forma abreviada, poderíamos simplesmente esta expressão para:
Exemplo 2: Legal.
- (NSMutableDictionary *) dictionary {
return (_dictionary = _dictionary ?: @{}.mutableCopy);
}
- Resumo ausente *
A etapa final é abstrair esse padrão com uma macro C. Eu chamo o meu de WSM_LAZY.
#define WSM_LAZY(object, assignment) (object = object ?: assignment)
- (NSMutableDictionary *) dictionary {
return WSM_LAZY(_dictionary, @{}.mutableCopy);
}
Legal, isso é útil ao fazer instanciação lenta padrão, mas e inicializações mais complexas? É aqui que podemos tirar proveito das instruções compostas, que são uma extensão GNU C raramente usada. As instruções compostas parecem blocos, mas implicitamente retornam o último objeto avaliado, ala Ruby. O snippet de código a seguir diz:
Se _dictionary for inicializado, retorne para mim a variável de instância _dictionary.
Caso contrário, use esta instrução composta para criar um valor inicial, defina-o como _dictionary e retorne seu valor.
- (NSMutableDictionary *) dictionary {
return WSM_LAZY(_dictionary, ({
NSMutableDictionary *initDictionary = @{}.mutableCopy;
initDictionary[@"starterKey"] = @"starterObject";
NSLog(@"Lazy instantiated dictionary: %@", initDictionary);
initDictionary;
}));
}