Indirektes Erzeugen einer Klasseninstanz und Aufruf einer Methode

anhe

anhe

Aktives Mitglied
Thread Starter
Dabei seit
13.11.2007
Beiträge
1.899
Reaktionspunkte
309
Hallo zusammen,

ich lerne gerade Objective-C nach dem Buch "Programming in Objective-C" von Stephen Kochan. Eine Aufgabe im Buch ist es eine "Musiksammlung" zu erstellen in der es "Playlisten" gibt die mit "Songs" gefüllt sind. "Songs" die der Musiksammlung hinzugefügt werden werden in einer "Playlist" Library gespeichert und zusätzlich in weiteren frei definierten Playlisten.

Im Grunde funktioniert auch alles wie ich es möchte. Ich habe die entsprechenden Klassen (MusicCollection, MyPlaylist und MySongs) erstellt und kann die auch direkt erzeugen und füllen.

Ich möchte aber meine Playlisten nicht direkt erzeugen und dann dem Array der MusicCollection zufügen, sondern indirekt über eine MusicCollection-Methode erzeugen und dem Array zuweisen. Das funktioniert zwar, aber dann kann ich keine Songs hinzufügen.

Ich wollte der zunächst ein Minimalbeispiel erzeugen um Euch das Problem zu verdeutlichen. Das Minimalbeispiel wurde letztlich genauso umfangreich wie das Original. Deswegen poste ich jetzt dem gesamten Quelltest. Ich hoffe Ihr verzeiht mir das ...

Könnt Ihr erkennen, wo das Problem liegt und warum ich bei der indirekten Erzeugung der Instanz keine "Songs" zufügen kann?

main.m:
Code:
#import "MySong.h"
#import "MyPlaylist.h"
#import "MusicCollection.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       
        // Erzeuge Instanz myMusic aus MusicCollection-Klasse
        MusicCollection *myMusic = [[MusicCollection alloc] initWithName:@"Andrés music"];
       
        // Erzeuge fünf Instanzen SongX aus MySong-Klasse
        MySong *Song1 = [[MySong alloc] init];
        MySong *Song2 = [[MySong alloc] init];
        MySong *Song3 = [[MySong alloc] init];
        MySong *Song4 = [[MySong alloc] init];
        MySong *Song5 = [[MySong alloc] init];
       
        [Song1 setTitle:@"Never let me down again" andArtist:@"Depeche Mode" andAlbum:@"Music for the Masses" andDuration:@"4:48"];
        [Song2 setTitle:@"Personal Jesus" andArtist:@"Depeche Mode" andAlbum:@"Violator" andDuration:@"4:56"];
        [Song3 setTitle:@"Enjoy the Silence" andArtist:@"Depeche Mode" andAlbum:@"Violator" andDuration:@"6:13"];
        [Song4 setTitle:@"Hells Bells" andArtist:@"AC/DC" andAlbum:@"Who made who" andDuration:@"5:13"];
        [Song5 setTitle:@"St. Stephen" andArtist:@"Grateful Dead" andAlbum:@"Aoxomoxoa (Remastered)" andDuration:@"4:26"];
       
        // Füge die Songs der MyPlaylist "masterList" aus der MusicCollection hinzu
        [myMusic addSongToLibrary:Song1];
        [myMusic addSongToLibrary:Song2];
        [myMusic addSongToLibrary:Song3];
        [myMusic addSongToLibrary:Song4];
        [myMusic addSongToLibrary:Song5];
       
        // Liste den Inhalt der masterList mit der MyPlaylist-Methode list
        [myMusic.masterList list];
       
        // Erzeuge Instanz test aus MyPlaylist-Klasse (direkt)
        MyPlaylist *test = [[MyPlaylist alloc] initWithName:@"Testbibliothek"];
       
        // Füge zwei Songs der MyPlaylist-Instanz test hinzu
        [test addSong:Song1];
        [test addSong:Song5];
       
        // Liste den Inhalt der test mit der MyPlaylist-Methode list
        [test list];
   
        // Definiere eine Variable depechemode des typs MyPlaylist
        MyPlaylist *depechemode;
       
        // Erzeuge eine Instanz "depechemode" der MyPlaylist-Klasse via MusicCollection-Methode createPlaylist
        [myMusic createPlaylist:depechemode andName:@"Depeche Mode"];
       
        for (MyPlaylist *element in myMusic.collection) {
            NSLog(@"%@", element.listName);
        }
        // Playlist depechemode wurde erzeugt und MyMusic hinzugefügt
        //******Bis hier funktioniert alles *******
       
       
        [depechemode addSong:Song1]; // funktioniert nicht!
        [depechemode addSong:Song2]; // funktioniert nicht!
        [depechemode addSong:Song3]; // funktioniert nicht!
       
        [depechemode list];// funktioniert nicht!
       
    }
    return 0;
}

