Treballar amb els errors

Arriba un moment, quan una aplicació o llibreria agafa dimensions considerables, que cal començar a plantejar-se com tractar amb els erros que es produeixen de forma estructurada i controlada.

En aquest tema miraré de descriure les conclusions que vagi traient de la documentació d'Apple i com miraré d'implementar-ho.

Aquest és l'enllaç de la documentació d'Apple:
- http://developer.apple.com/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/Error...

Xin  sep  30 juny 2009 09:22

La classe NSError és la classe base encarregada de gestionar els error en la programació Cocoa. És millor que els errors basats en codis numèrics o en cadenes de text, ja que permet incloure-hi més informació com cadenes localitzades, els objectes que han produït l'error i també poden copiar-se i desar-se.

Un NSError es forma a partir d'un domini de l'error, un codi i una informació associada.

A continuació en parlarem més a fons.

Xin  sep  30 juny 2009 09:35

El domini de l'error serveix per situar l'error dins d'un àmbit de manera que cada domini pugui generar els seus propis codis d'error sense por que ja estiguin repetits.

El domini de l'error hauria de ser formalment una cadena amb format invers i acabat amb ErrorDomain. Un exemple podria ser:
- cat.empresa.framework.ErrorDomain
- cat.empresa.aplicacio.ErrorDomain

Es pot crear més d'un domini d'error per framework o aplicació, però amb la idea que tinguin relació i que puguin identificar amb certa facilitat de quina aplicació/framework prové l'error.

Dins l'aplicació o la framework és bo crear un fitxer amb els codis dels errors i una petita descripció per tenir-los presents i documentats. Aquests són els primers errors POSIX:

#define EPERM       1       /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */

A partir del domini de l'error i el codi, el programa pot tractar l'error de forma específica o simplement mostrar-lo en una finestra i cancel·lar el que l'ha causat.

Xin  sep  30 juny 2009 09:43

El punt més interessant i que ja té molta funcionalitat implementada és la informació associada a l'error. Aquesta informació és un diccionari on hi podem ficar el que hi vulguem, però que ja hi ha uns elements pre-definits que poden facilitar-ne el seu ús o com a mínim unificar-lo amb la resta de Cocoa.

El primer element pre-definit utilitza la clau "NSLocalizedDescriptionKey" i es pot obtenir amb el mètode "localizedDescription". Aquest conté la descripció principal de l'error que apareix amb un tipus de lletra gran i en negreta. Normalment inclou una petita descripció de l'error produït i la raó de l'error (p.e: "El fitxer no s'ha pogut desar perquè el disc és ple").

El segon element pre-definit utilitza la clau "NSLocalizedFailureReasonErrorKey" i es pot obtenir amb el mètode "localizedFailureReason". Aquest conté només el motiu de l'error (p.e: "El disc és ple"). És útil si només es vol mostrar el motiu de l'error.

El tercer element pre-definit utilitza la clau "NSLocalizedRecoverySuggestionErrorKey" i es pot obtenir amb el mètode "localizedRecoverySuggestion". Aquest conté un text traduït on s'explica que pot fer l'usuari per solucionar l'error. Acostuma a aparèixer amb un tipus de lletra normal. Aquesta informació pot ser merament descriptiva o per a que l'usuari escolleixi una opció. Si aquesta recomanació fa referència als botons del missatge d'alerta, cal que utilitzi els mateixos títols de les opcions que se li presenten.

Per últim hi ha les opcions de recuperació que utilitzen la clau "NSLocalizedRecoveryOptionsErrorKey" i que es poden obtenir amb el mètode "localizedRecoveryOptions". Aquest element ha de contenir un array amb els textos traduïts de les opcions disponibles per reparar l'error. L'ordre dels textos dins de la finestra d'alerta va de la dreta a l'esquerra. Si no es defineix cap opció, simplement el missatge d'alerta només mostrarà el botó "OK".

Després ja parlarem de la resta d'elements pre-definits, de com mostrar els errors i com utilitzar les opcions de recuperació de forma senzilla.

Xin  sep  30 juny 2009 10:04

Amb el que hem parlat fins ara ja podem mostrar una finestra de l'error informativa a l'usuari.

imatge

Ho podem fer de diverses formes:

