Mål-C Succinkt Unntak og feil

I Mål-C er det to typer feil som kan oppstå mens et program kjører. Uventet feil er "seriøse" programmeringsfeil som vanligvis forårsaker at programmet slutter for tidlig. Disse kalles unntakene, siden de representerer en eksepsjonell tilstand i programmet. På den andre siden, forventet Feil oppstår naturlig i løpet av et programs gjennomføring og kan brukes til å bestemme suksess for en operasjon. Disse er referert til som feil.

Du kan også nærme forskjellen mellom unntak og feil som en forskjell i deres målgrupper. Generelt brukes unntak for å informere Programmerer om noe som gikk galt, mens feilene brukes til å informere bruker at en forespurt handling ikke kunne fullføres.

Kontrollstrøm for unntak og feil

For eksempel, hvis du prøver å få tilgang til en arrayindeks som ikke eksisterer, er et unntak (en programmeringsfeil), mens du ikke åpner en fil, er det en feil (en brukerfeil). I det forrige tilfellet gikk det sannsynligvis noe galt i strømmen av programmet ditt, og det burde trolig stenge kort etter unntaket. I det sistnevnte vil du fortelle brukeren at filen ikke kunne åpnes og muligens be om å prøve på nytt, men det er ingen grunn til at programmet ikke kunne fortsette å løpe etter feilen.


Avvikshåndtering

Hovedfordelen for Objective-Cs unntakshåndteringsmuligheter er muligheten til å skille håndteringen av feil fra feiloppdagelsen. Når en del kode opplever et unntak, kan det "kaste" det til nærmeste feilhåndteringsblokk, som kan "fange" bestemte unntak og håndtere dem på riktig måte. Det faktum at unntak kan kastes fra vilkårlig plassering eliminerer behovet for stadig å sjekke om suksess eller feilmeldinger fra hver funksjon som er involvert i en bestemt oppgave.

De @prøve, @å fange(), og @endelig Kompilatordirektiver brukes til å fange og håndtere unntak, og @kaste Direktivet brukes til å oppdage dem. Hvis du har jobbet med unntak i C #, bør disse konstruksjonene for unntakshåndtering være kjent for deg.

Det er viktig å merke seg at i Mål-C er unntak relativt langsomme. Som et resultat bør bruken av dem begrenses til å fange seriøse programmeringsfeil - ikke for grunnleggende kontrollstrøm. Hvis du prøver å bestemme hva du skal gjøre basert på en forventet feil (for eksempel ikke å laste inn en fil), vennligst se Feilhåndteringsseksjon.

NSException-klassen

Unntak er representert som forekomster av NSException klasse eller en underklasse derav. Dette er en praktisk måte å inkapslere all nødvendig informasjon knyttet til et unntak. De tre egenskapene som utgjør et unntak er beskrevet som følger:

  • Navn - En forekomst av NSString som unikt identifiserer unntaket.
  • grunnen til - En forekomst av NSString inneholder en menneskelig lesbar beskrivelse av unntaket.
  • brukerinformasjon - En forekomst av NSDictionary som inneholder applikasjonsspesifikke opplysninger relatert til unntaket.

Stiftelsesrammen definerer flere konstanter som definerer "standard" unntaksnavnene. Disse strengene kan brukes til å kontrollere hvilken type unntak som ble fanget.

Du kan også bruke initWithName: Grunnen: Userinfo: initialiseringsmetode for å lage nye unntaksobjekter med egne verdier. Egendefinerte unntak objekter kan bli fanget og kastet med de samme metodene dekket i de kommende seksjonene.

Generering Unntak

La oss begynne med å ta en titt på standardhåndteringshåndteringen til et program. De objectAtIndex: Metode av NSArray er definert for å kaste en NSRangeException (en underklasse av NSException) når du prøver å få tilgang til en indeks som ikke eksisterer. Så, hvis du ber om 10th element i en matrise som bare har tre elementer, har du selv et unntak for å eksperimentere med:

#importere  int main (int argc, const char * argv []) @ autoreleasepool NSArray * mannskap = [NSArray arrayWithObjects: @ "Dave", @ "Heywood", @ "Frank", null]; // Dette vil kaste et unntak. NSLog (@ "% @", [besetning objektAtIndex: 10]);  returnere 0; 

Når den møter et uncaught unntak, stopper Xcode programmet og viser deg til linjen som forårsaket problemet.

Avbryter et program på grunn av et uncaught unntak

