NSTableView, datasource et données en colonnes
kangourou38
Membre
Voilà , je débute en cocoa, mais pas en programmation, et je cherche quelques éclaircissements sur le dataSource d'un NSTableView.
Pour différentes raisons, mes données doivent impérativement organisées en 4 colonnes (pour l'instant) d'exactement 128 objets (par exemples des NSNumber). J'ai pensé à l'organisation suivante :
mes données sources sont un NSMutableDictionary dont chaque élément contiendra :
- un identifier NSString
- un NS(Mutable)Array qui contiendra les 128 pointeurs (pardon objets !) vers mes NSNumber.
Dans IB, j'ai sous-classé un NSMutableDictionary en MyDictionary, puis je l'ai instancié. J'ai crée un NStableView à 4 colonnes (dont j'ai nommé respectivement "col1",...,"col4" les identifiers de mes 4 NSTabColumn), puis j'ai connecté l'outlet dataSource de mon NSTableView vers mon instance.
j'ai ensuite implémenté les méthodes suivantes ci-dessous.
A l'execution, j'ai toujours un message d'erreur m'indiquant que -count ou -enumerator ne sont pas implémentés. Je ne sais pas pourquoi.
Sauriez-vous également de quelle façon le NSTableView communique avec son datasource ? est-ce uniquement avec les méthodes suivantes ??
- (void)tableView:setObjectValue:forTableColumn:row:
- (int)numberOfRowsinTableView:
- (id)tableView:objectValueForTableColumn:row:
Merci pour votre aide.
- (void)awakeFromNib
{
[ self addColumn : @col1 ] ;
[ self addColumn : @col2 ] ;
[ self addColumn : @col3 ] ;
[ self addColumn : @col4 ] ;
}
-(void)addColumn: (NSString*)nstr
{
NSNumber *number ;
NSMutableArray *array = [ NSMutableArray arrayWithCapacity: 128 ] ;
int i ;
for ( i = 0 ; i < 128 ; i++ )
{
number = [ NSNumber numberWithDouble: 0.0 ] ;
[ array addObject: number ] ;
// [ number release ] ; autorelease déjà fait ?
}
[ self setObject: array forKey: nstr ] ;
[ array release ] ;
}
-(int)numberOfRowsinTableView: (NSTableView*)the_table
{
return 128 ;
}
-(id)tableView:(NSTableView *)the_table objectValueForTableColumn:(NSTableColumn *)the_col row:(int)num_row
{
NSString *identifier = [ the_col identifier ] ;
NSArray *array = [ self objectForKey: identifier ] ;
return [ array objectAtIndex : num_row ] ;
}
- (void)tableView:(NSTableView *)the_table setObjectValue:(id)the_obj forTableColumn:(NSTableColumn *)the_col row:(int)num_row
{
NSString *identifier = [ the_col identifier ] ;
NSMutableArray *array = [ self objectForKey: identifier ] ;
[ array replaceObjectAtIndex: num_row withObject: the_obj ] ;
}
Pour différentes raisons, mes données doivent impérativement organisées en 4 colonnes (pour l'instant) d'exactement 128 objets (par exemples des NSNumber). J'ai pensé à l'organisation suivante :
mes données sources sont un NSMutableDictionary dont chaque élément contiendra :
- un identifier NSString
- un NS(Mutable)Array qui contiendra les 128 pointeurs (pardon objets !) vers mes NSNumber.
Dans IB, j'ai sous-classé un NSMutableDictionary en MyDictionary, puis je l'ai instancié. J'ai crée un NStableView à 4 colonnes (dont j'ai nommé respectivement "col1",...,"col4" les identifiers de mes 4 NSTabColumn), puis j'ai connecté l'outlet dataSource de mon NSTableView vers mon instance.
j'ai ensuite implémenté les méthodes suivantes ci-dessous.
A l'execution, j'ai toujours un message d'erreur m'indiquant que -count ou -enumerator ne sont pas implémentés. Je ne sais pas pourquoi.
Sauriez-vous également de quelle façon le NSTableView communique avec son datasource ? est-ce uniquement avec les méthodes suivantes ??
- (void)tableView:setObjectValue:forTableColumn:row:
- (int)numberOfRowsinTableView:
- (id)tableView:objectValueForTableColumn:row:
Merci pour votre aide.
- (void)awakeFromNib
{
[ self addColumn : @col1 ] ;
[ self addColumn : @col2 ] ;
[ self addColumn : @col3 ] ;
[ self addColumn : @col4 ] ;
}
-(void)addColumn: (NSString*)nstr
{
NSNumber *number ;
NSMutableArray *array = [ NSMutableArray arrayWithCapacity: 128 ] ;
int i ;
for ( i = 0 ; i < 128 ; i++ )
{
number = [ NSNumber numberWithDouble: 0.0 ] ;
[ array addObject: number ] ;
// [ number release ] ; autorelease déjà fait ?
}
[ self setObject: array forKey: nstr ] ;
[ array release ] ;
}
-(int)numberOfRowsinTableView: (NSTableView*)the_table
{
return 128 ;
}
-(id)tableView:(NSTableView *)the_table objectValueForTableColumn:(NSTableColumn *)the_col row:(int)num_row
{
NSString *identifier = [ the_col identifier ] ;
NSArray *array = [ self objectForKey: identifier ] ;
return [ array objectAtIndex : num_row ] ;
}
- (void)tableView:(NSTableView *)the_table setObjectValue:(id)the_obj forTableColumn:(NSTableColumn *)the_col row:(int)num_row
{
NSString *identifier = [ the_col identifier ] ;
NSMutableArray *array = [ self objectForKey: identifier ] ;
[ array replaceObjectAtIndex: num_row withObject: the_obj ] ;
}
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Quelques réponses :
Ton approche avec MyDictionary me semble un datasource tout à fait adapté.
C'est un premier signe que quelque chose ne se passe pas comme prévu : il y a une confusion d'objets, ce qui a généralement deux causes :
  -soit une de tes méthodes a renvoyé un mauvais objet (genre un NSNumber au lien d'un NSArray)
  -soit il y a une corruption de la mémoire due à une erreur dans ta gestion mémoire (et il y en a au moins une dans ton code ci-dessous)
La doc de NSTableView et NSTableDataSource indique quelles sont les méthodes requises et optionnelles pour un datasource (ainsi que pour le delegate).
Ce sont effectivement les trois seules méthodes requises pour un datasource
Si tu te poses la question, c'est qu'il te faut encore travailler un peu sur la gestion mémoire en Objective-C ! (Et oui, c'est dur à saisir au début). En bref:
Tu ne dois faire release que si tu as fait retain, copy, ou appelé un constructeur avec initxxx...
Donc là , l'autorelease est déjà fait.
Il y a donc une erreur ici : le array est déjà autoreleasé ! Il ne faut pas faire de release supplémentaire (ou alors, il faut changer [NSMutableArray arrayWithCapacity: 128 ] en [[NSMutableArray alloc] initWithCapacity:128]).
Cela résout-il ton problème ?
+
Chacha
[edit]
Je viens de voir, ce n'est pas
numberOfRowsinTableView
mais
numberOfRowsInTableView
[/edit]
[edit2]
Oh, je n'avais pas vu, c'est ton premier post : Bienvenue sur Objective-Cocoa !
[/edit2]
Apple déconseille fortement de sous-classer NSDictionary/NSMutableDictionary (ce sont des class-cluster).
Mais, si dans un accès de folie incontrôlée, tu décides de le faire, alors il faut ABSOLUMENT surcharger les méthodes count, objectForKey: et keyEnumerator. Visiblement, c'est ce qui fait planter ton appli.
Dans ton cas, je ne vois pas l'intérêt de faire ça.
Un bon vieux objet contrôleur (dérivant de NSObject) dans lequel tu implémentes les 3 méthodes datasource et dans lequel tu stockes dictionnaires et tableau pour le contenu de la table est suffisant.
.
Merci pour votre aide
Le système des retain et release est astucieux (j'avais d'alleurs implémenté un système semblable pour mon appli - GEMGraph pour les connaisseurs - lorsque je programmais sous GEM sur atari st). Une seule question qui me hante à propos des autorelease : QUAND ont-ils lieu ? Pas trop tôt, sinon le pointeur ne sert à rien, mais je l'espère pas trop tard car sinon, plein de mémoire gaspillée.
En fait, ce que je ne comprend pas, c'est pourquoi l'objective-C n'autorise pas à mettre des objets (ou leur pointeur) dans une pile plutôt que de faire systématiquement des allocations mémoire. Les autorelease ne seraient plus nécessaire !
Il y a certains intérêts à gérer la mémoire soi-même, par opposition par exemple au Garbage Collector de Java, qui est qu'on peut relâcher les objets dès qu'on sait qu'on n'en a plus besoin, et donc d'être plus efficace... et aussi moins lourd à l'execution.
Une autorelease pool est créée par Cocoa au début de chaque RunLoop et relâchée à chaque fin de RunLoop. Donc les objets qui sont "autoreleasés" sont relâchés à la fin de chaque RunLoop, ce qui répond à ta question
Il est aussi possible de créer et relâcher tes propres autoreleasepool. Ceci est utile justement quand tu as beaucoup d'allocations et d'objets autoreleasés à un moment dans ton code, en particulier sur une grosse boucle for ou while qui alloue de grandes quantités de donnnées (il suffit simplement de penser à une boucle qui alloue 3 ou 4 images autoreleasées, ça prend vite de la mémoire)
Donc dans ce cas de figure là on alloue une AutoreleasePool au début (et à l'intérieur) de la boucle et on le relâche à la fin. En le relachant, il se détruit, et détruit surtout tous les objets à qui on a envoyé un autorelease depuis sa création.
Donc en général c'est pas relâché trop tôt, et si tu as peur que ce soit relâché trop tard et gaspiller de la mémoire, tu peux créer des pools intermédiaires.
Quant à l'allocation totalement dynamique au lieu de dans une pile, c'est l'originalité de l'oBjective-C d'être totalement dynamique. Ca a des inconvénients, mais aussi pas mal d'avantages, donc bon.
Et pour savoir quelles sont les méthodes qui renvoient des objets autorelease et celles pour qui c'est à toi de gérer la mémoire, ben c'est pas trop dur à savoir : si c'est toi qui alloue explicitement la mémoire avec un alloc ou un copy c'est à toi de la relâcher avec un release. Et si tu fais des retain, il faut aussi les balancer avec des release.
Les constructeurs de commodité, qui ont leur nom commençant par le nom de la classe ([NSNumber numberWith...], [NSDictionary dictionaryWith...], [NSString stringWith...] etc), sont là exprès pour créer des objets temporaires, donc autoreleasés, et sont exactement équivalents à un alloc+init+autorelease, donc pour eux tu n'as pas à faire de release. Et pour tout les autres trucs, genre les objets retournés par des fonctions, ils sont autoreleasés, par convention, pour continuer à suivre la logique.
Bru a raison, j'ai dit une bêtise. En fait, j'avais lu de travers, je croyais que tu utilisais un dictionary contenant 4 array, mais je n'avais pas fait attention à l'héritage... mea culpa !
+
Chacha