Melhor maneira de definir métodos privados para uma classe no Objective-C

355

Comecei a programar o Objective-C e, tendo experiência em Java, me pergunto como as pessoas que escrevem programas Objective-C lidam com métodos privados.

Entendo que pode haver várias convenções e hábitos e penso sobre essa questão como um agregador das melhores técnicas que as pessoas usam para lidar com métodos privados no Objective-C.

Inclua um argumento para sua abordagem ao publicá-la. Por que isso é bom? Quais são as desvantagens (que você conhece) e como você lida com elas?


Quanto às minhas descobertas até agora.

É possível usar categorias [por exemplo, MyClass (Private)] definidas no arquivo MyClass.m para agrupar métodos privados.

Essa abordagem tem 2 problemas:

  1. Xcode (e compilador?) Não verifica se você define todos os métodos na categoria privada no bloco @implementation correspondente
  2. Você deve colocar @interface declarando sua categoria privada no início do arquivo MyClass.m, caso contrário, o Xcode se queixará de uma mensagem como "o self pode não responder à mensagem" privateFoo ".

A primeira edição pode ser contornada com a categoria vazia [por exemplo, MyClass ()].
O segundo me incomoda muito. Eu gostaria de ver métodos privados implementados (e definidos) perto do final do arquivo; Não sei se isso é possível.

Yurii Soldak
fonte
11
O pessoal pode achar essa pergunta interessante: stackoverflow.com/questions/2158660/…
bbum

Respostas:

435

Como já foi dito, não existe um método privado no Objective-C. No entanto, a partir do Objective-C 2.0 (ou seja, Mac OS X Leopard, iPhone OS 2.0 e posterior), você pode criar uma categoria com um nome vazio (por exemplo @interface MyClass ()) chamado Extensão de Classe . O que é único em uma extensão de classe é que as implementações de métodos devem ser iguais @implementation MyClassàs dos métodos públicos. Então eu estruturo minhas classes assim:

No arquivo .h:

@interface MyClass {
    // My Instance Variables
}

- (void)myPublicMethod;

@end

E no arquivo .m:

@interface MyClass()

- (void)myPrivateMethod;

@end

@implementation MyClass

- (void)myPublicMethod {
    // Implementation goes here
}

- (void)myPrivateMethod {
    // Implementation goes here
}

@end

Eu acho que a maior vantagem dessa abordagem é que ela permite agrupar suas implementações de método por funcionalidade, não pela distinção público / privada (às vezes arbitrária).

Alex
fonte
8
e gerará uma "MYClass pode não responder a '-myPrivateMethod-", não uma exceção / erro.
Özgür
2
Na verdade, isso está começando a aparecer no código padrão da Apple. ++
Chris Trahey
75
com o compilador LLVM 4 em diante, você nem precisa fazer isso. você pode apenas defini-los em sua implementação sem precisar colocá-los em uma extensão de classe.
Abizern 23/07/2012
11
Se você receber os avisos que o @Comptrol menciona, é porque você definiu um método abaixo e não outro método que o chama (consulte a resposta de Andy) - e você ignora esses avisos por sua conta e risco. if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])...Cometi esse erro e o compilador ficou confuso até que aninhei uma chamada como esta: Então o fWidthCombined estava sempre chegando como 0. #
Wienke
6
@ Wienke Não é mais necessário se preocupar com o pedido. Versões recentes do LLVM encontrarão o método, mesmo que apareça abaixo de onde é chamado.
Steve Waddicor
52

Não existe realmente um "método privado" no Objective-C, se o tempo de execução puder determinar qual implementação usar, ele fará. Mas isso não quer dizer que não haja métodos que não façam parte da interface documentada. Para esses métodos, acho que uma categoria é boa. Em vez de colocar o @interfacearquivo no topo do arquivo .m como seu ponto 2, eu o colocaria em seu próprio arquivo .h. Uma convenção que sigo (e já vi em outros lugares, acho que é uma convenção da Apple, já que o Xcode agora oferece suporte automático a ela) é nomear esse arquivo após sua classe e categoria com um + separando-os, para que @interface GLObject (PrivateMethods)possa ser encontrado em GLObject+PrivateMethods.h. O motivo para fornecer o arquivo de cabeçalho é para que você possa importá-lo em suas classes de teste de unidade :-).

A propósito, no que diz respeito à implementação / definição de métodos perto do final do arquivo .m, você pode fazer isso com uma categoria implementando a categoria na parte inferior do arquivo .m:

@implementation GLObject(PrivateMethods)
- (void)secretFeature;
@end

ou com uma extensão de classe (o que você chama de "categoria vazia"), basta definir esses métodos por último. Os métodos Objective-C podem ser definidos e usados ​​em qualquer ordem na implementação; portanto, não há nada para impedir que você coloque os métodos "privados" no final do arquivo.

Mesmo com extensões de classe, muitas vezes crio um cabeçalho separado ( GLObject+Extension.h) para que eu possa usar esses métodos, se necessário, imitando a visibilidade "amigo" ou "protegido".