Deretter lærer vi hvordan vi får tak i unntak og forhindrer at programmet avsluttes.

Fangende unntak

For å håndtere et unntak, vil enhver kode som kan resultere i et unntak bør plasseres i a @prøve blokkere. Deretter kan du fange spesifikke unntak ved å bruke @å fange() direktiv. Hvis du trenger å utføre noen husholdningskode, kan du eventuelt plassere den i en @endelig blokkere. Følgende eksempel viser alle tre av disse håndteringsdirektiver:

@try NSLog (@ "% @", [besetning objektAtIndex: 10]);  @catch (NSException * unntak) NSLog (@ "Fanget et unntak"); // Vi vil bare tydelig ignorere unntaket.  @finally NSLog (@ "Cleaning up"); 

Dette skal sende følgende i Xcode-konsollen:

Fanget et unntak! Navn: NSRangeException Årsak: *** - [__ NSArrayI objectAtIndex:]: indeks 10 utenfor grenser [0 ... 2] Opprydding

Når programmet møter [besetning objektAtIndex: 10] melding, det kaster en NSRangeException, som er fanget i @å fange() direktiv. Innsiden av @å fange() blokk er hvor unntaket faktisk håndteres. I dette tilfellet viser vi bare en beskrivende feilmelding, men i de fleste tilfeller vil du sikkert skrive noen kode for å ta vare på problemet.

Når et unntak oppstår i @prøve blokk, programmet hopper til det tilsvarende @å fange() blokkere, noe som betyr hvilken kode som helst etter unntaket oppstått vil ikke bli utført. Dette utgjør et problem hvis @prøve blokk trenger litt rydding (for eksempel hvis den åpnet en fil, må den filen være stengt). De @endelig blokk løses dette problemet, siden det er garantert å bli henrettet uansett om et unntak oppstod. Dette gjør det til det perfekte stedet å binde opp løse ender fra @prøve blokkere.

Parentesene etter @å fange() Direktivet lar deg definere hvilken type unntak du prøver å fange. I dette tilfellet er det en NSException, som er standard unntaksklasse. Men et unntak kan faktisk være noen klasse-ikke bare en NSException. For eksempel, følgende @å fange() Direktivet vil håndtere et generisk objekt:

@catch (id genericException)

Vi lærer å kaste forekomster av NSException så vel som generiske objekter i neste avsnitt.

Kaster unntak

Når du oppdager en eksepsjonell tilstand i koden din, oppretter du en forekomst av NSException og fyll den med relevant informasjon. Deretter kaster du den ved hjelp av den passende navnet @kaste Direktivet, som ber om det nærmeste @prøve/@å fange blokk for å håndtere det.

For eksempel definerer følgende eksempel en funksjon for å generere tilfeldige tall mellom et spesifisert intervall. Hvis den som ringer passerer et ugyldig intervall, kaster funksjonen en tilpasset feil.

#importere  int generateRandomInteger (int minimum, int maksimum) if (minimum> = maksimum) // Opprett unntaket. NSException * exception = [NSException exceptionWithName: @ "RandomNumberIntervalException" grunn: @ "*** generateRandomInteger ():" "maksimal parameter ikke større enn minimum parameter" userInfo: null); // Kast unntaket. @throw unntak;  // Returner et tilfeldig heltall. returnere arc4random_uniform ((maksimum - minimum) + 1) + minimum;  int main (int argc, const char * argv []) @ autoreleasepool int resultat = 0; @try result = generateRandomInteger (0, 10);  @catch (NSException * unntak) NSLog (@ "Problem! Fangst unntak:% @", [unntak navn]);  NSLog (@ "Tilfeldig nummer:% i", resultat);  returnere 0; 

Siden denne koden passerer et gyldig intervall (0, 10) til generateRandomInteger (), det vil ikke ha unntak for fangst. Men hvis du endrer intervallet til noe som helst (0, -10), får du se @å fange() blokkere i aksjon. Dette er i hovedsak hva som skjer under hetten når rammeklassene møter unntak (f.eks NSRangeException oppdratt av NSArray).

Det er også mulig å gjenkaste unntak som du allerede har tatt. Dette er nyttig hvis du vil bli informert om at et bestemt unntak oppstod, men ikke nødvendigvis vil håndtere det selv. Som en bekvemmelighet kan du til og med utelate argumentet til @kaste direktiv:

@try result = generateRandomInteger (0, -10);  @catch (NSException * unntak) NSLog (@ "Problem! Fangst unntak:% @", [unntak navn]); // Gjenta nåværende unntak. @throw

Dette gir det fanget unntaket opp til den nærmeste høyeste handler, som i dette tilfellet er toppnivåns unntakshandler. Dette burde vise produksjonen fra vår @å fange() blokk, samt standard Avsluttende app på grunn av uncaught unntak ... melding, etterfulgt av en abrupt utgang.

De @kaste Direktivet er ikke begrenset til NSException objekter-det kan kaste bokstavelig talt noen gjenstand. Følgende eksempel kaster en NSNumber objekt i stedet for et normalt unntak. Legg også merke til hvordan du kan målrette mot forskjellige objekter ved å legge til flere @å fange() uttalelser etter @prøve blokkere:

#importere  int generateRandomInteger (int minimum, int maksimum) if (minimum> = maksimum) // Generer et tall med "standard" intervall. NSNummer * gjett = [NSNummer nummerWithInt: generateRandomInteger (0, 10)]; // Kast nummeret. @throw gjette;  // Returner et tilfeldig heltall. returnere arc4random_uniform ((maksimum - minimum) + 1) + minimum;  int main (int argc, const char * argv []) @ autoreleasepool int resultat = 0; @try result = generateRandomInteger (30, 10);  @catch (NSNumber * gjetning) NSLog (@ "Advarsel: Brukt standardintervall"); result = [gjett intValue];  @catch (NSException * unntak) NSLog (@ "Problem! Fangst unntak:% @", [unntak navn]);  NSLog (@ "Tilfeldig nummer:% i", resultat);  returnere 0; 

I stedet for å kaste en NSException gjenstand, generateRandomInteger () Forsøker å generere et nytt tall mellom noen "standard" grenser. Eksemplet viser deg hvordan @kaste kan fungere med ulike typer objekter, men dette er ikke den beste applikasjonsdesignen, og det er heller ikke den mest effektive bruken av Objective-Cs unntakshåndteringsverktøy. Hvis du egentlig bare hadde tenkt å bruke den kastede verdien som den forrige koden gjør, ville du være bedre med en vanlig gammel betinget sjekk ved å bruke NSError, som omtalt i neste avsnitt.

I tillegg er noen av Apples kjernerammer forvente en NSException protester mot å bli kastet, så vær forsiktig med tilpassede objekter når du integrerer med standardbiblioteker.


Feilhåndtering

Mens unntak er laget for å la programmører vite når ting har gått dårlige feil, er feil utformet for å være en effektiv, grei måte å kontrollere om en handling lykkes eller ikke. I motsetning til unntak, feil er designet for å bli brukt i dine daglige kontrollflyt setninger.

NSError-klassen

Den eneste tingen som feil og unntak har til felles er at de begge er implementert som objekter. De NSError klassen innkapsler all nødvendig informasjon for å representere feil:

  • kode - en NSInteger som representerer feilens unike identifikator.
  • domene - En forekomst av NSString definerer domenet for feilen (beskrevet nærmere i neste avsnitt).
  • brukerinformasjon - En forekomst av NSDictionary som inneholder programspesifikke opplysninger relatert til feilen. Dette brukes vanligvis mye mer enn brukerinformasjon ordbok av NSException.

I tillegg til disse kjernegenskapene, NSError Lagrer også flere verdier som er utformet for å hjelpe til med gjengivelse og behandling av feil. Alle disse er faktisk snarveier til brukerinformasjon ordbok beskrevet i forrige liste.

  • localizedDescription - en NSString inneholder den fulle beskrivelsen av feilen, som vanligvis inneholder årsaken til feilen. Denne verdien vises vanligvis til brukeren i et varselpanel.
  • localizedFailureReason - en NSString inneholder en frittstående beskrivelse av årsaken til feilen. Dette brukes kun av klienter som ønsker å isolere årsaken til feilen fra sin fullstendige beskrivelse.
  • recoverySuggestion - en NSString instruere brukeren hvordan å gjenopprette fra feilen.
  • localizedRecoveryOptions - en NSArray av titler som brukes til knappene i feildialogen. Hvis dette arrayet er tomt, en enkelt OK knappen vises for å avvise varselet.
  • helpAnchor - en NSString å vise når brukeren trykker på Hjelp Ankerknapp i et varselpanel.

Som med NSException, de initWithDomain: Kode: Userinfo Metoden kan brukes til å initialisere tilpasset NSError forekomster.

