Dichiarazione / definizione delle posizioni delle variabili in ObjectiveC?


113

Da quando ho iniziato a lavorare con le app iOS e l'obiettivo C sono rimasto davvero perplesso dalle diverse posizioni in cui si potrebbero dichiarare e definire variabili. Da un lato abbiamo il tradizionale approccio C, dall'altro abbiamo le nuove direttive ObjectiveC che aggiungono OO in cima a quello. Potreste aiutarmi a capire le migliori pratiche e le situazioni in cui vorrei utilizzare queste posizioni per le mie variabili e forse correggere la mia comprensione attuale?

Ecco una classe di esempio (.h e .m):

#import <Foundation/Foundation.h>

// 1) What do I declare here?

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

// 3) class-specific method / property declarations

@end

e

#import "SampleClass.h"

// 4) what goes here?

@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

@implementation SampleClass
{
    // 6) define ivars
}

// 7) define methods and synthesize properties from both public and private
//    interfaces

@end
  • La mia comprensione di 1 e 4 è che quelle sono dichiarazioni e definizioni basate su file in stile C che non hanno alcuna comprensione del concetto di classe, e quindi devono essere utilizzate esattamente come sarebbero usate in C. Le ho viste utilizzato per l'implementazione di singleton basati su variabili statiche prima. Ci sono altri usi convenienti che mi manca?
  • La mia opinione dal lavoro con iOS è che gli ivar sono stati quasi completamente eliminati al di fuori della direttiva @synthesize e quindi possono essere per lo più ignorati. È così?
  • Per quanto riguarda 5: perché dovrei mai voler dichiarare metodi in interfacce private? I miei metodi di classe privata sembrano compilarsi bene senza una dichiarazione nell'interfaccia. È principalmente per la leggibilità?

Grazie mille, gente!

Risposte:


154

Posso capire la tua confusione. Soprattutto perché i recenti aggiornamenti a Xcode e al nuovo compilatore LLVM hanno cambiato il modo in cui possono essere dichiarati ivars e proprietà.

Prima del "moderno" Objective-C (nel "vecchio" Obj-C 2.0) non avevi molte scelte. Le variabili di istanza venivano dichiarate nell'intestazione tra le parentesi graffe { }:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@end

Sei stato in grado di accedere a queste variabili solo nella tua implementazione, ma non da altre classi. Per farlo, dovevi dichiarare metodi di accesso, che assomigliano a questo:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}

- (int)myVar;
- (void)setMyVar:(int)newVar;

@end


// MyClass.m
@implementation MyClass

- (int)myVar {
   return myVar;
}

- (void)setMyVar:(int)newVar {
   if (newVar != myVar) {
      myVar = newVar;
   }
}

@end

In questo modo sei stato in grado di ottenere e impostare questa variabile di istanza anche da altre classi, usando la solita sintassi delle parentesi quadre per inviare messaggi (metodi di chiamata):

// OtherClass.m
int v = [myClass myVar];  // assuming myClass is an object of type MyClass.
[myClass setMyVar:v+1];

Perché dichiarare e implementare manualmente ogni metodo di accesso era piuttosto fastidioso @propertye @synthesizesono stati introdotti per generare automaticamente i metodi di accesso:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@synthesize myVar;
@end

Il risultato è un codice molto più chiaro e più breve. I metodi di accesso saranno implementati per te e puoi ancora usare la sintassi delle parentesi come prima. Ma in aggiunta, puoi anche utilizzare la sintassi del punto per accedere alle proprietà:

// OtherClass.m
int v = myClass.myVar;   // assuming myClass is an object of type MyClass.
myClass.myVar = v+1;

Da Xcode 4.4 non devi più dichiarare una variabile di istanza da solo e puoi @synthesizeanche saltare . Se non dichiari un ivar, il compilatore lo aggiungerà per te e genererà anche i metodi di accesso senza che tu debba usare @synthesize.

Il nome predefinito per ivar generato automaticamente è il nome o la proprietà che inizia con un trattino basso. È possibile modificare il nome ivar generato utilizzando@synthesize myVar = iVarName;

// MyClass.h
@interface MyClass : NSObject 
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@end

Funzionerà esattamente come il codice sopra. Per motivi di compatibilità puoi comunque dichiarare ivars nell'intestazione. Ma poiché l'unico motivo per cui vorresti farlo (e non dichiarare una proprietà) è creare una variabile privata, ora puoi farlo anche nel file di implementazione e questo è il modo preferito.

Un @interfaceblocco nel file di implementazione è in realtà un'estensione e può essere utilizzato per inoltrare metodi di dichiarazione (non più necessari) e per (ri) dichiarare proprietà. Ad esempio, potresti dichiarare una readonlyproprietà nella tua intestazione.

@property (nonatomic, readonly) myReadOnlyVar;

e dichiararlo nuovamente nel file di implementazione readwritein modo da poterlo impostare utilizzando la sintassi delle proprietà e non solo tramite accesso diretto a ivar.