- La primera seria crear un panell d'alerta amb un simple mètode de classe i posteriorment mostrar-lo com més ens interessés: amb una finestra modal esperant l'acció de l'usuari o amb una pestanya modal:

NSAlert *alert = [NSAlert alertWithError:error];
[alert runModal];
[alert beginSheetModalForWindow: modalDelegate: didEndSelector: contextInfo:]

- La segona forma seria cridar el mètode "presentError:" des de qualsevol objecte "NSResponder" (finestres, documents, …). Aquest mètode mostra l'error i si permet recuperar-lo (explicat posteriorment) ho intenta retornant si s'ha recuperat o no.

Aquest mètode permet personalitzar el propi error sobre-escribint el mètode "willPresentError:" de manera que es pot oferir informació més personalitzada de la que conté. La idea és que a partir del domini de l'error i el codi es personalitzi els textos traduïts, ja sigui perquè els que hi ha no són prou descriptius o perquè simplement no n'hi ha cap.

Xin  sep  30 juny 2009 10:19

Arribats a aquest punt acabarem de descriure la resta d'elements pre-definits dels quals en parlarem posteriorment.

- El primer dels nous elements pre-definits es desa en la clau "NSRecoveryAttempterErrorKey" que conté un objecte que intenta fer recuperacions. Aquest objecte pot ser qualsevol objecte que implemente el protocol "NSErrorRecoveryAttempting" que implementa els següents mètodes per intentar recuperar un error:

- (BOOL)attemptRecoveryFromError:(NSError *)error optionIndex:(NSUInteger)recoveryOptionIndex

- (void)attemptRecoveryFromError:(NSError *)error optionIndex:(NSUInteger)recoveryOptionIndex delegate:(id)delegate didRecoverSelector:(SEL)didRecoverSelector contextInfo:(void *)contextInfo
On s'envia l'error i el número de l'opció que ha escollit l'usuari.

- Un altre objecte pre-definit queda en la clau "NSUnderlyingErrorKey" i permet incloure l'error anterior que ha generat aquest. Així si una operació ha generat un error, i a partir d'aquest n'heu generat un de nou sense tractar el primer, el podeu incloure dins de la informació. Així més amunt els pot mostrar tot el reguitzell d'errors per si es vol tenir més informació.

- En principi no queden més objectes pre-definits genèricament. Però cada domini pot tenir els seus. És a dir, que vosaltres podeu ficar-hi més informació que pugui interessar a qui gestiona l'error. Algunes claus de Cocoa són: NSStringEncodingErrorKey, NSURLErrorKey i NSFilePathErrorKey.

Xin  sep  30 juny 2009 10:43

Un cop hem vist com és un NSError, anem a veure com crear-los i utilitzar-los.

La majoria dels mètodes de frameworks contenen en l'últim paràmetre una referència indirecta a un error. Per exemple aquest:

- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError;

Normalment aquests mètodes o retornen el resultat de l'operació (YES o NO) o l'objecte resultant o nil. Amb aquesta informació podem veure si hi ha hagut un error, analitzar-lo, tractar-lo i/o mostrar-lo.

La forma d'obtenir l'error és enviant la referència d'un objecte error. Si hi indiquem NULL, vol dir que no ens interessa l'error.

NSError *theError;
BOOL success = [myDoc writeToURL:[self docURL]
ofType:@"html"
error:&theError];
if (success == NO) {
// maybe try to determine cause of error and recover first
NSAlert *theAlert = [NSAlert alertWithError:theError];
[theAlert runModal]; // ignore return value
}

Podeu veure que hem de tenir un objecte error sense inicialitzar on es crearà l'error si existeix. Penseu que l'error el crea el mètode que cridem, així que ja ve auto-alliberat i que si volem guardar-lo l'haurem de retenir. Si sou vosaltres qui creeu un error enviat com a paràmetre no us oblideu d'auto-alliberar-lo (autorelese).

En aquest exemple, simplement es mostra l'error a l'usuari per continuar sense fer-hi res. Però nosaltres podem implementar diferents formes d'actuar. Com per exemple:
- Fer alguna altra cosa sense notificar-ho a l'usuari
- Si saps com pots recuperar l'error pots crear un nou error amb opcions de recuperació (si aquest no les té) i demanar permís a l'usuari.
- Pots afegir nova informació sobre l'error relativa al teu àmbit.
- Crear un nou error i enviar-lo més amunt

