Wo kann man iVars in „modernes“ Objective-C einfügen?

81

Das Buch "iOS6 by Tutorials" von Ray Wenderlich enthält ein sehr schönes Kapitel über das Schreiben von "modernerem" Objective-C-Code. In einem Abschnitt wird beschrieben, wie iVars aus dem Header der Klasse in die Implementierungsdatei verschoben werden. Da alle iVars privat sein sollten, scheint dies das Richtige zu sein.

Bisher habe ich jedoch drei Möglichkeiten gefunden. Jeder macht es anders.

1.) Setzen Sie iVars unter @implementantion in einen Block geschweifter Klammern (so wird es im Buch gemacht).

2.) Setzen Sie iVars unter @implementantion ohne Block geschweifter Klammern

3.) Platzieren Sie iVars in der privaten Schnittstelle über dem @implementantion (eine Klassenerweiterung).

Alle diese Lösungen scheinen gut zu funktionieren, und bisher habe ich keinen Unterschied im Verhalten meiner Anwendung festgestellt. Ich denke, es gibt keinen "richtigen" Weg, dies zu tun, aber ich muss einige Tutorials schreiben und möchte nur einen Weg für meinen Code wählen.

Welchen Weg soll ich gehen?

Edit: Ich spreche hier nur von iVars. Keine Eigenschaften. Nur zusätzliche Variablen, die das Objekt nur für sich selbst benötigt und die nicht nach außen exponiert werden sollten.

Codebeispiele

1)

#import "Person.h"

@implementation Person
{
    int age;
    NSString *name;
}

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

2)

#import "Person.h"

@implementation Person

int age;
NSString *name;


- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

3)

#import "Person.h"

@interface Person()
{
    int age;
    NSString *name;
}
@end

@implementation Person

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end
TalkingCode
quelle
4
Kein Kommentar zu "richtig", aber eine andere Option - auf die ich mich zu bewegen scheine - besteht darin, iVars vollständig zu sichern und alles zu einer Eigenschaft zu machen (meistens innerhalb einer Klassenerweiterung in der .m-Datei). Konsistenz erspart mir das Nachdenken über die Implementierungsdetails des Zustands.
Phillip Mills
1
Diese Frage würde anderen wirklich zugute kommen, wenn Sie die Frage so aktualisieren, dass echte Codebeispiele für jede der drei Optionen angezeigt werden. Ich persönlich benutze nur Option 1, habe Option 3 gesehen, bin aber mit Option 2 nicht vertraut.
rmaddy
1
Danke Papa. Ich habe einen Beispielcode hinzugefügt.
TalkingCode
4
Entschuldigung, habe gerade das Update bemerkt. Option 2 ist nicht gültig. Das schafft keine Ivars. Dadurch werden globale Dateivariablen erstellt. Jede Instanz dieser Klasse teilt diesen einen Satz von Variablen. Sie könnten sie Klassenvariablen, nicht Instanzvariablen.
rmaddy

Antworten:

162

Die Möglichkeit, Instanzvariablen in den @implementationBlock oder in eine Klassenerweiterung einzufügen, ist eine Funktion der „modernen Objective-C-Laufzeit“, die von jeder iOS-Version und von 64-Bit-Mac OS X-Programmen verwendet wird.

Wenn Sie 32-Bit-Mac OS X-Apps schreiben möchten, müssen Sie Ihre Instanzvariablen in die @interfaceDeklaration einfügen. Möglicherweise müssen Sie jedoch keine 32-Bit-Version Ihrer App unterstützen. OS X unterstützt seit Version 10.5 (Leopard), die vor über fünf Jahren veröffentlicht wurde, 64-Bit-Apps.

Nehmen wir also an, Sie schreiben nur Apps, die die moderne Laufzeit verwenden. Wo solltest du deine Ivars hinstellen?

