Obiectiv-C în mod succint excepții și erori

În Obiectiv-C, există două tipuri de erori care pot apărea în timpul executării unui program. Neașteptat erorile sunt erori de programare "grave" care, de obicei, cauzează ieșirea din program prematură. Acestea sunt numite excepții, deoarece acestea reprezintă o condiție excepțională în programul dvs. Pe de altă parte, așteptat erorile apar în mod natural în cursul execuției unui program și pot fi folosite pentru a determina succesul unei operații. Acestea sunt denumite erori.

De asemenea, puteți aborda distincția dintre excepții și erori ca o diferență în publicul țintă. În general, excepțiile sunt utilizate pentru a informa programator despre ceva care a mers prost, în timp ce erorile sunt folosite pentru a informa utilizator că o acțiune solicitată nu a putut fi finalizată.

Controlați fluxul pentru excepții și erori

De exemplu, încercarea de a accesa un indice matrice care nu există este o excepție (o eroare de programator), în timp ce nereușind să deschideți un fișier este o eroare (o eroare de utilizator). În primul caz, ceva probabil a mers foarte prost în fluxul programului dvs. și ar trebui probabil închis la scurt timp după excepție. În cel de-al doilea, doriți să le spuneți utilizatorului că fișierul nu a putut fi deschis și, eventual, să ceară să încerce din nou acțiunea, dar nu există niciun motiv pentru care programul dvs. nu ar putea continua să funcționeze după eroare.


Excepție de manipulare

Principalul avantaj al capabilităților de gestionare a excepțiilor de la Obiectiv-C este capacitatea de a separa tratarea erorilor de la detectarea erorilor. Atunci când o parte din cod întâlnește o excepție, poate "arunca" -ul la cel mai apropiat bloc de manipulare a erorilor, care poate "capta" excepții specifice și să le gestioneze în mod corespunzător. Faptul că excepțiile pot fi aruncate din locații arbitrare elimină necesitatea de a verifica constant mesajele de succes sau de eșec din fiecare funcție implicată într-o anumită sarcină.

@încerca, @captură(), și @in cele din urma compilatoare sunt utilizate pentru a prinde și de a face față excepțiilor, și @arunca directiva este folosită pentru a le detecta. Dacă ați lucrat cu excepții în C #, aceste construcții de manipulare a excepțiilor ar trebui să vă fie cunoscute.

Este important de menționat că în Obiectiv-C, excepțiile sunt relativ lente. Ca urmare, utilizarea lor ar trebui să se limiteze la capturarea erorilor grave de programare - nu pentru fluxul de control de bază. Dacă încercați să determinați ce trebuie să faceți pe baza unui așteptat (de exemplu, dacă nu încărcați un fișier), consultați secțiunea Eroare Manipulare secțiune.

Clasa NSException

Excepțiile sunt reprezentate ca exemple ale NSException clasa sau o subclasă a acesteia. Aceasta este o modalitate convenabilă de a încapsula toate informațiile necesare asociate cu o excepție. Cele trei proprietăți care constituie o excepție sunt descrise după cum urmează:

  • Nume - Un exemplu de NSString care identifică în mod unic excepția.
  • motiv - Un exemplu de NSString conținând o descriere care poate fi citită de către om a excepției.
  • userinfo - Un exemplu de NSDictionary care conține informații specifice aplicației legate de excepție.

Cadrul Fundației definește mai multe constante care definesc numele de excepție "standard". Aceste șiruri de caractere pot fi folosite pentru a verifica tipul de excepție prins.

De asemenea, puteți utiliza funcția initWithName: Motivul: userinfo: metoda de inițializare pentru a crea noi obiecte de excepție cu propriile valori. Obiectele de excepție personalizate pot fi prinse și aruncate utilizând aceleași metode acoperite în secțiunile viitoare.

Generarea de excepții