MySong.h:
Code:
#import <Foundation/Foundation.h>

@interface MySong : NSObject

@property (nonatomic, copy) NSString *title, *artist, *album, *duration;

-(void) setTitle:(NSString *) theTitle andArtist: (NSString *) theArtist andAlbum: (NSString *) theAlbum andDuration: (NSString *) theDuration;
-(void) print;

-(BOOL) isEqual:(MySong *) theSong;

@end

MySong.m:
Code:
#import "MySong.h"

@implementation MySong

@synthesize title, artist, album, duration;

-(void) setTitle:(NSString *)theTitle andArtist:(NSString *)theArtist andAlbum:(NSString *)theAlbum andDuration:(NSString *)theDuration
{
    [self setTitle:theTitle];
    [self setArtist:theArtist];
    [self setAlbum:theAlbum];
    [self setDuration:theDuration];
}

-(void) print
{
    NSLog(@"=================================");
    NSLog(@"|  %-28s |", [title UTF8String]);
    NSLog(@"|  %-28s |", [artist UTF8String]);
    NSLog(@"|  %-28s |", [album UTF8String]);
    NSLog(@"|  %-28s |", [duration UTF8String]);
    NSLog(@"=================================");
}

-(BOOL) isEqual:(MySong *)theSong
{
    if ([title isEqualToString:theSong.title] == YES && [artist isEqualToString:theSong.artist ]==YES && [album isEqualToString:theSong.album] == YES && [duration isEqualToString:theSong.duration]==YES) {
        return YES;
    } else {
        return NO;
    }
}

@end

MyPlaylist.h:
Code:
#import <Foundation/Foundation.h>
#import "MySong.h"

@interface MyPlaylist : NSObject

@property (nonatomic, copy) NSString *listName;
@property (nonatomic, strong) NSMutableArray *playlist;

-(instancetype) initWithName: (NSString *) name;
-(instancetype) init;

-(void) addSong: (MySong *) theTitle;
-(void) list;

@end

MyPlaylist.m:
Code:
#import "MyPlaylist.h"

@implementation MyPlaylist

@synthesize listName, playlist;

-(instancetype) initWithName:(NSString *)name
{
    self = [super init];
   
    if (self) {
        listName = [NSString stringWithString:name];
        playlist = [NSMutableArray array];
    }
   
    return self;
}

-(instancetype) init
{
    return [self initWithName:@"No Name"];
}

-(void) addSong:(MySong *)theTitle
{
    [playlist addObject:theTitle];
    NSLog(@"Add Song %@ to %@", theTitle.title, self.listName);
}

-(void) list
{
    NSLog(@"============== Contents of %@ ==============", listName);
    [playlist enumerateObjectsUsingBlock:^(MySong *theSong, NSUInteger idx, BOOL *stop) {
        NSLog(@"  %-28s  %-14s", [theSong.title UTF8String], [theSong.artist UTF8String]);
    }];
    NSLog(@"=================================================");
}

@end

MusicCollection.h:
Code:
#import <Foundation/Foundation.h>
#import "MyPlaylist.h"
#import "MySong.h"

@interface MusicCollection : NSObject

@property (nonatomic, copy) NSString *collectionName;
@property (nonatomic, strong) NSMutableArray *collection;
@property (nonatomic, strong) MyPlaylist *masterList;

-(instancetype) initWithName: (NSString *) name;
-(instancetype) init;

-(void) createPlaylist: (MyPlaylist *) thePlaylist andName:(NSString*) name;
-(void) addSongToLibrary: (MySong *) theTitle;

@end

MusicCollection.m:
Code:
#import "MusicCollection.h"

@implementation MusicCollection

@synthesize collectionName, collection, masterList;

-(instancetype) initWithName:(NSString *)name
{
    self = [super init];
   
    if (self) {
        collectionName = [NSString stringWithString:name];
        collection = [NSMutableArray array];
        masterList = [[MyPlaylist alloc] initWithName:@"library"];
        [self.collection addObject:masterList];
    }
   
    return self;
}

-(instancetype) init
{
    return [self initWithName:@"No Name"];
}

-(void) createPlaylist:(MyPlaylist *) thePlaylist andName:(NSString *)name
{
    thePlaylist = [[MyPlaylist alloc] initWithName:name];
    [collection addObject:thePlaylist];
}

-(void) addSongToLibrary:(MySong *) theTitle
{
    [masterList.playlist addObject:theTitle];
}
@end

Viele Grüße

André
 