Feil domener

Et feildomene er som et navneområde for feilkoder. Koder bør være unike innenfor et enkelt domene, men de kan overlappe koder fra andre domener. I tillegg til å hindre kodekollisjoner, gir domener også informasjon om hvor feilen kommer fra. De fire viktigste innebygde feildomenene er: NSMachErrorDomain, NSPOSIXErrorDomain, NSOSStatusErrorDomain, og NSCocoaErrorDomain. De NSCocoaErrorDomain inneholder feilkoder for mange av Apples standardmål-C-rammer; Det er imidlertid noen rammer som definerer sine egne domener (f.eks., NSXMLParserErrorDomain).

Hvis du trenger å opprette egendefinerte feilkoder for biblioteker og programmer, bør du alltid legge dem til din egen feildomene - utvide aldri noen av de innebygde domenene. Å lage ditt eget domene er en relativt triviell jobb. Fordi domener er bare strenger, er alt du trenger å definere en strengskonstant som ikke er i konflikt med noen av de andre feildomenene i søknaden din. Apple foreslår at domener har form av com...ErrorDomain.

Fange feil

Det er ingen dedikerte språkkonstruksjoner for håndtering NSError forekomster (selv om flere innebygde klasser er designet for å håndtere dem). De er designet for å brukes sammen med spesialdesignede funksjoner som returnerer en gjenstand når de lykkes og nil når de feiler. Den generelle prosedyren for å fange feil er som følger:

  1. Erklære en NSError variabel. Du trenger ikke å tildele eller initialisere det.
  2. Pass denne variabelen som en dobbel pointer til en funksjon som kan føre til en feil. Hvis noe går galt, vil funksjonen bruke denne referansen til å registrere informasjon om feilen.
  3. Kontroller returverdien til den funksjonen for suksess eller feil. Hvis operasjonen mislyktes, kan du bruke NSError å håndtere feilen selv eller vise den til brukeren.

Som du kan se, fungerer ikke en funksjon vanligvis komme tilbake en NSError objekt-det returnerer hvilken verdi den skal hvis den lykkes, ellers returnerer den nil. Du bør alltid bruke returverdien til en funksjon for å oppdage feil. Bruk aldri tilstedeværelsen eller fraværet av en NSError Motta for å se om en handling lykkes. Feilobjekter skal bare beskrive en potensiell feil, ikke fortelle om det oppstod en.

Følgende eksempel viser en realistisk brukstilstand for NSError. Den bruker en fil-lasting metode av NSString, som faktisk ligger utenfor bokens omfang. De iOS Succinctly Boken dekker filbehandling i dybden, men for nå, la oss bare fokusere på feilhåndteringsfunksjonene til Objective-C.

Først genererer vi en filsti som peker på ~ / Desktop / SomeContent.txt. Deretter oppretter vi en NSError referanse og send det til stringWithContentsOfFile: koding: error: metode for å fange opp informasjon om eventuelle feil som oppstår under lasting av filen. Legg merke til at vi passerer en henvisning til *feil pekeren, som betyr at metoden er å be om en peker til en peker (dvs. en dobbeltpeker). Dette gjør det mulig for metoden å fylle variabelen med eget innhold. Til slutt, vi sjekker returverdi (ikke eksistensen av feil variabel) for å se om stringWithContentsOfFile: koding: error: lykkes eller ikke. Hvis det gjorde det, er det trygt å arbeide med verdien som er lagret i innhold variabel; ellers bruker vi feil variabel for å vise informasjon om hva som gikk galt.

#importere  int main (int argc, const char * argv []) @ autoreleasepool // Generer den ønskede filbanen. NSString * filnavn = @ "SomeContent.txt"; NSArray * stier = NSSearchPathForDirectoriesInDomains (NSDesktopDirectory, NSUserDomainMask, YES); NSString * desktopDir = [stier objectAtIndex: 0]; NSString * bane = [desktopDir stringByAppendingPathComponent: filnavn]; // Prøv å laste filen. NSError * feil; NSString * content = [NSString stringWithContentsOfFile: stikoding: NSUTF8StringEncoding error: & error]; // Sjekk om det virket. hvis (innhold == nil) // Noen feil oppstod. NSLog (@ "Feil ved lasting av fil% @!", Bane); NSLog (@ "Description:% @", [error localizedDescription]); NSLog (@ "Årsak:% @", [feil localizedFailureReason]);  ellers // innhold lastet vellykket. NSLog (@ "Innhold lastet!"); NSLog (@ "% @", innhold);  returnere 0; 