Să începem să aruncăm o privire la comportamentul implicit de tratare a excepțiilor unui program. objectAtIndex: Metodă de NSArray este definit pentru a arunca un NSRangeException (o subclasă de NSException) atunci când încercați să accesați un index care nu există. Deci, dacă cereți 10lea element dintr-un matrice care are doar trei elemente, veți avea o excepție de experimentat cu:

#import  int main (int argc, const char * argv []) @autoreleasepool NSArray * echipaj = [NSArray arrayWithObjects: @ "Dave", @ "Heywood", @ "Frank", nil]; // Aceasta va arunca o excepție. NSLog (@ "% @", [crew objectAtIndex: 10]);  retur 0; 

Când se întâlnește o excepție necondiționată, Xcode oprește programul și vă îndreaptă spre linia care a provocat problema.

Anularea unui program din cauza unei excepții necondiționate

Apoi, vom învăța cum să prindem excepții și să împiedicăți încetarea programului.

Capturarea excepțiilor

Pentru a trata o excepție, orice cod care Mai rezultă că o excepție ar trebui să fie plasată în @încerca bloc. Apoi, puteți prinde excepții specifice folosind @captură() directivă. Dacă aveți nevoie să executați orice cod de menaj, puteți plasa opțional în a @in cele din urma bloc. Următorul exemplu prezintă toate cele trei directive de tratare a excepțiilor:

@try NSLog (@ "% @", [crew objectAtIndex: 10]);  @catch (excepție NSException *) NSLog (@ "A fost capturată o excepție"); // Vom ignora exclusiv excepția.  @finally NSLog (@ "Curățenie"); 

Acest lucru ar trebui să emită următoarele în consolă Xcode:

A fost o excepție! Nume: NSRangeException Motiv: *** - [__ NSArrayI objectAtIndex:]: index 10 dincolo de limite [0 ... 2] Curățare

Când programul întâlnește [echipaj objectAtIndex: 10] mesaj, aruncă o NSRangeException, care este prins în @captură() directivă. În interiorul @captură() bloc este cazul în care excepția este de fapt manipulate. În acest caz, afișăm doar un mesaj de eroare descriptiv, dar în majoritatea cazurilor, probabil că veți dori să scrieți un cod pentru a avea grijă de problemă.

Atunci când se întâlnește o excepție în @încerca bloc, programul sare pe cel corespunzător @captură() bloc, ceea ce înseamnă orice cod după excepția apărută nu va fi executată. Aceasta prezintă o problemă dacă @încerca blocul are nevoie de curățare (de ex. dacă a deschis un fișier, fișierul trebuie închis). @in cele din urma bloc rezolvă această problemă, deoarece este garantat pentru a fi executate indiferent dacă a avut loc o excepție. Acest lucru îl face locul perfect pentru a lega orice capete libere de la @încerca bloc.

Parantezele după @captură() vă permite să definiți tipul de excepție pe care încercați să o capturați. În acest caz, este un NSException, care este clasa excepțională standard. Dar, poate fi o excepție orice clasă - nu doar un NSException. De exemplu, următoarele @captură() directiva va gestiona un obiect generic:

@catch (id genericException)

Vom învăța cum să aruncăm instanțe NSException precum și obiecte generice în secțiunea următoare.

Aruncarea excepțiilor

Când descoperiți o condiție excepțională în codul dvs., creați o instanță de NSException și să o populați cu informațiile relevante. Apoi, îl aruncați folosind numele potrivit @arunca directivă, determinând cel mai apropiat @încerca/@captură bloc pentru a se ocupa de ea.

De exemplu, următorul exemplu definește o funcție pentru generarea de numere aleatorii între un interval specificat. Dacă apelantul trece un interval nevalid, funcția aruncă o eroare personalizată.

