25. Juli 2019

Objective-C Kurzreferenz

C-Typen

int intNumber = -5;

float floatNumber = 5.2f;

double doubleNumber = 5.3;

char *cString = "Hello World";

BOOL flag = YES;

Objekt-Typen

Bei der Verwendung von Objekttypen gilt es zu beachten, dass diese als C-Pointer deklariert werden. So wird eine Referenz auf ein beliebiges Objekt als Variable vom Typ NSObject * deklariert:

NSObject *object;
NSObject *object2, *object3;

Mit nil wird der Nullpointer referenziert (genau wie in C werden Variablen mit 0 initialisiert, so dass die explizite Zuweisung von nil nicht notwendig ist):

NSObject *object3 = nil;

Der Datentyp id referenziert beliebige Objekte, erlaubt beliebige Methodenaufrufe und wird implizit gecastet:

id obj = @[@"string1", @"string2"];
int i = [obj length];
NSArray *array = obj;

Für Zeichenketten, Listen und assoziative Arrays stehen die Typen NSString, NSArray und NSDictionary und zugehörige Literale zur Verfügung:

NSString *string = @"Hello World";

NSArray *cities = @[ @"Berlin", @"Paris", @"Rom" ];

NSDictionary *capitals = @{ @"Frankreich"  : @"Paris",
                            @"Deutschland" : @"Berlin",
                            @"Italien"     : @"Rom" };

Die Verwendung von C-Strings mittels "" ist in Objective-C unüblich. Achten Sie bei der Verwendung von Strings daher immer auf das @ vor den Anführungszeichen, so dass NSString-Objekte erzeugt werden.

Methodenaufrufe

Methoden werden in Objective-C mit [obj method] aufgerufen, zum Beispiel:

int length = [string length];

Methodenparameter sind in Objective-C benannt. Der Name des 1. Parameters verschmilzt dabei mit dem Methodennamen. So wird beispielsweise mit hasPrefix: geprüft, ob ein String mit einem bestimmten String beginnt:

BOOL prefix = [string hasPrefix:@"Hello"];

Analog werden Methoden aufgerufen, die mehrere Parameter erwarten. Beispielsweise kann mit der Methode stringByReplacingOccurrencesOfString:withString: in einem NSString gesucht und ersetzt werden:

NSString *str2 = [string stringByReplacingOccurrencesOfString:@"Hello" withString:@"Hi"];

Die Objective-C-Syntax für Methodenaufrufe unterscheidet sich also stark von der in anderen Sprachen gebräuchlichen Syntax obj.method(). Im Vergleich zu Java:

Methodenaufrufe Vergleich Objective-C - Java

Objekte konstruieren: Objektallokation, Initializer, Convenience-Konstruktoren

In Objective-C werden Objekte in zwei Schritten konstruiert: Der Aufruf von alloc allokiert Speicher für ein neues Objekt. Dieses Objekt ist noch nicht fertig und muss mit einem Initializer init initialisiert werden. Der Aufruf dieser beiden Methoden, z.B. [[NSArray alloc] init...] gehört zusammen und ist vergleichbar mit einem Konstruktor-Aufruf in anderen Sprachen:

NSArray *array1 = [[NSArray alloc] init];

NSArray *array2 = [[NSArray alloc] initWithArray:array1];

Viele Klassen deklarieren zusätzlich Convenience-Konstruktoren als Klassenmethoden, die alloc + init in einem Schritt erledigen:

array1 = [NSArray array];

array2 = [NSArray arrayWithArray:array1];

Klassen

Eine Objective-C Klasse wird in einer Header-Datei mit @interface deklariert und mittels @implementation in einer .m-Datei implementiert:

Imports

Header werden in Objective-C mit #import eingebunden. Dabei ist darauf zu achten, ob ein Header aus dem eigenen Projekt oder ein Framework-Header importiert wird:

Instanzvariablen und Methoden deklarieren

Instanzvariablen werden mit einem { ... }-Block in @implementation deklariert. Methoden, die für andere Quelldateien sichtbar sein sollen, werden im Header deklariert. Alle Methoden werden im @implementation-Block der Klasse implementiert:

Hinweis: Instanzvariablen können alternativ in @interface in einem { ... }-Block deklariert werden. Dies ist weitverbreitet, da vor Xcode 4.2 Instanzvariablen in @interface deklariert werden mussten. Da Instanzvariablen nicht nach außen sichtbar sein sollten, empfiehlt es sich, Instanzvariablen nun generell in @implementation in der .m-Datei zu deklarieren. Achten Sie dabei unbedingt auf die Deklaration in einem { ... }-Block, da ansonsten eine globale C-Variable deklariert wird!

Methodensignaturen

Klassenmethoden werden mit + deklariert, Instanzmethoden mit -. Darauf folgt der Rückgabewert z.B. (int), oder (void) falls kein Wert zurückgegeben wird. Danach wird die Methodensignatur aus "parameter:(type)variable"-Deklarationen zusammengesetzt:

// + Klassenmethoden

+ (id) calculator;
+ (id) calculatorWithValue:(int)value;

// - Instanzmethoden

- (id) initWithValue:(int)no;
- (void) increment;
- (void) add:(int)no;
- (void) addNumber1:(int)param1 number2:(int)param2;
- (int) value;
- (void) setValue:(int)newValue;

Methoden

Methoden werden in @implementation durch die Methodensignatur gefolgt von einem { ... }-Block implementiert. Mit self wird auf das Objekt selbst zugegriffen:

- (void) add:(int)no {
    value += no;
    NSLog(@"New %@ value: %i", self, value);
}

Initializer

Klassen können die Initializer-Methode init überschreiben bzw. weitere Methoden wie initWithParam: deklarieren, um den initialen Zustand der Objekte zu konfigurieren. Dabei ist [super init] aufzurufen und self zu setzen. Es sollte geprüft werden, ob ein Fehler beim Initialisieren des Objektes aufgetreten ist (dann gibt [super init] nil zurück). Dann kann das Objekt initialisiert und zurückgegeben werden:

- (id) init {
    self = [super init];
    if (self) {
        value = 0;
    }
    return self;
}

- (id) initWithValue:(int)no {
    self = [self init];
    if (self) {
        value = no;
    }
    return self;
}

Convenience-Konstruktor

Für Initializer, die häufig aufgerufen werden, empfiehlt es sich, Convenience Konstruktoren als statische Klassenmethode bereitzustellen, die alloc + init in einem Schritt erledigen und das Objekt zurückliefern:

+ (id) calculatorWithValue:(int)value;
    return [[self alloc] initWithValue:value];
}

Property

Soll der Wert einer Instanzvariable außerhalb der Klasse lesbar oder veränderbar sein, werden typischerweise Getter- und Setter-Methoden hinzugefügt, die den Zugriff auf den Wert der Instanzvariable erlauben. Zum Beispiel:

@implementation Calculator {
    int value;
}

- (int) value {
    return value;
}

- (void) setValue:(int)newValue {
    value = newValue;
}

Dasselbe kann mit einem Property erreicht werden:

@interface Calculator : NSObject { }

@property int value;

@end

Die Definition eines Properties wie z.B. value bewirkt, dass vom Compiler automatisch eine Instanzvariable _value, eine Getter-Methode value und eine Setter-Methode setValue generiert wird.

Der Zugriff auf ein Property oder entsprechend benannte Getter- und Setter-Methoden kann mittels obj.property abgekürzt werden:

int value = obj.value;
obj.value = value + 1;
obj.value++;

In der Klasse selbst erfolgt der Zugriff auf das Property über self oder direkt über die Instanzvariable:

// Property
self.value = 5;

// Instanzvariable
_value = 5

Bei der Definition von Properties können Eigenschaften angegeben werden, die beim Generieren der Methoden berücksichtigt werden, z.B.:

@property (nonatomic, copy) NSString *text;

Folgende Eigenschaften können angegeben werden:

Strings

Literal

NSString *string = @"Hello World";

Strings formatieren

NSString *formattedString = [NSString stringWithFormat:@"%i. %@", intNumber, array1];

Siehe: String Format Specifiers

Suchen und ersetzen