Cal fer esment que mai utilitzeu els textos traduïts com a base per tractar els errors. L'únic vàlid és utilitzar el domini i el codi de l'error.

També tingueu present com tractar els errors desconeguts de forma que l'usuari en pugui tenir constància i posteriorment podeu tractar-los específicament si cal.

I recordeu que els errors també poden enviar-se en mètodes delegats com a paràmetres.

Xin  sep  30 juny 2009 11:09

Ja hem explicat anteriorment com mostrar els errors. Anem a aprofundir-hi.

La forma més senzilla, és el mètode "alertWithError:" de la classe NSAlert, que un cop mostrada retornarà el codi del botó premut.

NSAlert *theAlert = [NSAlert alertWithError:theError];
int button = [theAlert runModal];
if (button != NSAlertFirstButtonReturn) {
// handle
}
Depenent del botó premut, posterioment podem mirar de recuperar l'error o fer el que creguem oportú.

La forma més complexa o flexible és utilitzant els mètodes de les classes "NSResponder" (presentError: i presentError:modalForWindow:delegate:didPresentSelector:contextInfo:). Aquests mètodes gestionen la recuperació de l'error automàticament i en retornen el resultat. Anem a veure-ho amb més detall.

NSError *theError;
NSData *theData = [doc dataOfType:@"xml" error:&theError];
if (!theData && theError)
[anyView presentError:theError
modalForWindow:[doc windowForSheet]
delegate:self
didPresentSelector:
@selector(didPresentErrorWithRecovery:contextInfo:)
contextInfo:nil];
En aquest exemple a partir de la vista (finestra o aplicació) anyView presentem l'error modal a una finestra de manera que un cop acabi (intentant recuperar l'error si s'escau) ens cridi el mètode (didPresentErrorWithRecovery:contextInfo) on el primer paràmetre indica si s'ha pogut recuperar l'error o no i el segon és la mateixa informació enviada per si ens cal.

Si utilitzem el mètode simple (presentError:) el propi mètode ens retornaria el resultat de la recuperació i podríem actuar en conseqüència.

Xin  sep  30 juny 2009 11:16

Si sou vosaltres que voleu implementar el retorn d'un error en un paràmetre d'un mètode heu de seguir aquestes directives. Tingueu en compte a afegir els textos traduïts amb la informació pertinent tot i que aquí no es tradueixen.

- (BOOL) executeSomeThingError:(NSError*)tErr;
{
// Fer alguna cosa que por no funcionar
if ( !doSomeThing() )
{
if ( tErr != NULL )
{
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
@"Error at do something because disc is full", NSLocalizedDescriptionKey,
@"Disc is full", NSLocalizedFailureReasonErrorKey,
@"Burn the computer", NSLocalizedRecoverySuggestionErrorKey,
nil];
*tErr = [[[NSError alloc] initWithDomain:@"cat.company.application.ErrorDomain"
code:12 userInfo:info] autorelease];
}
return NO;
}

return YES;
}

Fixeu-vos que a l'hora de crear l'error utilitzem la forma (*tErr) ja que "tErr" només és la referència a l'error (l'error és *tErr). També fixeu-vos que com que som nosaltres els que hem creat l'error, som nosaltres els errors d'alliberar-lo, per això fem un "autorelease" (vigileu no fos cas que tinguéssiu un "NSAutoreleasePool" en aquest mètode.

Xin  sep  30 juny 2009 11:27

Pels que no estem acostumats a treballar amb errors i només ho fem a excepcions ens pot resultar confús veure'n la diferència.

Pel que diu Apple és important veure-la, ja que els errors i les excepcions serveixen per coses diferents.

Les excepcions (NSException) són errors de programació, com quan s'intenta accedir a un element d'un array que no existeix. En canvi un error (NSError) són errors de funcionament, com quan no es pot trobar un fitxer o no es pot llegir una cadena en una codificació.

Resumint podríem dir que els errors estan previstos que passin i cal tractar-les, mentre que les excepcions s'han d'eliminar, o si més no evitar, abans de ficar l'aplicació en producció.

Xin  sep  30 juny 2009 12:00

Anem a explicar una mica com funciona la personalització en la presentació dels errors.

Quan cridem el mètode "presentError:", o algun dels seus derivats, d'una vista el que estem és creant una cadena d'errors que poden arribar fina a l'aplicació que és qui ens la mostrarà. En aquest recorregut, les diferents classes poden modificar l'aparença de l'error, ja sigui per canviar-ne els textos o per proposar solucions de recuperació.

La cadena d'errors és com mostren aquestes imatges:

imatgeimatgeimatge

Cada implementació de cada classe de la cadena pot modificar l'aspecte de l'error i al final el delegat de l'aplicació també ho pot fer (així no cal crear una classe d'aplicació).