Per quanto riguarda la dichiarazione delle variabili completamente al di fuori di qualsiasi @interfaceo di @implementationblocco, sì quelli sono variabili C di pianura e il lavoro esattamente lo stesso.


2
Bella risposta! Anche nota: stackoverflow.com/questions/9859719/...
nycynik

44

Per prima cosa, leggi la risposta di @ DrummerB. È una buona panoramica dei perché e di cosa dovresti fare in generale. Con questo in mente, alle tue domande specifiche:

#import <Foundation/Foundation.h>

// 1) What do I declare here?

Nessuna definizione di variabile effettiva va qui (è tecnicamente legale farlo se sai esattamente cosa stai facendo, ma non farlo mai). Puoi definire diversi altri tipi di cose:

  • typdefs
  • enumerazioni
  • externs

Gli esterni sembrano dichiarazioni di variabili, ma sono solo una promessa di dichiararlo effettivamente da qualche altra parte. In ObjC, dovrebbero essere usati solo per dichiarare costanti e generalmente solo costanti stringa. Per esempio:

extern NSString * const MYSomethingHappenedNotification;

Dovresti quindi nel tuo .mfile dichiarare la costante effettiva:

NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

Come notato da DrummerB, questa è un'eredità. Non mettere niente qui.


// 3) class-specific method / property declarations

@end

Sì.


#import "SampleClass.h"

// 4) what goes here?

Costanti esterne, come descritto sopra. Anche le variabili statiche del file possono andare qui. Questi sono l'equivalente delle variabili di classe in altre lingue.


@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end


@implementation SampleClass
{
    // 6) define ivars
}

Ma molto raramente. Quasi sempre dovresti consentire a clang (Xcode) di creare le variabili per te. Le eccezioni riguardano solitamente ivar non ObjC (come gli oggetti Core Foundation, e in particolare gli oggetti C ++ se questa è una classe ObjC ++), o ivar che hanno una semantica di archiviazione strana (come ivar che non corrispondono a una proprietà per qualche motivo).


// 7) define methods and synthesize properties from both public and private
//    interfaces

Generalmente non dovresti più @synthesize. Clang (Xcode) lo farà per te e dovresti lasciarlo.

Negli ultimi anni, le cose sono diventate notevolmente più semplici. L'effetto collaterale è che ora ci sono tre diverse epoche (Fragile ABI, Non fragile ABI, Non fragile ABI + auto-syntheisze). Quindi quando vedi il codice più vecchio, può essere un po 'di confusione. Quindi confusione derivante dalla semplicità: D


Mi chiedevo solo, ma perché non dovremmo sintetizzare esplicitamente? Lo faccio perché trovo il mio codice più facile da capire, soprattutto quando alcune proprietà hanno funzioni di accesso sintetizzate e alcune hanno implementazioni personalizzate, poiché sono abituato a sintetizzare. Ci sono inconvenienti nella sintesi esplicita?
Metabble

Il problema con l'utilizzo come documentazione è che in realtà non documenta nulla. Nonostante utilizzi synthesize, potresti aver sovrascritto una o entrambe le funzioni di accesso. Non c'è modo di dire dalla riga di sintesi qualcosa di veramente utile. L'unica cosa peggiore dell'assenza di documentazione è la documentazione fuorviante. Lascialo fuori.
Rob Napier,

3
Perché il n. 6 è raro? Non è questo il modo più semplice per ottenere una variabile privata?
pfrank

Il modo più semplice e migliore per ottenere una proprietà privata è il n. 5.
Rob Napier,

1
@RobNapier È ancora necessario usare @ synthesize a volte (ad esempio, se una proprietà è di sola lettura ha la sua funzione di accesso sovrascritta)
Andy

6

Sono anche abbastanza nuovo, quindi spero di non rovinare nulla.

1 e 4: variabili globali in stile C: hanno un ampio ambito di file. La differenza tra i due è che, poiché sono larghi file, il primo sarà disponibile a chiunque importi l'intestazione mentre il secondo no.

2: variabili di istanza. La maggior parte delle variabili di istanza vengono sintetizzate e recuperate / impostate tramite funzioni di accesso utilizzando le proprietà perché rende la gestione della memoria piacevole e semplice, oltre a fornire notazioni a punti di facile comprensione.

6: Gli ivar di implementazione sono in qualche modo nuovi. È un buon posto per mettere ivar privati, dal momento che vuoi esporre solo ciò che è necessario nell'intestazione pubblica, ma le sottoclassi non le ereditano AFAIK.

3 e 7: metodo pubblico e dichiarazioni di proprietà, quindi implementazioni.

5: interfaccia privata. Uso sempre interfacce private ogni volta che posso per mantenere le cose pulite e creare una sorta di effetto scatola nera. Se non hanno bisogno di saperlo, mettilo lì. Lo faccio anche per leggibilità, non so se ci sono altri motivi.