#import  int generateRandomInteger (int minim, int maxim) if (minimum> = maximum) // Crearea exceptiei. NSException * excepție = [NSException exceptionWithName: @ "RandomNumberIntervalException" motiv: @ "*** generateRandomInteger ():" "parametrul maxim nu este mai mare decât parametrul minim" userInfo: nil "; // Aruncați excepția. @rațineți excepția;  // Întoarceți un întreg aleatoare. returnează arc4random_uniform ((maxim - minim) + 1) + minim;  int principal (int argc, const char * argv []) @autoreleasepool int rezultat = 0; @try rezultat = generateRandomInteger (0, 10);  @catch (excepție NSException *) NSLog (@ "Problema !!! excepție capturată:% @", [nume excepție]);  NSLog (@ "Număr aleatoriu:% i", rezultat);  retur 0; 

Deoarece acest cod trece un interval valid (0, 10) la generateRandomInteger (), nu va avea o excepție pentru a prinde. Cu toate acestea, dacă schimbați intervalul la ceva de genul (0, -10), veți vedea @captură() blocați în acțiune. Aceasta este în esență ceea ce se întâmplă sub capotă atunci când clasele cadru se confruntă cu excepții (de ex NSRangeException crescut de NSArray).

De asemenea, este posibil să repetați excepțiile pe care le-ați prins deja. Acest lucru este util dacă doriți să fiți informat că a apărut o anumită excepție, dar nu doriți să o faceți în mod necesar. Ca o comoditate, puteți chiar să omiteți argumentul @arunca directivă:

@try rezultat = generateRandomInteger (0, -10);  @catch (excepție NSException *) NSLog (@ "Problema !!! excepție capturată:% @", [nume excepție]); // Reluați excepția curentă. @throw

Aceasta transmite excepția prinsă până la cel mai mare handler de cel mai înalt nivel, care este, în acest caz, procedura de tratare a excepțiilor de nivel superior. Aceasta ar trebui să afișeze ieșirea din partea noastră @captură() bloc, precum și implicit Se termină aplicația din cauza unei excepții ... mesaj, urmată de o ieșire bruscă.

@arunca directiva nu se limitează la NSException obiecte - poate arunca literalmente orice obiect. Următorul exemplu aruncă o NSNumber obiect în loc de o excepție normală. De asemenea, observați cum puteți viza obiecte diferite prin adăugarea mai multor elemente @captură() declarații după @încerca bloc:

#import  int generateRandomInteger (int minim, int maxim) if (minimum> = maximum) // Generați un număr utilizând intervalul "implicit". NSNumber * presupune = [NSNumber numberWithInt: generateRandomInteger (0, 10)]; // Aruncați numărul. @ gândiți-vă;  // Întoarceți un întreg aleatoare. returnează arc4random_uniform ((maxim - minim) + 1) + minim;  int principal (int argc, const char * argv []) @autoreleasepool int rezultat = 0; @try rezultat = generateRandomInteger (30, 10);  @catch (NSNumber * ghici) NSLog (@ "Avertisment: Interval implicit utilizat"); rezultat = [ghici intValue];  @catch (excepție NSException *) NSLog (@ "Problema !!! excepție capturată:% @", [nume excepție]);  NSLog (@ "Număr aleatoriu:% i", rezultat);  retur 0; 

În loc de a arunca o NSException obiect, generateRandomInteger () încearcă să genereze un nou număr între anumite limite "implicite". Exemplul vă arată cum @arunca poate lucra cu diferite tipuri de obiecte, dar, strict vorbind, acest lucru nu este cel mai bun design al aplicațiilor, nici nu este cea mai eficientă utilizare a instrumentelor de tratare a excepțiilor ale obiectivului C. Dacă într-adevăr plănuiați să utilizați valoarea aruncată, așa cum face codul anterior, ați fi mai bine cu un simplu test condițional vechi folosind NSError, după cum se discută în secțiunea următoare.

În plus, unele dintre cadrele de bază Apple aştepta un NSException obiect pentru a fi aruncat, așa că aveți grijă cu obiectele personalizate când integrați cu bibliotecile standard.


Eroare de manipulare