Option 0: In der @interface(Don't Do It)

Lassen Sie uns zunächst untersuchen, warum wir keine Instanzvariablen in eine @interfaceDeklaration einfügen möchten .

  1. Durch das Einfügen von Instanzvariablen in wird @interfaceden Benutzern der Klasse Details der Implementierung angezeigt. Dies kann dazu führen, dass sich diese Benutzer (auch Sie selbst, wenn Sie Ihre eigenen Klassen verwenden!) Auf Implementierungsdetails verlassen, die sie nicht sollten. (Dies ist unabhängig davon, ob wir die Ivare deklarieren @private.)

  2. Wenn Sie Instanzvariablen in eine Datei @interfaceeinfügen, dauert das Kompilieren länger, da wir jedes Mal, wenn wir eine ivar-Deklaration hinzufügen, ändern oder entfernen, jede .mDatei neu kompilieren müssen , die die Schnittstelle importiert.

Wir wollen also keine Instanzvariablen in die @interface. Wo sollen wir sie hinstellen?

Option 2: In den @implementationohne Klammern (Don't Do It)

Lassen Sie uns als Nächstes Ihre Option 2 diskutieren: "Setzen Sie iVars unter @implementantion ohne Block geschweifter Klammern". Dies deklariert keine Instanzvariablen! Sie sprechen darüber:

@implementation Person

int age;
NSString *name;

...

Dieser Code definiert zwei globale Variablen. Es werden keine Instanzvariablen deklariert.

Es ist in Ordnung, globale Variablen in Ihrer .mDatei zu definieren , auch in Ihrer @implementation, wenn Sie globale Variablen benötigen - zum Beispiel, weil alle Ihre Instanzen einen bestimmten Status haben sollen, z. B. einen Cache. Sie können diese Option jedoch nicht zum Deklarieren von Ivars verwenden, da Ivars nicht deklariert werden. (Außerdem sollten globale Variablen, die für Ihre Implementierung privat sind, normalerweise deklariert werden static, um eine Verschmutzung des globalen Namespace und das Risiko von Verbindungszeitfehlern zu vermeiden.)

Damit bleiben Ihre Optionen 1 und 3.

Option 1: In den @implementationmit Klammern (Do It)

Normalerweise möchten wir Option 1 verwenden: Setzen Sie sie @implementationin geschweiften Klammern wie folgt in Ihren Hauptblock:

@implementation Person {
    int age;
    NSString *name;
}

Wir setzen sie hier ein, weil es ihre Existenz privat hält und die zuvor beschriebenen Probleme verhindert, und weil es normalerweise keinen Grund gibt, sie in eine Klassenerweiterung aufzunehmen.

Wann möchten wir Ihre Option 3 verwenden und sie in eine Klassenerweiterung einfügen?

Option 3: In einer Klassenerweiterung (nur bei Bedarf ausführen)

Es gibt fast nie einen Grund, sie in einer Klassenerweiterung in derselben Datei wie die Klasse abzulegen @implementation. Wir könnten sie @implementationin diesem Fall genauso gut in den Fall setzen.

Gelegentlich schreiben wir jedoch eine Klasse, die groß genug ist, um den Quellcode in mehrere Dateien aufzuteilen. Wir können das mit Kategorien machen. Wenn wir beispielsweise implementieren UICollectionView(eine ziemlich große Klasse), könnten wir entscheiden, dass wir den Code, der die Warteschlangen wiederverwendbarer Ansichten (Zellen und zusätzliche Ansichten) verwaltet, in eine separate Quelldatei einfügen möchten. Wir könnten das tun, indem wir diese Nachrichten in eine Kategorie aufteilen:

// UICollectionView.h

@interface UICollectionView : UIScrollView

- (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
@property (nonatomic, retain) UICollectionView *collectionViewLayout;
// etc.

@end

@interface UICollectionView (ReusableViews)

- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;

- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;

- (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;
- (id)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;

@end

OK, jetzt können wir die UICollectionViewHauptmethoden in UICollectionView.mimplementieren und die Methoden implementieren UICollectionView+ReusableViews.m, mit denen wiederverwendbare Ansichten verwaltet werden , wodurch unser Quellcode ein wenig übersichtlicher wird.

Unser wiederverwendbarer Ansichtsverwaltungscode benötigt jedoch einige Instanzvariablen. Diese Variablen müssen für die Hauptklasse @implementationin UICollectionView.mverfügbar gemacht werden, damit der Compiler sie in der .oDatei ausgibt. Außerdem müssen wir diese Instanzvariablen für den Code in verfügbar machen UICollectionView+ReusableViews.m, damit diese Methoden die ivars verwenden können.

Hier brauchen wir eine Klassenerweiterung. Wir können die ivars für die Verwaltung wiederverwendbarer Ansichten in eine Klassenerweiterung in einer privaten Header-Datei einfügen:

// UICollectionView_ReusableViewsSupport.h

@interface UICollectionView () {
    NSMutableDictionary *registeredCellSources;
    NSMutableDictionary *spareCellsByIdentifier;

    NSMutableDictionary *registeredSupplementaryViewSources;
    NSMutableDictionary *spareSupplementaryViewsByIdentifier;
}

- (void)initReusableViewSupport;

@end

Wir werden diese Header-Datei nicht an Benutzer unserer Bibliothek senden. Wir importieren nur in UICollectionView.mund in UICollectionView+ReusableViews.m, so dass alles, was Bedürfnisse diese ivars zu sehen , sie sehen. Wir haben auch eine Methode eingeführt, die die Hauptmethode initaufrufen soll, um den Code für die Verwaltung wiederverwendbarer Ansichten zu initialisieren. Wir werden diese Methode von -[UICollectionView initWithFrame:collectionViewLayout:]in aufrufen UICollectionView.mund in implementieren UICollectionView+ReusableViews.m.

Rob Mayoff
quelle
4
Option 3: Kann auch verwendet werden, wenn 1) Sie die Verwendung von Eigenschaften für Ihre ivars erzwingen möchten, auch innerhalb Ihrer eigenen Implementierung (@property int age), und 2) Sie eine schreibgeschützte öffentliche Eigenschaft (@property (schreibgeschützt) int age überschreiben möchten) in Ihrem Header, damit Ihr Code als Readwrite in der Implementierung auf die Eigenschaft zugreifen kann (@property (readwrite) int age). Sofern es keine anderen Möglichkeiten gibt, kann @rob mayoff?
Leanne
@leanne Wenn Sie eine readonlyEigenschaft überschreiben möchten, um privat zu sein readwrite, müssen Sie eine Klassenerweiterung verwenden. Aber die Frage war, wo man Ivars platzieren sollte, nicht wo man Eigentumserklärungen platzieren sollte. Ich sehe nicht, wie die Verwendung einer Klassenerweiterung verhindern kann, dass die Implementierung auf ivars zugreift. Dazu müssen Sie Ihre Methodenimplementierungen in eine Kategorie einordnen, da der Compiler alle ivar-deklarierenden Klassenerweiterungen sehen muss, bevor er die sieht @implementation.
Rob Mayoff
@rob mayoff, wahr und wahr - Sie machen gültige Punkte. Holli gab an, dass Eigenschaften hier nicht in Frage kamen und "Durchsetzen" meinerseits in Bezug auf ihre Verwendung etwas stark war. Unabhängig davon, ob man Eigenschaften verwenden möchte, um auf ihre Ivars zuzugreifen, anstatt dies direkt zu tun, dann wäre dies der richtige Weg. Und natürlich können Sie immer noch direkt mit dem '_' auf die ivars zugreifen (_age = someAge). Ich habe den Kommentar hinzugefügt, weil ich dachte, dass die Verwendung von Immobilien bei der Diskussion über Ivars erwähnenswert ist, da viele Leute sie zusammen verwenden, und dies wäre der Weg, dies zu tun.
Leanne
Das ist "Option 0: In der @ Schnittstelle (nicht tun)".
Rob Mayoff
5

Option 2 ist völlig falsch. Dies sind globale Variablen, keine Instanzvariablen.

Die Optionen 1 und 3 sind im Wesentlichen identisch. Es macht absolut keinen Unterschied.

Sie können wählen, ob Instanzvariablen in die Header-Datei oder in die Implementierungsdatei eingefügt werden sollen. Der Vorteil der Verwendung der Header-Datei besteht darin, dass Sie über eine schnelle und einfache Tastenkombination (Befehl + Strg + Nach oben in Xcode) verfügen, mit der Sie Ihre Instanzvariablen und die Schnittstellendeklaration anzeigen und bearbeiten können.

Der Nachteil ist, dass Sie die privaten Details Ihrer Klasse in einem öffentlichen Header verfügbar machen. Dies ist in einigen Fällen nicht wünschenswert, insbesondere wenn Sie Code für andere schreiben. Ein weiteres potenzielles Problem besteht darin, dass Sie bei Verwendung von Objective-C ++ vermeiden sollten, C ++ - Datentypen in Ihre Header-Datei aufzunehmen.

Implementierungsinstanzvariablen sind in bestimmten Situationen eine gute Option, aber für den größten Teil meines Codes habe ich die Instanzvariablen immer noch in den Header eingefügt, nur weil es für mich als Codierer, der in Xcode arbeitet, bequemer ist. Mein Rat ist, alles zu tun, was Sie für bequemer halten.

Darren
quelle
4

Dies hat größtenteils mit der Sichtbarkeit des Ivar für Unterklassen zu tun. Unterklassen können nicht auf im @implementationBlock definierte Instanzvariablen zugreifen .

Bei wiederverwendbarem Code, den ich verteilen möchte (z. B. Bibliotheks- oder Framework-Code), bei dem ich keine Instanzvariablen für die öffentliche Überprüfung offenlegen möchte, neige ich dazu, die ivars in den Implementierungsblock zu platzieren (Ihre Option 1).

FluffulousChimp
quelle
3

Sie sollten Instanzvariablen in einer privaten Schnittstelle über der Implementierung platzieren. Option 3.

Die Dokumentation dazu ist das Programmieren in Objective-C- Handbuch.

Aus der Dokumentation:

Sie können Instanzvariablen ohne Eigenschaften definieren

Es wird empfohlen, eine Eigenschaft für ein Objekt immer dann zu verwenden, wenn Sie einen Wert oder ein anderes Objekt verfolgen müssen.

Wenn Sie Ihre eigenen Instanzvariablen definieren müssen, ohne eine Eigenschaft zu deklarieren, können Sie sie in geschweiften Klammern oben in der Klassenschnittstelle oder Implementierung wie folgt hinzufügen:

JoePasq
quelle
1
Ich stimme Ihnen zu: Wenn Sie ivar definieren wollten, ist die private Klassenerweiterung der beste Ort. Seltsamerweise nimmt die Dokumentation, auf die Sie verweisen, nicht nur keine Position dazu ein, wo Ivare definiert werden sollten (sie enthält lediglich alle drei Alternativen), sondern geht noch weiter und empfiehlt, überhaupt keine Ivars zu verwenden, sondern eher "es ist die beste Praxis, eine Eigenschaft zu verwenden." Das ist der beste Rat, den ich bisher gesehen habe.
Rob
1

Öffentliche Ivars sollten wirklich als Eigenschaften in der @ Schnittstelle deklariert werden (wahrscheinlich das, woran Sie in 1 denken). Private Ivars können, wenn Sie den neuesten Xcode ausführen und die moderne Laufzeit (64-Bit-OS X oder iOS) verwenden, in der @ -Implementierung (2) deklariert werden und nicht in einer Klassenerweiterung. Ich denke an in 3.

Jon Shier
quelle