Desde que esta resposta foi originalmente escrita, o compilador clang começou a fazer duas passagens pelos métodos Objective-C. Isso significa que você pode evitar declarar completamente seus métodos "particulares" e, se eles estão acima ou abaixo do site de chamada, eles serão encontrados pelo compilador.


fonte
37

Embora eu não seja especialista em Objective-C, eu apenas defino o método na implementação da minha classe. Concedido, ele deve ser definido antes (acima) de quaisquer métodos que o chamem, mas definitivamente exige a menor quantidade de trabalho a ser feito.

Andy
fonte
4
Esta solução tem a vantagem de evitar adicionar estrutura de programa supérflua apenas para evitar um aviso do compilador.
Jan Hettich
11
Eu costumo fazer isso também, mas também não sou especialista em Objective-C. Para os especialistas, existe alguma razão para não fazer dessa maneira (além do problema de pedido de método)?
Eric Smith
2
A ordenação de métodos parece ser um problema menor, mas se você o traduzir em legibilidade de código , poderá se tornar um problema muito importante, especialmente quando estiver trabalhando em equipe.
precisa saber é o seguinte
17
A ordenação de métodos não é mais significativa. As versões recentes do LLVM não se importam com a ordem em que os métodos são implementados. Assim, você pode se adequar ao pedido, sem precisar declarar primeiro.
Steve Waddicor
Veja também esta resposta de @justin
lagweezle
19

Definir seus métodos particulares no @implementationbloco é ideal para a maioria dos propósitos. Clang verá isso dentro da ordem @implementation, independentemente da declaração. Não há necessidade de declará-los em uma continuação de classe (também conhecida como extensão de classe) ou categoria nomeada.

Em alguns casos, você precisará declarar o método na continuação da classe (por exemplo, se estiver usando o seletor entre a continuação da classe e a @implementation).

static funções são muito boas para métodos particulares particularmente sensíveis ou críticos para a velocidade.

Uma convenção para nomear prefixos pode ajudar a evitar a substituição acidental de métodos privados (acho o nome da classe um prefixo seguro).

As categorias nomeadas (por exemplo @interface MONObject (PrivateStuff)) não são uma ideia particularmente boa devido a possíveis colisões de nomes durante o carregamento. Eles são realmente úteis apenas para métodos protegidos ou amigos (que raramente são uma boa escolha). Para garantir que você seja avisado sobre implementações de categorias incompletas, você deve realmente implementá-lo:

@implementation MONObject (PrivateStuff)
...HERE...
@end

Aqui está uma pequena folha de dicas anotadas:

MONObject.h

@interface MONObject : NSObject

// public declaration required for clients' visibility/use.
@property (nonatomic, assign, readwrite) bool publicBool;

// public declaration required for clients' visibility/use.
- (void)publicMethod;

@end

MONObject.m

@interface MONObject ()
@property (nonatomic, assign, readwrite) bool privateBool;

// you can use a convention where the class name prefix is reserved
// for private methods this can reduce accidental overriding:
- (void)MONObject_privateMethod;

@end

// The potentially good thing about functions is that they are truly
// inaccessible; They may not be overridden, accidentally used,
// looked up via the objc runtime, and will often be eliminated from
// backtraces. Unlike methods, they can also be inlined. If unused
// (e.g. diagnostic omitted in release) or every use is inlined,
// they may be removed from the binary:
static void PrivateMethod(MONObject * pObject) {
    pObject.privateBool = true;
}

@implementation MONObject
{
    bool anIvar;
}

static void AnotherPrivateMethod(MONObject * pObject) {
    if (0 == pObject) {
        assert(0 && "invalid parameter");
        return;
    }

    // if declared in the @implementation scope, you *could* access the
    // private ivars directly (although you should rarely do this):
    pObject->anIvar = true;
}

- (void)publicMethod
{
    // declared below -- but clang can see its declaration in this
    // translation:
    [self privateMethod];
}

// no declaration required.
- (void)privateMethod
{
}

- (void)MONObject_privateMethod
{
}

@end

Outra abordagem que pode não ser óbvia: um tipo C ++ pode ser muito rápido e fornecer um grau de controle muito mais alto, minimizando o número de métodos objc exportados e carregados.

justin
fonte
11
+1 para usar o nome completo da classe como o prefixo do nome do método! É muito mais seguro do que apenas um sublinhado ou mesmo seu próprio TLA. (E se o método privado estiver em uma biblioteca que você usa em outro de seus projetos e você esquecer que já usou o nome, há um ou dois anos ...?)
big_m
14

Você pode tentar definir uma função estática abaixo ou acima da sua implementação que leva um ponteiro para sua instância. Ele poderá acessar qualquer uma das variáveis ​​da sua instância.

//.h file
@interface MyClass : Object
{
    int test;
}
- (void) someMethod: anArg;

@end


//.m file    
@implementation MyClass

static void somePrivateMethod (MyClass *myClass, id anArg)
{
    fprintf (stderr, "MyClass (%d) was passed %p", myClass->test, anArg);
}


- (void) someMethod: (id) anArg
{
    somePrivateMethod (self, anArg);
}