În timp ce excepțiile sunt menite să permită programatorilor să știe când lucrurile au mers greșit, erorile sunt concepute astfel încât să fie o modalitate eficientă și directă de a verifica dacă o acțiune a reușit sau nu. Spre deosebire de excepții, erori sunteți concepute pentru a fi utilizate în declarațiile dvs. de control de zi cu zi.

Clasa NSError

Singurul lucru care au în comun erori și excepții este că ambele sunt implementate ca obiecte. NSError clasa încapsulează toate informațiile necesare pentru reprezentarea erorilor:

  • cod - Un NSInteger care reprezintă identificatorul unic al erorii.
  • domeniu - Un exemplu de NSString definirea domeniului pentru eroare (descris în detaliu în secțiunea următoare).
  • userinfo - Un exemplu de NSDictionary care conține informații specifice aplicației legate de eroare. Acest lucru este folosit de obicei mult mai mult decât userinfo dicționar de NSException.

În plus față de aceste atribute de bază, NSError stochează, de asemenea, mai multe valori concepute pentru a ajuta la redarea și prelucrarea erorilor. Toate acestea sunt, de fapt, scurtături în userinfo dicționar descris în lista precedentă.

  • localizedDescription - Un NSString care conține descrierea completă a erorii, care include de obicei motivul eșecului. Această valoare este de obicei afișată utilizatorului în panoul de alertă.
  • localizedFailureReason - Un NSString conținând o descriere autonomă a motivului erorii. Acest lucru este utilizat numai de clienții care doresc să izoleze motivul erorii de la descrierea completă.
  • recoverySuggestion - Un NSString instruind utilizatorul cum să recupereze din eroare.
  • localizedRecoveryOptions - Un NSArray din titlurile utilizate pentru butoanele dialogului de eroare. Dacă această matrice este goală, un singur O.K este afișat butonul pentru a respinge avertizarea.
  • helpAnchor - Un NSString pentru a afișa când utilizatorul apasă Ajutor ancora într-un panou de alertă.

Ca și în cazul NSException, initWithDomain: Cod: userinfo metoda poate fi folosită pentru a inițializa personalizat NSError instanțe.

Eroare Domenii

Un domeniu de eroare este ca un spațiu de nume pentru codurile de eroare. Codurile trebuie să fie unice într-un singur domeniu, dar se pot suprapune cu codurile din alte domenii. În plus față de prevenirea coliziunilor cu coduri, domeniile oferă, de asemenea, informații despre unde provine eroarea. Cele patru domenii principale de eroare încorporate sunt: NSMachErrorDomain, NSPOSIXErrorDomain, NSOSStatusErrorDomain, și NSCocoaErrorDomain. NSCocoaErrorDomain conține codurile de eroare pentru multe dintre cadrele standard ale obiectivului C ale Apple; totuși, există câteva cadre care definesc propriile domenii (de ex., NSXMLParserErrorDomain).

Dacă trebuie să creați coduri de eroare personalizate pentru bibliotecile și aplicațiile dvs., trebuie să le adăugați întotdeauna al tau domeniu de eroare - nu extindeți niciodată nici unul dintre domeniile încorporate. Crearea propriului domeniu este un loc de muncă relativ banal. Deoarece domeniile sunt doar șiruri de caractere, tot ce trebuie să faceți este să definiți o constantă de șir, care nu intră în conflict cu nici unul dintre celelalte domenii de eroare din aplicația dvs. Apple sugerează că domeniile iau forma com...ErrorDomain.

Capturarea erorilor