NSString *replaced = [string stringByReplacingOccurrencesOfString:@"Hello" withString:@"Hi"];

Konvertierung zwischen char* und NSString

const char *utf8CString = [string UTF8String];
NSString *fromCString = [NSString stringWithUTF8String:utf8CString];

Array

Literal

NSArray *array = @[ @"string1", @"string2" ];

Elementzugriff

NSString *value = array[0];

for-each Loop

for (NSString *str in array) {
    NSLog(@"%@", str);
}

Dictionary

Literal

NSDictionary *dict = @{ @"key" : @"value", @"foo" : @"bar" };

Wert für Key

NSString *value = dict[@"key"];

Iteration

[dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
    NSLog(@"%@ => %@", key, value);
}];

for (NSString *key in dict) {
    NSLog(@"%@ => %@", key, [dict objectForKey:key]);
}

Mutable Types

NSLog

NSLog(@"String: %@, Array: %@, int: %i", formattedString, array1, intNumber);
NSLog(@"%@", string);

Konstanten deklarieren

.h:

extern NSString *const kDetailSegueIdentifier;

.m:

NSString *const kDetailSegueIdentifier = @"DetailSegue";

.m only:

static NSString *const kDetailSegueIdentifier = @"DetailSegue";

Selektoren

Eine Methodensignatur wie stringByReplacingOccurencesOfString:withString: wird als Selektor bezeichnet. Selektoren können im Code mittels @selector erzeugt werden:

SEL selector = @selector(someMethodWithParam:anotherParam:);

Prüfen, ob ein Objekt eine Nachricht versteht, d.h. auf den Aufruf des Selektors antworten würde:

BOOL responds = [self respondsToSelector:@selector(someMethodWithParam:)];

Manueller Aufruf eines Selektors:

id result = [anObject performSelector:
                        @selector(someMethodWithParam:) withObject:someParamObject];

Block-Syntax

Ein Objective-C Block repräsentiert Code als Objekt:

id block = ^{
    NSLog(@"Hello block!");
};

Dieser Mechanismus ist vor allem nützlich für Callbacks und nebenläufige Programmierung, da so recht einfach Code übergeben werden kann, der zu einem späterem Zeitpunkt ausgeführt werden soll, ohne dafür explizit eine Klasse oder Methode deklarieren zu müssen.

Blöcke verwenden

Einige iOS-APIs stellen blockbasierte Aufrufe bereit, z.B: ruft enumerateKeysAndObjectsUsingBlock: für ein NSDictionary den übergebenen Block für jedes Key-Value-Paar auf und übergibt dem Block key und value als Parameter:

NSDictionary *dict = @{ @"key" : @"value", @"key2" : @"value2" };

[dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
    NSLog(@"%@ => %@", key, value);
}];

Dabei kann der Block auf alle im Scope deklarierten Variablen lesend zugreifen, mittels __block können Variablen auch schreibend verwendet werden:

NSDictionary *dict = @{@"key" : @"value", @"key2" : @"value2" };
NSMutableArray *array = [NSMutableArray array];
__block int length;

[dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
    [array addObject:key];
    length += key.length;
}];

Blöcke mit typedef deklarieren

Um Blöcke als Methodenparameter für eigene Methoden zu verwenden, empfiehlt es sich, einen typedef zu deklarieren. Zum Beispiel:

typedef int (^NumberOperationBlock)(int, int);

Für die Deklaration eines Block-Typedefs gibt es ein Xcode-Snippet:

Im Beispiel können so Blöcke vom Typ int (^)(int, int) als NumberOperationBlock deklariert werden:

NumberOperationBlock block = ^(int a, int b){
    return a + b;
};

int result = block(3, 4);

Captures und Modifier __block

Werte von außerhalb definierten Variablen werden im Block erfasst und können nur lesend verwendet werden:

int i = 10;

void(^block)(void) = ^{ NSLog(@"%i", i); };

i = 20;

block();

// Ausgabe: 10

__block ermöglicht schreibenden Zugriff und gemeinsamen Storage:

__block int i = 10;

void(^block)(void) = ^{ NSLog(@"%i", ++i); };

i = 20;

block();

// Ausgabe: 21