Siden ~ / Desktop / SomeContent.txt filen finnes sannsynligvis ikke på maskinen din, vil denne koden sannsynligvis føre til en feil. Alt du trenger å gjøre for å få lasten til å lykkes er å skape SomeContent.txt på skrivebordet ditt.

Tilpassede feil

Egendefinerte feil kan konfigureres ved å akseptere en dobbeltpeker til en NSError objekt og befolke det på egen hånd. Husk at din funksjon eller metode skal returnere enten et objekt eller nil, avhengig av om det lykkes eller feiler (ikke returner NSError henvisning).

Det neste eksemplet bruker en feil i stedet for et unntak for å redusere ugyldige parametere i generateRandomInteger () funksjon. Legg merke til det **feil er en dobbel pointer, som lar oss fylle den underliggende variabelen fra funksjonen. Det er veldig viktig å kontrollere at brukeren faktisk har bestått en gyldig **feil parameter med hvis (feil! = NULL). Du bør alltid gjøre dette i dine egne feilgenererende funksjoner. Siden **feil parameteren er en dobbel pointer, vi kan tilordne en verdi til den underliggende variabelen via *feil. Og igjen, vi ser etter feil ved bruk av returverdi (hvis (resultat == nil)), ikke feil variabel.

#importere  NSNumber * generateRandomInteger (int minimum, int maksimum, NSError ** feil) hvis (minimum> = maksimum) if (error! = NULL) // Opprett feilen. NSString * domain = @ "com.MyCompany.RandomProject.ErrorDomain"; int errorCode = 4; NSMutableDictionary * userInfo = [NSMutableDictionary ordbok]; [userInfo setObject: @ "Maksimum parameter er ikke større enn minimum parameter" forKey: NSLocalizedDescriptionKey]; // Populere feilreferansen. * error = [[NSError alloc] initWithDomain: domenekode: errorCode userInfo: userInfo];  returner null;  // Returner et tilfeldig heltall. returnere [NSNummer nummerWithInt: arc4random_uniform ((maksimum - minimum) + 1) + minimum];  int main (int argc, const char * argv []) @ autoreleasepool NSError * feil; NSNumber * result = generateRandomInteger (0, -10, & error); hvis (resultat == nil) // Sjekk for å se hva som gikk galt. NSLog (@ "En feil oppstod!"); NSLog (@ "Domain:% @ Code:% li", [feil-domene], [feilkode]); NSLog (@ "Description:% @", [error localizedDescription]);  else // Trygg å bruke returverdien. NSLog (@ "Tilfeldig nummer:% i", [result intValue]);  returnere 0; 

Alle localizedDescription, localizedFailureReason, og beslektede egenskaper av NSError er faktisk lagret i sin brukerinformasjon ordbok ved hjelp av spesielle taster definert av NSLocalizedDescriptionKey, NSLocalizedFailureReasonErrorKey, etc. Så alt vi trenger å gjøre for å beskrive feilen, er å legge til noen strenger til de riktige tastene, som vist i den siste prøven.

Vanligvis vil du definere konstanter for egendefinerte feildomener og koder slik at de er konsistente på tvers av klasser.


Sammendrag

Dette kapitlet ga en detaljert diskusjon om forskjellene mellom unntak og feil. Unntak er utformet for å informere programmører om dødelige problemer i deres program, mens feil representerer en mislykket brukerhandling. Generelt bør en produksjonsklar applikasjon ikke kaste unntak, unntatt i tilfelle virkelig uvanlige omstendigheter (for eksempel å gå tom for minne i en enhet).

Vi dekket den grunnleggende bruken av NSError, men husk at det er flere innebygde klasser dedikert til å behandle og vise feil. Dessverre er disse alle grafiske komponenter, og dermed utenfor omfanget av denne boken. De iOS Succinctly oppfølger har en dedikert seksjon om å vise og gjenopprette fra feil.

I det siste kapitlet i Mål-C Succinctly, Vi diskuterer et av de mer forvirrende emnene i Objective-C. Vi vil finne ut hvordan blokker la oss behandle funksjonalitet På samme måte som vi behandler data. Dette vil ha en vidtgående innvirkning på hva som er mulig i et mål-C-program.

Denne leksjonen representerer et kapittel fra Objective-C Succinctly, en gratis eBok fra teamet på Syncfusion.