Nu există construcții de limbaj dedicate pentru manipulare NSError (deși mai multe clase încorporate sunt concepute să le gestioneze). Acestea sunt concepute pentru a fi utilizate în combinație cu funcții special proiectate care returnează un obiect atunci când acestea reușesc și zero când nu reușesc. Procedura generală de captare a erorilor este următoarea:

  1. Declarați un NSError variabil. Nu este nevoie să îl alocați sau să-l inițializați.
  2. Treceți acea variabilă ca un indicator dublu la o funcție care Mai duce la o eroare. Dacă ceva nu merge bine, funcția va folosi această referință pentru a înregistra informații despre eroare.
  3. Verificați valoarea returnată a acelei funcții pentru succes sau eșec. Dacă operația a eșuat, puteți utiliza NSError pentru a rezolva singur eroarea sau a le afișa utilizatorului.

După cum puteți vedea, o funcție nu este obișnuită întoarcere un NSError obiect - returnează orice valoare, dacă ar reuși, altfel se întoarce zero. Trebuie să utilizați întotdeauna valoarea returnată a unei funcții pentru a detecta erorile - nu utilizați niciodată prezența sau absența unei funcții NSError obiect pentru a verifica dacă o acțiune a reușit. Eroarea obiectelor trebuie doar să descrie o eroare potențială, nu să vă spună dacă s-a produs o eroare.

Următorul exemplu demonstrează un caz de utilizare realist pentru NSError. Utilizează o metodă de încărcare a fișierelor NSString, care este de fapt în afara sferei de aplicare a cărții. iOS Succinct cartea acoperă managementul fișierelor în profunzime, dar pentru moment, să ne concentrăm doar pe capabilitățile de gestionare a erorilor ale obiectivului C.

Mai întâi, generăm o cale de fișier care să indice ~ / Desktop / SomeContent.txt. Apoi, creăm un NSError trimiteți-l și trimiteți-l la stringWithContentsOfFile: codare: eroare: pentru a capta informații despre eventualele erori care apar în timpul încărcării fișierului. Rețineți că trecem a referinţă la *eroare pointer, ceea ce înseamnă că metoda cere un pointer unui pointer (adică un indicator dublu). Acest lucru face posibil ca metoda să populeze variabila cu propriul conținut. În cele din urmă, verificăm valoare returnată (nu existența eroare variabilă) pentru a vedea dacă stringWithContentsOfFile: codare: eroare: a reușit sau nu. Dacă este cazul, este sigur să lucrați cu valoarea stocată în conţinut variabil; altfel, folosim eroare variabilă pentru a afișa informații despre ceea ce a mers prost.

#import  int main (int argc, const char * argv []) @autoreleasepool // Generează calea dorită a fișierului. NSString * nume fișier = @ "SomeContent.txt"; Căile NSArray * = NSSearchPathForDirectoriesInDomains (NSDesktopDirectory, NSUserDomainMask, YES); NSString * desktopDir = [căi objectAtIndex: 0]; NSString * cale = [desktopDir stringByAppendingPathComponent: nume fișier]; // Încercați să încărcați fișierul. Eroare NSError *; NSString * content = [NSString stringWithContentsOfFile: codificare cale: eroare NSUTF8StringEncoding: & eroare]; // Verificați dacă funcționează. dacă (conținut == zero) // A apărut o eroare. NSLog (@ "Eroare la încărcarea fișierului% @!", Cale); NSLog (@ "Descriere:% @", [error localizedDescription]); NSLog (@ "Motivul:% @", [error localizedFailureReason]);  altceva // Continutul încărcat cu succes. NSLog (@ "conținut încărcat!"); NSLog (@ "% @", conținut);  return 0; 

Din moment ce ~ / Desktop / SomeContent.txt fișierul probabil nu există pe mașina dvs., acest cod va duce cel mai probabil la o eroare. Trebuie doar să creați tot ce trebuie să faceți pentru a reuși încărcarea SomeContent.txt pe desktop.

Erori personalizate

Erori personalizate pot fi configurate prin acceptarea unui indicator dublu la un NSError obiect și populând-o pe cont propriu. Amintiți-vă că funcția sau metoda dvs. trebuie să returneze fie un obiect, fie o zero, în funcție de faptul dacă reușesc sau nu (nu se întoarce NSError referinţă).