És clar que per modificar-lo cal conèixer el tipus d'error i actuar correctament. Per exemple, si tenim un error que impedeix escriure en un fitxer perquè està bloquejat, podem oferir la possibilitat de recuperació i desbloquejar-lo. O si la descripció de l'error no és prou clara, podem mirar de canviar-la.

És tant senzill com implementar el mètode "willPresentError:" o "application: willPresentError:" creant un nou error a partir de l'anterior canviant-li el que creguem convenient.

Un exemple d'Apple on canvia la descripció d'un error NSFileLockingError

- (NSError *)willPresentError:(NSError *)error {
if ([[error domain] isEqualToString:NSCocoaErrorDomain]) {

switch([error code]) {
case NSFileLockingError:
case NSFileReadNoSuchFileError:
{
NSString *locFailure = [error localizedFailureReason];
if (locFailure) {
NSMutableDictionary *newUserInfo = [NSMutableDictionary dictionaryWithCapacity:[[[error userInfo] allKeys] count]];
[newUserInfo setDictionary:[error userInfo]];
NSString *errorDesc = [NSString stringWithFormat: NSLocalizedString(@"MyGreatApp cannot open the file. %@", @""), locFailure];
[newUserInfo setObject:errorDesc forKey:NSLocalizedDescriptionKey];
NSError *newError = [NSError errorWithDomain:[error domain] code:[error code] userInfo:newUserInfo];
return newError;
} else {
return [super willPresentError:error];
}
}
default:
return [super willPresentError:error];
}
}
}

Xin  sep  30 juny 2009 12:28

Per acabar toca parlar del tema més complex. No des del punt de vista d'entendre'l, sinó com adaptar-lo a les nostres necessitats.

Crear un recuperador d'error és senzill, simplement s'ha d'implementar el mètode indicat del protocol "NSErrorRecoveryAttempting" depenent si és una recuperació amb retorn directe o no. La primer retorna el resultat un cop acabat i la segona ha de cridar al mètode indicat enviant-li el resultat. Eren aquestes:

- (BOOL)attemptRecoveryFromError:(NSError *)error optionIndex:(NSUInteger)recoveryOptionIndex
- (void)attemptRecoveryFromError:(NSError *)error optionIndex:(NSUInteger)recoveryOptionIndex delegate:(id)delegate didRecoverSelector:(SEL)didRecoverSelector contextInfo:(void *)contextInfo

Però per utilitzar-ho cal tenir present algunes coses, com si ens realitzarà la recuperació automàticament o l'haurem de demanar nosaltres.

1.-Si utilitzem un NSAlert la recuperació de l'error no es farà automàticament. Sinó que ens retornarà el número del botó que hem premut i serem nosaltres qui haurem de cridar el recuperador.

2.- Si utilitzem el "presentError:", ens farà la recuperació de forma automàtica si així ho hem definit i a més ens permetrà adaptar l'error al nostre programa.

Una altra cosa que cal tenir present és si el flux del programa s'aturarà o continuarà. Això és molt important, ja que si tenim un procés que ha de fer diverses coses que poden generar diversos errors, cal tenir present com codificar la reparació d'errors dins d'aquest flux d'operacions.

1.- Si utilitzem els mètodes [NSAlert runModal] o el [NSResponder presentError:] el flux del programa s'aturarà fins que hagem acceptat l'alerta i podrem continuar en l'execució del procés depenent del resultat. Com hem dit en el punt anterior, l'NSAlert ens retornarà el botó premut i serem nosaltres els qui haurem de cridar la reparació de l'error, i el NSResponder ens el recuperarà automàticament retornant-ne el resultat.