1
Non pensare di aver sbagliato nulla :) Alcuni commenti - # 1 e # 4 esp con # 4 spesso vedi variabili di archiviazione statiche. # 1 spesso vedrai lo spazio di archiviazione esterno specificato e quindi lo spazio di archiviazione effettivo allocato in # 4. # 2) di solito solo se una sottoclasse ne ha bisogno per qualsiasi motivo. # 5 non è più necessario inoltrare metodi dichiarati privati.
Carl Veazey,

Sì, ho appena controllato personalmente la dichiarazione anticipata. Era solito dare un avviso se un metodo privato ne chiamava un altro definito dopo di esso senza una dichiarazione anticipata, giusto? Sono rimasto un po 'sorpreso quando non mi ha avvertito.
Metabble

Sì, è una parte nuova del compilatore. Ultimamente hanno fatto davvero molti progressi.
Carl Veazey

6

Questo è un esempio di tutti i tipi di variabili dichiarate in Objective-C. Il nome della variabile indica il suo accesso.

File: Animal.h

@interface Animal : NSObject
{
    NSObject *iProtected;
@package
    NSObject *iPackage;
@private
    NSObject *iPrivate;
@protected
    NSObject *iProtected2; // default access. Only visible to subclasses.
@public
    NSObject *iPublic;
}

@property (nonatomic,strong) NSObject *iPublic2;

@end

File: Animal.m

#import "Animal.h"

// Same behaviour for categories (x) than for class extensions ().
@interface Animal(){
@public
    NSString *iNotVisible;
}
@property (nonatomic,strong) NSObject *iNotVisible2;
@end

@implementation Animal {
@public
    NSString *iNotVisible3;
}

-(id) init {
    self = [super init];
    if (self){
        iProtected  = @"iProtected";
        iPackage    = @"iPackage";
        iPrivate    = @"iPrivate";
        iProtected2 = @"iProtected2";
        iPublic     = @"iPublic";
        _iPublic2    = @"iPublic2";

        iNotVisible   = @"iNotVisible";
        _iNotVisible2 = @"iNotVisible2";
        iNotVisible3  = @"iNotVisible3";
    }
    return self;
}

@end

Notare che le variabili iNotVisible non sono visibili da nessun'altra classe. Questo è un problema di visibilità, quindi dichiararli con @propertyo @publicnon lo cambia.

All'interno di un costruttore è buona norma accedere alle variabili dichiarate @propertyutilizzando invece il carattere di sottolineatura selfper evitare effetti collaterali.

Proviamo ad accedere alle variabili.

File: Cow.h

#import "Animal.h"
@interface Cow : Animal
@end

File: Cow.m

#import "Cow.h"
#include <objc/runtime.h>

@implementation Cow

-(id)init {
    self=[super init];
    if (self){
        iProtected    = @"iProtected";
        iPackage      = @"iPackage";
        //iPrivate    = @"iPrivate"; // compiler error: variable is private
        iProtected2   = @"iProtected2";
        iPublic       = @"iPublic";
        self.iPublic2 = @"iPublic2"; // using self because the backing ivar is private

        //iNotVisible   = @"iNotVisible";  // compiler error: undeclared identifier
        //_iNotVisible2 = @"iNotVisible2"; // compiler error: undeclared identifier
        //iNotVisible3  = @"iNotVisible3"; // compiler error: undeclared identifier
    }
    return self;
}
@end

Possiamo ancora accedere alle variabili non visibili utilizzando il runtime.

File: Cow.m (parte 2)

@implementation Cow(blindAcess)

- (void) setIvar:(NSString*)name value:(id)value {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    object_setIvar(self, ivar, value);
}

- (id) getIvar:(NSString*)name {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    id thing = object_getIvar(self, ivar);
    return thing;
}

-(void) blindAccess {
    [self setIvar:@"iNotVisible"  value:@"iMadeVisible"];
    [self setIvar:@"_iNotVisible2" value:@"iMadeVisible2"];
    [self setIvar:@"iNotVisible3" value:@"iMadeVisible3"];
    NSLog(@"\n%@ \n%@ \n%@",
          [self getIvar:@"iNotVisible"],
          [self getIvar:@"_iNotVisible2"],
          [self getIvar:@"iNotVisible3"]);
}

@end

Proviamo ad accedere alle variabili non visibili.

File: main.m

#import "Cow.h"
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
    @autoreleasepool {
        Cow *cow = [Cow new];
        [cow performSelector:@selector(blindAccess)];
    }
}

Questo stampa

iMadeVisible 
iMadeVisible2 
iMadeVisible3

Si noti che sono stato in grado di accedere all'ivar di supporto _iNotVisible2che è privato della sottoclasse. In Objective-C tutte le variabili possono essere lette o impostate, anche quelle contrassegnate @private, senza eccezioni.

Non ho incluso oggetti associati o variabili C in quanto sono uccelli diversi. Per quanto riguarda le variabili C, qualsiasi variabile definita all'esterno @interface X{}o @implementation X{}è una variabile C con ambito di file e archiviazione statica.

Non ho discusso gli attributi di gestione della memoria, o readonly / readwrite, getter / setter.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.