Im Buch wird erläutert, dass mutable Container als Attribute einer Entität eine gefährliche
Sache sind. Allerdings kann man bei to-many-Relationships, welche durch
NSArray
oder
NSSet
repräsentiert werden, durchaus zu mutable Collections greifen, wenn man sich der Lage bewusst
ist. Die Belohnung: Verbesserung des Laufzeitverhaltens.
Legen Sie sich ein Projekt »Besseres KVC« als einfache Cocoa Application an.
Erzeugen Sie sich zunächst eine Klasse
Person
(*gähn schnnarch schon wieder augenroll*), die wie folgt aussieht:
Person.h
#import <Cocoa/Cocoa.h>
@interface Person : NSObject {
NSString* firstName;
NSString* lastName;
}
- (id)initWithFirstName:(NSString*)firstName
andLastName:(NSString*)lastName;
- (Person*)personWithFirstName:(NSString*)firstName
andLastName:(NSString*)lastName;
- (NSString *)firstName;
- (void)setFirstName:(NSString *)value;
- (NSString *)lastName;
- (void)setLastName:(NSString*)value;
@end
Also eine Entität, die die beiden Attribute
firstName
und
lastName
hat. Dazu Accessoren. Schnell auch noch die Implementierung herunter getippt;
Person.m
#import "Person.h"
@implementation Person
- (NSString *)firstName {
return [[firstName retain] autorelease];
}
- (void)setFirstName:(NSString *)value {
if (firstName != value) {
[firstName release];
firstName = [value copy];
}
}
- (NSString *)lastName {
return [[lastName retain] autorelease];
}
- (void)setLastName:(NSString *)value {
if (lastName != value) {
[lastName release];
lastName = [value copy];
}
}
- (id)initWithFirstName:(NSString*)firstName_
andLastName:(NSString*)lastName_
{
self = [super init];
if( self ) {
[self setFirstName:firstName_];
[self setLastName:lastName_];
}
return self;
}
+ (Person*)personWithFirstName:(NSString*)firstName_
andLastName:(NSString*)lastName_
{
return [[[self alloc] initWithFirstName:firstName_
andLastName:lastName_] autorelease];
}
- (void) dealloc {
[self setFirstName:nil];
[self setLastName:nil];
[super dealloc];
}
@end
Zu den hier verwendeten Namenskonventionen schauen sie bitte in das entsprechende Tutorial.
Da es ja um Beziehungen geht, müssen wir eine zweite Entität erzeugen, die in Bezug nimmt. Bauen
wir uns eine Klasse
Book
(Sagen Sie nicht, dass Sie bereits damit gerechnet haben!!9!!8!!):
Book.h
#import <Cocoa/Cocoa.h>
@interface Book : NSObject {
NSString* title;
NSArray* authors;
}
- (NSString *)title;
- (void)setTitle:(NSString *)value;
- (NSArray *)authors;
- (void)setAuthors:(NSArray *)value;
@end
Also eine Entität mit einem Attribut
title
und einer to-many-Relationship authors. Die dazu gehörige Implementierung lautet dann;
Book.m
#import "Book.h"
@implementation Book
- (NSString *)title {
return [[title retain] autorelease];
}
- (void)setTitle:(NSString *)value {
if (title != value) {
[title release];
title = [value copy];
}
}
- (NSArray *)authors {
return [[authors retain] autorelease];
}
- (void)setAuthors:(NSArray *)value {
if (authors != value) {
[authors release];
authors = [value retain];
}
}
- (id)init {
self = [super init];
if( self ) {
[self setTitle:@"Objective-C und Cocoa"];
[self setAuthors:[NSArray array]];
}
return self;
}
- (void) dealloc {
[self setTitle:nil];
[self setAuthors:nil];
[super dealloc];
}
@end
Auf eine Implementierung von umfangreicheren Initialisierern und convenience Allocators verzichten wir hier, um den Text kompakt zu halten. Wir werden diese Klasse nur im Interface Builder instantieren.
Mal eine Compilierung wagen, um die Teppfihler zu entdecken und zu beseitigen.
Im Interface Builder lesen wir die Klasse
Book
und instantieren davon ein Objekt. Wir legen uns einen Object-Controller an, dessen Content-Outlet
wir mit der Book-Instanz verbinden. Ziehen Sie zudem einen Array-Controller herein, der unsere
Autoren verwalten wird. Dessen
contentArray
-Binding binden Sie an den Object-Controller mit dem Model-Path
authors
.
Schwupp, Fenster aufgemacht und ein Textfield für den Titel angelegt und an den Object-Controller
gebunden. Außerdem ein Tableview, dessen Spalten wir an den Array-Controller mit den Model-Paths
firstName
und
lastName
binden. Unten zwei Buttons für Einfügen und Löschen, welche Sie mittels ctrl-Drag mit den
Actions
add:
und
remove:
des Array-Controllers verbinden. Alles speichern, kompilieren und starten. Das kennen Sie ja
schon.
Um besser sehen zu können, was passiert, schreiben wir uns eine eigene Einfügemethode. Wir
benötigen also einen Controller, der eine Action -
addAuthor:
anbietet. Gehen Sie in den Interface Builder und erzeugen Sie mittels
Classes
|
Subclass NSObject
eine Subklasse von
NSObject
, die sie Controller nennen.
Sie können freilich diese Klasse auch wieder in Xcode erzeugen und in den Interface Builder importieren. Letztlich hängt es von Ihrem Geschmack ab. Ich versuche, beide Möglichkeiten gleich zu trainieren. Achten Sie bei der Erstellung von Klassen aus dem Interface Builder aber stets darauf, dass sie ihn aus dem Projekt gestartet haben. Nur so können die Dateien dem Projekt hinzugefügt werden.
Fügen Sie im Inspector der Klasse ein Outlet book und eine Action
-addAuthor:
hinzu und lassen Sie von dem Interface Builder mittels
Classes
|
Create Files for Controller
die Sourcen erzeugen. Zuletzt erstellen Sie eine Instanz im Nib. Ziehen Sie mit gedrückter
ctrl-Taste die Verbindung von der Controller-Instanz zur Book-Instanz.
Bevor Sie jetzt den Interface Builder verlassen, fügen Sie einen weiteren Button ein, den Sie etwa
mit »Add by Code« nennen. Diesen verbinden Sie mit der Action
-addAuthor:
des Controllers.
Speichern und zu Xcode wechseln. Der Header von Controller sollte jetzt so aussehen:
Controller.h
#import <Cocoa/Cocoa.h>
@interface Controller : NSObject
{
IBOutlet Book *book;
}
- (IBAction)addAuthor:(id)sender;
@end
Wenn das Outlet mit
id
untypisiert sein sollte, so haben Sie vergessen, bei der Anlage im Interface Builder dem Outlet
einen Typ zu geben. Das passiert leicht. Es ist nicht weiter schlimm. Dennoch sollten Sie in der
Regel eine Typisierung vornehmen, wenn sie ein Objekt einer bestimmten Klasse erwarten. Also hinter
die Ohren schreiben. Sie können das auch noch jetzt im Interface Builder ändern und dann die
Dateien neu erzeugen lassen. Die zweimalige Nachfrage bestätigen Sie mit einem Klick auf
Overwrite,
Nach dem Import fügen Sie bitte eine Zeile
Controller.h
@class Book;
ein, damit die Klasse
Book
bekannt ist. (Erde an Apple: Das könnte der Interface Builder auch automatisch machen, nicht
wahr?)
Die Implementierung hat folgendes Aussehen:
Controller.m
#import "Controller.h"
@implementation Controller
- (IBAction)addAuthor:(id)sender {
}
@end
Zunächst setzen Sie unter das Bestehende Import zwei weiteres für unsere Klasse
Book
und
Person
:
Controller.m
#import "Book.h"
#import "Person.h"
Schreiben wir uns also wie im Buch beschrieben die Methode für das Einfügen:
Controller.m
- (IBAction)addAuthor:(id)sender
{
// Einen neuen Autor erzeugen
Person* newPerson = [Person personWithFirstName:@"Amin"
andLastName:@"Negm-Awad"];
// Die bisherigen Autoren abholen und eine mutable Variante
// aus dem Array machen
NSMutableArray* authors
= [NSMutableArray arrayWithArray:[book authors]];
// Den neuen Autor einfuegen
[authors addObject:newPerson];
// Und das Ganze wieder setzen
[book setAuthors:authors];
}
Wenn Sie das Programm starten, so wird automatisch der Autor eingefügt.