2.- Si utilitzem els mètodes "[NSAlert beginSheetModalForWindow: modalDelegate: didEndSelector: contextInfo:]" o el "[NSResponder presentError:modalForWindow:delegate:didPresentSelector:contextInfo:]" el flux del programa continuarà sense esperar que es recuperi l'error. Així, en aquest cas, caldrà ometre la resta del flux del procés per a que no ens faci malbé res.

Xin  sep  30 juny 2009 16:27

Anem a veure uns casos:

1.- Si ens ho volem fer nosaltres amb un NSAlert ho podríem ficar tot en un mateix mètode. De manera que per cada possible error, puguem actuar com més ens convingui. Així:

- (BOOL) process;
{
// Variables
NSAlert *alert;
NSError *err;
BOOL result;

// Tasca 1
result = [filemanager copyFilePath:@"file1.txt" toPath:@"file2.txt" error:&err];
if ( !result )
{
// Mostra l'error, intenta recuperar i finalitza si no pot
alert = [NSAlert alertWithError:err];
switch ( [alert runModal] )
{
case 0:
// Do something
break;
case 1:
// Do something other
break;
default:
// Error => show definitive error and exit
return NO;
}
}

// Tasca 2
result = [filemanager copyFilePath:@"file2.txt" toPath:@"file1.txt" error:&err];
if ( !result )
{
// Mostra l'error, intenta recuperar i finalitza si no pot
alert = [NSAlert alertWithError:err];
switch ( [alert runModal] )
{
case 0:
// Do something
break;
case 1:
// Do something other
break;
default:
// Error => show definitive error and exit
return NO;
}
}

return YES;
}