@end
dreamlax
fonte
11
A Apple reservou nomes com um sublinhado líder para seus próprios usos.
Georg Schölly 16/03/09
11
E se você não usar as estruturas da Apple? Eu frequentemente desenvolvo código Objective-C sem as estruturas da Apple, na verdade, desenvolvo no Linux, Windows e Mac OS X. Eu o removi de qualquer maneira, considerando que a maioria das pessoas que codificam no Objective-C provavelmente o usam no Mac OS X.
dreamlax
3
Eu acho que este é um método verdadeiramente privado no arquivo .m. Na verdade, outros métodos de categoria de classe não são privados porque você simplesmente não pode colocar privado para métodos no bloco @interface ... @ end.
David.Chu.ca
Por que você faria isso? se você simplesmente adicionar "-" no início da definição do método, poderá acessar "self" sem passar como parâmetro.
Guy
11
@ Guy: porque então o método é detectável por reflexão e, portanto, não é privado.
dreamlax
3

Você poderia usar blocos?

@implementation MyClass

id (^createTheObject)() = ^(){ return [[NSObject alloc] init];};

NSInteger (^addEm)(NSInteger, NSInteger) =
^(NSInteger a, NSInteger b)
{
    return a + b;
};

//public methods, etc.

- (NSObject) thePublicOne
{
    return createTheObject();
}

@end

Estou ciente de que esta é uma pergunta antiga, mas é uma das primeiras que encontrei quando procurava uma resposta para essa pergunta. Eu não vi essa solução discutida em nenhum outro lugar; então, deixe-me saber se há algo tolo em fazer isso.

FellowMD
fonte
11
O que você fez aqui é criar uma variável global do tipo bloco, que não é realmente melhor do que uma função (e nem mesmo verdadeiramente privada, pois não é declarada static). Mas eu tenho experimentado atribuir blocos a ivars privados (a partir do método init) - tipo JavaScript - que também permite acesso a ivars privados, algo que não é possível a partir de funções estáticas. Ainda não tenho certeza do que eu prefiro.
big_m
3

todos os objetos no Objetivo C estão em conformidade com o protocolo NSObject, que se mantém no método performSelector:. Eu também procurava anteriormente uma maneira de criar alguns métodos "auxiliares ou particulares" que não precisavam ser expostos em nível público. Se você deseja criar um método privado sem sobrecarga e sem precisar defini-lo no arquivo de cabeçalho, experimente ...

defina o seu método com uma assinatura semelhante ao código abaixo ...

-(void)myHelperMethod: (id) sender{
     // code here...
}

quando precisar fazer referência ao método, basta chamá-lo como seletor ...

[self performSelector:@selector(myHelperMethod:)];

essa linha de código chamará o método que você criou e não terá um aviso irritante sobre não tê-lo definido no arquivo de cabeçalho.

Zack Sheppard
fonte
6
Dessa forma, você não tem como passar um terceiro parâmetro.
quer
2

Se você quiser evitar o @interfacebloco na parte superior, sempre poderá colocar as declarações privadas em outro arquivo, MyClassPrivate.hnão o ideal, mas não sobrecarregando a implementação.

MyClass.h

interface MyClass : NSObject {
 @private
  BOOL publicIvar_;
  BOOL privateIvar_;
}

@property (nonatomic, assign) BOOL publicIvar;
//any other public methods. etc
@end

MyClassPrivate.h

@interface MyClass ()

@property (nonatomic, assign) BOOL privateIvar;
//any other private methods etc.
@end

MyClass.m

#import "MyClass.h"
#import "MyClassPrivate.h"
@implementation MyClass

@synthesize privateIvar = privateIvar_;
@synthesize publicIvar = publicIvar_;

@end
rebelzach
fonte
2

Mais uma coisa que não vi mencionada aqui - o Xcode suporta arquivos .h com "_private" no nome. Digamos que você tenha uma classe MyClass - você tem MyClass.m e MyClass.he agora você também pode ter MyClass_private.h. O Xcode reconhecerá isso e o incluirá na lista de "homólogos" no Editor Assistente.

//MyClass.m
#import "MyClass.h"
#import "MyClass_private.h"
Rich Schonthal
fonte
1

Não há como contornar o problema nº 2. É assim que o compilador C (e, portanto, o compilador Objective-C) funciona. Se você usar o editor XCode, o pop-up da função deve facilitar a navegação nos blocos @interfacee @implementationno arquivo.

Barry Wark
fonte
1

Há um benefício da ausência de métodos privados. Você pode mover a lógica que pretendia ocultar para a classe separada e usá-la como delegada. Nesse caso, você pode marcar o objeto delegado como privado e não será visível de fora. Mover a lógica para a classe separada (talvez várias) cria um melhor design do seu projeto. Porque suas classes se tornam mais simples e seus métodos são agrupados em classes com nomes próprios.

Sneg
fonte
0

Como outras pessoas disseram, definir métodos privados no @implementationbloco é bom para a maioria dos propósitos.

Sobre o tema da organização de código - eu gosto de mantê-los juntos pragma mark privatepara facilitar a navegação no Xcode

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

@end
Milão
fonte