Următorul exemplu folosește o eroare în locul unei excepții pentru a atenua parametrii invalizi în generateRandomInteger () funcţie. Observa asta **eroare este un pointer dublu, care ne permite să populam variabila de bază din interiorul funcției. Este foarte important să verificați dacă utilizatorul a trecut de fapt un valid **eroare parametru cu dacă (eroare! = NULL). Ar trebui întotdeauna să faceți acest lucru în propriile funcții de generare a erorilor. Din moment ce **eroare parametrul este un indicator dublu, putem atribui o valoare variabilei subiacente prin intermediul *eroare. Și din nou, verificăm erorile folosind valoare returnată (dacă (rezultat == zero)), Nu eroare variabil.

#import  NSNumber * generateRandomInteger (int minimum, int maxim, NSError ** eroare) if (minimum> = maximum) if (error! = NULL) // Crearea erorii. NSString * domain = @ "com.MyCompany.RandomProject.ErrorDomain"; int errorCode = 4; NSMutableDictionary * userInfo = [NSMutableDictionary dictionary]; [userInfo setObject: @ "Parametrul maxim nu este mai mare decât parametrul minim" pentruKey: NSLocalizedDescriptionKey]; // Populați referința de eroare. * eroare = [[NSError alocare] initWithDomain: cod de domeniu: errorCode userInfo: userInfo];  returnați zero;  // Întoarceți un întreg aleatoare. retur [NSNumber numberWithInt: arc4random_uniform ((maxim - minim) + 1) + minim];  int principal (int argc, const char * argv []) @autoreleasepool eroare NSError *; NSNumber * result = generateRandomInteger (0, -10, & eroare); dacă (rezultat == zero) // Verificați pentru a vedea ce sa întâmplat. NSLog (@ "A apărut o eroare!"); NSLog (@ "Domeniu:% @ Cod:% li", [domeniu de eroare], [cod de eroare]); NSLog (@ "Descriere:% @", [error localizedDescription]);  altfel // Sigur de utilizat valoarea returnată. NSLog (@ "Număr aleator:% i", [result intValue]);  return 0; 

Toate din localizedDescription, localizedFailureReason, și proprietățile conexe ale NSError sunt de fapt stocate în ei userinfo dicționar folosind tastele speciale definite de NSLocalizedDescriptionKey, NSLocalizedFailureReasonErrorKey, etc. Deci, tot ceea ce trebuie să facem pentru a descrie eroarea este să adăugăm niște șiruri la tastele corespunzătoare, așa cum se arată în ultimul eșantion.

În mod tipic, veți dori să definiți constantele pentru domeniile și codurile de eroare personalizate, astfel încât să fie consecvente între clase.


rezumat

Acest capitol a oferit o discuție detaliată a diferențelor dintre excepții și erori. Excepțiile sunt concepute pentru a informa programatorii despre problemele fatale din programul lor, în timp ce erorile reprezintă o acțiune nereușită a utilizatorului. În general, o aplicație gata de producție ar trebui nu aruncați excepții, cu excepția situațiilor cu adevărat excepționale (de exemplu, pierderea memoriei într-un dispozitiv).

Am acoperit utilizarea de bază a NSError, dar rețineți că există mai multe clase încorporate dedicate procesării și afișării erorilor. Din nefericire, acestea sunt toate componentele grafice, deci în afara scopului acestei cărți. iOS Succinct sequel are o secțiune dedicată privind afișarea și recuperarea de la erori.

În capitolul final din Obiectiv-C Succinct, vom discuta unul dintre subiectele mai confuze în Obiectiv-C. Vom descoperi cum să ne tratăm blocurile funcționalitate în același fel în care tratăm date. Acest lucru va avea un impact foarte important asupra a ceea ce este posibil într-o aplicație Obiectiv-C.

Această lecție reprezintă un capitol din Obiectiv-C Succinct, o carte electronică gratuită de la echipa de la Syncfusion.