ARC räumt dir die Playlist direkt wieder aus dem Speicher und setzt den Zeiger auf nil.
Du kannst createPlaylist so umändern, dass die erstellt Playlist zurück gegeben wird, was das normale wäre.
also in etwa
Code:
 - (MyPlaylist*)playlistWithName:(NSString*)name;

oder du musst der Methode einen Zeiger auf einen Zeiger übergeben.
Code:
-(void) createPlaylist:(MyPlaylist **) thePlaylist andName:(NSString *)name {
    *thePlaylist = [[MyPlaylist alloc] initWithName:name];
    [collection addObject:*thePlaylist];
}

der Aufruf wäre dann so
Code:
[myMusic createPlaylist:&depechemode andName:@"Depeche Mode"];
 
  • Gefällt mir
Reaktionen: anhe
Hallo André,

Du erzeugst ja niergendwo "depechmode".
Lediglich als Parameter wird es übergeben.

Du müsstest irgendwo eine Zuweisung haben aka "depechmode = …".
Du kannst auch eine Variable "rückwärts" via Parameter befüllen.
D.h. Du müsstest den Zeiger mit & übergeben.
Also "…playlist:&depechmode …".

Das ist aber für diesen Anwendungsfall nicht ratsam.

Des Weiteren solltest Du lokale Variablen/Zeiger immer mit nil zuweisen.
"NSString *x = nil;"
Ohne zeigt das Ding nämlich irgendwohin.
Lediglich beim init ist garantiert, dass die Eigenschaften auf nil sind.

Gerade bei NSError-Zeigern lernt man das sehr schnell…

Viele Grüße
 
  • Gefällt mir
Reaktionen: anhe
Hallo pierredrks und little_pixel,

vielen Dank Euch beiden für Eure schnellen Antworten. Ich habe mich jeweils für Eure erste Lösung entschieden also:

Code:
-(MyPlaylist*) createPlaylistWithName:(NSString *)name
{
    MyPlaylist *thePlaylist = [[MyPlaylist alloc] initWithName:name];
    [collection addObject:thePlaylist];
    return thePlaylist;
}

und Aufruf in main.m mit:

Code:
        // Definiere eine Variable depechemode des typs MyPlaylist
        MyPlaylist *depechemode = nil;
       
        // Erzeuge eine Instanz "depechemode" der MyPlaylist-Klasse via MusicCollection-Methode createPlaylist
        depechemode = [myMusic createPlaylistWithName:@"Depeche Mode"];

jetzt funktioniert es. Ich habe es aber, glaube ich, nur halb verstanden warum. An welcher Stelle wurde die Playlist "depechemode" direkt wieder aus dem Speicher geräumt? Jetzt erzeuge ich innerhalb der Methode createPlaylistWithName eine Playlist und füge die dem Array hinzu. Innerhalb von main.m setze ich nur noch einen Zeiger auf diesen Eintrag im Array, korrekt? Dieser hatte vorher zunächst "irgendwohin" gezeigt und zeigt nun bis zur Zuweisung ins nil, richtig?

Vielen Dank noch mal und Euch ein schönes Wochenende

André
 
Was der Compiler aus deiner erste Version von createPlaylist macht sieht in etwa so aus :
Code:
-(void) createPlaylist:(MyPlaylist *) thePlaylist andName:(NSString *)name {
thePlaylist = [[MyPlaylist alloc] initWithName:name];
[collection addObject:thePlaylist];
//Hier ist die Methode fertig und thePlaylist läuft aus dem Scope, deshalb wird aufgeräumt
[thePlaylist release];
thePlaylist = nil;
}

Die neue Methode ist in etwa so:
Code:
-(MyPlaylist*) createPlaylistWithName:(NSString *)name
{
MyPlaylist *thePlaylist = [[[MyPlaylist alloc] initWithName:name] autorelease];
[collection addObject:thePlaylist];
return thePlaylist;
}

//Und danach
depechemode = [[myMusic createPlaylistWithName:@"Depeche Mode"] retain];

Da du das Ergebnis direkt der Variablen depecheMode zuweist und diese per default eine strong Referenz ist bleibt der Wert erhalten. Hat also erstmal nichts damit zu tun, dass du die Playlist einer collection hinzufügst.
Deine erste Version wird übrigens üblicherweise genutzt wenn du neben einem Rückgabewert noch einen weiteren Wert zurück geben möchtest, oft gibt eine Methode BOOL zurück und hat dann noch einen NSError der über einen Parameter zurück gegeben wird. Ich persönlich mag das nicht besonders und finde das auch bei den Cocoa (Touch) Methoden eher schlecht.
 
  • Gefällt mir
Reaktionen: anhe
Hallo nochmal pierredrks,

vielen Dank für Deine Ausführungen.

Viele Grüße

André
 
Zurück
Oben Unten