2.- L'altra opció és fer-ho també aturant el procés però que la recuperació se n'encarregui el "NSRecoveryAttempterErrorKey" que conté el propi error. D'aquesta manera, no hem d'aturar el procés i és un altre mètode (potser d'un altre objecte) qui recuperar l'error. Aquesta forma té la limitació que no podem treballar amb variables locals del mètode, excepte que les adjuntem a la informació de l'error. Així:

- (BOOL) process;
{
// Variables
NSFileManager *filemanager = [NSFileManager defaultManager];
NSError *err;

// Tasca 1 comprovant l'error
if ( ![filemanager copyFilePath:@"file1.txt" toPath:@"file2.txt" error:&err] )
{
// Si hi ha error s'afegeix les variables necessaries per reparar
[[err info] setObject:filemanager forKey:@"manager"];
// Si no es repara sortim
if ( ![NSApp presentError:err] )
return NO;
}

// Tasca 2
if ( ![filemanager copyFilePath:@"file2.txt" toPath:@"file1.txt" error:&err] )
{
// Si hi ha error s'afegeix les variables necessaries per reparar
[[err info] setObject:filemanager forKey:@"manager"];
// Si no es repara sortim
if ( ![NSApp presentError:err] )
return NO;
}

return YES;
}
- (BOOL) attemptRecoveryFromError:(NSError*)tErr optionIndex:(NSUInteger)tIdx;
{
// Comprovem el domini de l'error, el codi i l'opció. Si en gestiona més d'un
if ( [[tErr domain] isEqual:@"cat.xin.app.ErrorDomain"] )
{
// Comprovem el codi de l'error
switch ( [tErr code] )
{
case 0: // Error 0
// Recuperem la variable local
NSFileManager *manager = [[tErr info] objectForKey:@"manager"];

// Comprovem l'opció de recuperació de l'error
switch ( tIdx )
{
case 0: // Option 0 for recovery error
case 1: // Option 1 for recovery error
default: // Cancel recovery error
return NO;
}
break;
default: // Unknown error
return NO;
break;
}
}
return YES;
}

Xin  sep  30 juny 2009 16:46

Un cas més:

Si volem treballar sense aturar el procés un cop mostrar l'error, el millor és adaptar la crida de cada tasca en mètodes diferents que es vagin cridant consecutivament quan aquests acaben. Així si el primer mètode no té cap error crida el segon i així consecutivament. Si un dels mètodes té un error, es presenta l'error i finalitza el procés, quan l'usuari realitzi l'acció de recuperació de l'error, el mètode que realitza la recuperació pot continuar el procés simplement executant el següent mètode previst que es pot passar com a paràmetre i fins i tot pot tenir la mateix signatura que el selector escollit. És clar que pot complicar les coses si s'han d'enviar dades entre els mètodes, però amb el contextInfo es pot fer. Així:

- (void) process;
{
// Variables
NSFileManager *filemanager = [NSFileManager defaultManager];
NSDictionary *info = [NSDictionary dictionaryWithObject:filemanager forKey:@"manager"];

// Fa la primra tasca
return [self taskOneWithInfo:info];
}
- (void) taskOneWithInfo:(NSDictionary*)tInfo;
{
NSFileManager *filemanager = [tInfo objectForKey:@"manager"];
NSAlert *alert;
NSError *err;

if ( ![filemanager copyFilePath:@"file1.txt" toPath:@"file2.txt" error:&err] )
{
// Mostrem l'error
[NSApp presentError:err
modalForWindow:[NSApp mainWindow];
ondelegate:self
didPresentSelector:@selector(didPresentErrorTaskOneWithRecovery:contextInfo:)
contextInfo:tInfo]
}

return [self taskTwoWithInfo:tInfo];
}
- (void) didPresentErrorTaskOneWithRecovery:(BOOL)tRcv contextInfo:(void*)tInfo;
{
// Si s'ha recuperat continua amb la tasca 2
if ( tRcv ) [self taskTwoWithInfo:tInfo];

// Si no, mostrem una alerta adient i acabem
else NSRunAlertPanel(@"Error Task 1", @"Message task 1", nil, nil, nil);
}
- (void) taskTwoWithInfo:(NSDictionary*)tInfo;
{
NSFileManager *filemanager = [tInfo objectForKey:@"manager"];
NSAlert *alert;
NSError *err;

if ( ![filemanager copyFilePath:@"file2.txt" toPath:@"file1.txt" error:&err] )
{
// Mostrem l'error
[NSApp presentError:err
modalForWindow:[NSApp mainWindow];
ondelegate:self
didPresentSelector:@selector(didPresentErrorTaskTwoWithRecovery:contextInfo:)
contextInfo:tInfo]
}

return YES;
}
- (void) didPresentErrorTaskTwoWithRecovery:(BOOL)tRcv contextInfo:(void*)tInfo;
{
// Si s'ha recuperat continua amb la tasca 3 (si existeix)
if ( tRcv ) [self taskThreeWithInfo:tInfo];

// Si no, mostrem una alerta adient i acabem
else NSRunAlertPanel(@"Error Task 2", @"Message task 1", nil, nil, nil);
}
- (void) attemptRecoveryFromError:(NSError*)tErr optionIndex:(NSUInteger)tIdx delegate:(id)tDlg didRecoverSelector:(SEL)tSel contextInfo:(void*)tInfo;
{
BOOL recovered = NO;
// Comprovem el domini de l'error, el codi i l'opció. Si en gestiona més d'un
if ( [[tErr domain] isEqual:@"cat.xin.app.ErrorDomain"] )
{
// Comprovem el codi de l'error
switch ( [tErr code] )
{
case 0: // Error 0
// Recuperem la variable local
NSFileManager *manager = [[tErr info] objectForKey:@"manager"];

// Comprovem l'opció de recuperació de l'error
switch ( tIdx )
{
case 0: // Option 0 for recovery error
case 1: // Option 1 for recovery error
recovered = YES;
}
break;
}
}

// Informem si s'ha recuperat l'error
NSInvocation *invoke = [NSInvocation invocationWithMethodSignature:[tDlg methodSignatureForSelector:tSel]];
[invoke setSelector:didRecoverSelector];
[invoke setArgument:(void*)&recovered atIndex:2];
[invoke setArgument:(void*)&tInfo atIndex:3];

[invoke invokeWithTarget:tDlg];
}

És una mica complex, però és l'única forma decent que he trobat per fer-ho. I si ens interessa mostrar un sheet penjada d'una finestra en comptes d'un missatge d'alerta, cal fer-ho així.

Xin  sep  30 juny 2009 16:57

Per acabar un mostro el codi base per si voleu provar-ho en una aplicació. No es realitza cap tasca, simplement es mostren els errors de les diferents formes, per veure com funcionen. Si voleu provar de realitzar aquestes tasques ho podeu fer afegint més botons que executin aquests mètodes.

En aquest codi, l'acció és la mateixa pels 4 botons, i a dins es comprova el "tag" del boto que executa l'acció (1, 2, 3, 4) per decidir quin mètode utilitzar.

La interfície és així:

imatge

I el codi és aquest:

#import <Cocoa/Cocoa.h>


@interface AppErrCtrl : NSObject
{
// ImageTextCell variables
IBOutlet NSWindow *window;
IBOutlet NSTextField *_errDesc;
IBOutlet NSTextField *_errReason;
IBOutlet NSTextField *_errSuggest;
IBOutlet NSTextField *_errButtons;
IBOutlet NSButton *_errDescEnable;
IBOutlet NSButton *_errReasonEnable;
IBOutlet NSButton *_errSuggestEnable;
IBOutlet NSButton *_errButtonsEnable;
}

- (IBAction) actionError:(id)tSndr;

@end

#import "AppErrCtrl.h"

@implementation AppErrCtrl

- (IBAction) actionError:(id)tSndr;
{
// Variables
NSString *msg = [_errDesc stringValue];
NSString *fail = [_errReason stringValue];
NSString *sug = [_errSuggest stringValue];
NSArray *but = [[_errButtons stringValue] componentsSeparatedByString:@","];
NSMutableDictionary *info = [NSMutableDictionary dictionary];
NSError *err;
NSAlert *alert;
unsigned option;
BOOL result;

if ( [_errDescEnable state] == NSOnState )
[info setObject:msg forKey:NSLocalizedDescriptionKey];
if ( [_errReasonEnable state] == NSOnState )
[info setObject:fail forKey:NSLocalizedFailureReasonErrorKey];
if ( [_errSuggestEnable state] == NSOnState )
[info setObject:sug forKey:NSLocalizedRecoverySuggestionErrorKey];
if ( [_errButtonsEnable state] == NSOnState )
[info setObject:but forKey:NSLocalizedRecoveryOptionsErrorKey];
if ( [_errButtonsEnable state] == NSOnState )
[info setObject:self forKey:NSRecoveryAttempterErrorKey];

err = [NSError errorWithDomain:@"cat.xin.xnct.ErrorDomain" code:1234 userInfo:info];
NSLog(@"Before show error");
switch ( [tSndr tag] )
{
case 1:
option = [[NSAlert alertWithError:err] runModal];
NSLog(@"Option: %d", option);
break;
case 2:
[[NSAlert alertWithError:err] beginSheetModalForWindow:window
modalDelegate:self
didEndSelector:@selector(didPresentErrorWithRecovery:contextInfo:)
contextInfo:nil];
break;
case 3:
result = [NSApp presentError:err];
NSLog(@"Result: %@", result ? @"YES" : @"NO");
break;
case 4:
[NSApp presentError:err
modalForWindow:window
delegate:self
didPresentSelector:@selector(didPresentErrorWithRecovery:contextInfo:)
contextInfo:nil];
break;
}
NSLog(@"After show error");
}
- (BOOL) attemptRecoveryFromError:(NSError*)tErr optionIndex:(unsigned)tIdx;
{
NSLog(@"attemptRecoveryFromError:optionIndex: %d",tIdx);
return YES;
}
- (void) attemptRecoveryFromError:(NSError*)tErr optionIndex:(unsigned)tIdx delegate:(id)tDlg didRecoverSelector:(SEL)tSel contextInfo:(void*)tInfo;
{
NSLog(@"attemptRecoveryFromError:optionIndex:delegate:didRecoverSelector:contextInfo: %d",tIdx);
return [tDlg didPresentErrorWithRecovery:NO contextInfo:nil];
}
- (void) didPresentErrorWithRecovery:(BOOL)tRcv contextInfo:(void*)tInfo;
{
NSLog(@"didPresentErrorWithRecovery:contextInfo: %@", tRcv ? @"YES" : @"NO");
}

@end

Xin  sep  30 juny 2009 17:08

Selecciona'l abans d'enviar el commentari