I Objective-C finns det två typer av fel som kan inträffa när ett program körs. Oväntat fel är "allvarliga" programmeringsfel som vanligtvis orsakar att ditt program avslutas för tidigt. Dessa kallas undantag, eftersom de representerar ett exceptionellt villkor i ditt program. Å andra sidan, förväntat Fel uppstår naturligt under ett programs genomförande och kan användas för att bestämma framgången för en operation. Dessa kallas för fel.
Du kan också närma sig skillnaden mellan undantag och fel som en skillnad i deras målgrupper. Generellt används undantag för att informera programmerare om något som gick fel, medan fel används för att informera användare att en efterfrågad åtgärd inte kunde slutföras.
Kontrollflöde för undantag och felOm du till exempel försöker få åtkomst till ett arrayindex som inte existerar är ett undantag (ett programfelfel), medan du inte öppnar en fil är ett fel (ett användarfel). I det förra fallet gick det förmodligen mycket fel i flödet av ditt program och det borde antagligen stängas av snart efter undantaget. I det senare fallet vill du berätta för användaren att filen inte kunde öppnas och eventuellt be om att försöka igen, men det finns ingen anledning att ditt program inte skulle kunna fortsätta att gå efter felet.
Den största fördelen med Objective-C: s undantagshanteringsförmåga är möjligheten att skilja felhanteringen från att upptäcka fel. När en del av koden möter ett undantag kan det "kasta" det till närmaste felhanteringsblock, vilket kan "fånga" specifika undantag och hantera dem på lämpligt sätt. Det faktum att undantag kan kastas från godtyckliga platser eliminerar behovet av att ständigt kontrollera efter framgång eller felmeddelanden från varje funktion som är inblandad i en viss uppgift.
De @Prova
, @fånga()
, och @till sist
kompilatordirektiv används för att fånga och hantera undantag, och @kasta
Direktivet används för att upptäcka dem. Om du har arbetat med undantag i C #, bör dessa konstruktioner för undantagshantering vara bekanta för dig.
Det är viktigt att notera att i Mål-C är undantag relativt långsamma. Som ett resultat bör användningen av dem begränsas till att fånga allvarliga programmeringsfel - inte för grundläggande kontrollflöde. Om du försöker bestämma vad du ska göra baserat på en förväntat fel (till exempel att inte ladda en fil), hänvisas till Felhanteringsavsnittet.
Undantag är representerade som fall av NSException
klass eller en underklass därav. Detta är ett bekvämt sätt att inkapsla alla nödvändiga uppgifter som hör samman med ett undantag. De tre egenskaper som utgör ett undantag beskrivs enligt följande:
namn
- En förekomst av NSString
som unikt identifierar undantaget.anledning
- En förekomst av NSString
innehållande en läsbara beskrivning av undantaget.användarinformation
- En förekomst av NSDictionary
som innehåller applikationsspecifik information relaterad till undantaget.Stiftelsens ramverk definierar flera konstanter som definierar "standard" undantagsnamnen. Dessa strängar kan användas för att kontrollera vilken typ av undantag som fångades.
Du kan också använda initWithName: anledning: Userinfo:
initialiseringsmetod för att skapa nya undantagsobjekt med egna värden. Anpassade undantag objekt kan fångas och kastas med samma metoder som omfattas av de kommande sektionerna.
Låt oss börja med att titta på standardprogrammet för undantagshantering av ett program. De objectAtIndex:
metod av NSArray
definieras för att kasta en NSRangeException
(en underklass av NSException
) när du försöker komma åt ett index som inte existerar. Så, om du begär 10th objekt i en array som bara har tre element, har du själv ett undantag för att experimentera med:
#importeraint main (int argc, const char * argv []) @ autoreleasepool NSArray * besättning = [NSArray arrayWithObjects: @ "Dave", @ "Heywood", @ "Frank", noll]; // Detta kommer att göra ett undantag. NSLog (@ "% @", [besättning objektAtIndex: 10]); returnera 0;
När det uppstår ett oavsett undantag stoppar Xcode programmet och pekar på linjen som orsakade problemet.
Avbryter ett program på grund av ett oavsett undantagDärefter lär vi oss att fånga undantag och förhindra att programmet avslutas.
För att hantera ett undantag, vilken kod som helst Maj resultera i ett undantag bör placeras i a @Prova
blockera. Då kan du fånga specifika undantag med hjälp av @fånga()
direktiv. Om du behöver utföra någon hushållskod kan du valfritt placera den i en @till sist
blockera. Följande exempel visar alla tre av dessa undantagshanteringsdirektiv:
@try NSLog (@ "% @", [besättning objektAtIndex: 10]); @catch (NSException * undantag) NSLog (@ "Fångat ett undantag"); // Vi kommer bara tyst att ignorera undantaget. @finalt NSLog (@ "Rengöring");
Detta ska mata ut följande i din Xcode-konsol:
Fångade ett undantag! Namn: NSRangeException Orsak: *** - [__ NSArrayI objectAtIndex:]: index 10 bortom gränserna [0 ... 2] Rengöring
När programmet möter [besättning objektAtIndex: 10]
meddelande, det kastar en NSRangeException
, som fångas i @fånga()
direktiv. Inne i @fånga()
block är där undantaget faktiskt hanteras. I det här fallet visar vi bara ett beskrivande felmeddelande, men i de flesta fall kommer du förmodligen att vilja skriva en kod för att ta hand om problemet.
När ett undantag upptäcks i @Prova
block, hoppar programmet till motsvarande @fånga()
block, vilket betyder vilken kod som helst efter Undantaget inträffade kommer inte att utföras. Detta innebär ett problem om @Prova
block behöver lite rengöring (till exempel om den öppnade en fil måste den filen vara stängd). De @till sist
block löser detta problem, eftersom det är garanterat att utföras oavsett om ett undantag inträffade. Detta gör den till den perfekta platsen för att binda upp några lösa ändar från @Prova
blockera.
Parenteserna efter @fånga()
Direktivet kan du definiera vilken typ av undantag du försöker fånga. I det här fallet är det en NSException
, vilket är standard undantag klassen. Men ett undantag kan faktiskt vara några klass-inte bara en NSException
. Till exempel följande @fånga()
direktivet kommer att hantera ett generiskt objekt:
@catch (id genericException)
Vi lär oss att kasta instanser av NSException
såväl som generiska objekt i nästa avsnitt.
När du upptäcker ett exceptionellt villkor i din kod skapar du en instans av NSException
och fyll i den med relevant information. Sedan kastar du den med hjälp av den lämpliga namnet @kasta
direktiv, vilket beror på närmaste @Prova
/@fånga
blockera för att hantera det.
Exempelvis definierar följande exempel en funktion för att generera slumptal mellan ett angivet intervall. Om anroparen skickar ett ogiltigt intervall, kastar funktionen ett anpassat fel.
#importeraint generateRandomInteger (int minimum, int maximum) if (minimum> = maximum) // Skapa undantaget. NSException * exception = [NSException exceptionWithName: @ "RandomNumberIntervalException" orsak: @ "*** generateRandomInteger ():" "maximal parameter inte större än minsta parameter" userInfo: nil]; // Kasta undantaget. @throw undantag; // Returnera ett slumpmässigt heltal. returnera arc4random_uniform ((max - minimum) + 1) + minimum; int main (int argc, const char * argv []) @autoreleasepool int result = 0; @try result = generateRandomInteger (0, 10); @catch (NSException * undantag) NSLog (@ "Problem !!! Fångat undantag:% @", [undantagsnamn]); NSLog (@ "Slumpmässigt nummer:% i", resultat); returnera 0;
Eftersom denna kod passerar ett giltigt intervall (0, 10
) till generateRandomInteger ()
, det kommer inte att ha ett undantag till fånga. Om du ändrar intervallet till något liknande (0, -10
) får du se @fånga()
block i åtgärd. Detta är i huvudsak vad som händer under huven när ramklasserna möter undantag (t.ex. NSRangeException
höjas med NSArray
).
Det är också möjligt att omkasta undantag som du redan har tagit. Detta är användbart om du vill bli informerad om att ett visst undantag inträffade men inte nödvändigtvis vill hantera det själv. Som en bekvämlighet kan du även släppa bort argumentet till @kasta
direktiv:
@try result = generateRandomInteger (0, -10); @catch (NSException * undantag) NSLog (@ "Problem !!! Fångat undantag:% @", [undantagsnamn]); // Återkast nuvarande undantaget. @throw
Detta överför det fångna undantaget till nästa högsta hanterare, vilket i det här fallet är den högsta undantagshanteraren. Detta ska visa utmatningen från vår @fånga()
blockera, liksom standardvärdet Avslutande app på grund av oskyddat undantag ...
meddelande, följt av en abrupt utgång.
De @kasta
Direktivet är inte begränsat till NSException
objekt - det kan kasta bokstavligen några objekt. Följande exempel slänger en NSNumber
objekt istället för ett normalt undantag. Observera också hur du kan rikta olika objekt genom att lägga till flera @fånga()
uttalanden efter @Prova
blockera:
#importeraint generateRandomInteger (int minimum, int maximum) if (minimum> = maximum) // Generera ett tal med "standard" -intervall. NSNumber * guess = [NSNumber numberWithInt: generateRandomInteger (0, 10)]; // Kasta numret. Tänk på; // Returnera ett slumpmässigt heltal. returnera arc4random_uniform ((max - minimum) + 1) + minimum; int main (int argc, const char * argv []) @autoreleasepool int result = 0; @try result = generateRandomInteger (30, 10); @catch (NSNumber * gissning) NSLog (@ "Warning: Used default interval"); result = [gissa intValue]; @catch (NSException * undantag) NSLog (@ "Problem !!! Fångat undantag:% @", [undantagsnamn]); NSLog (@ "Slumpmässigt nummer:% i", resultat); returnera 0;
I stället för att kasta en NSException
objekt, generateRandomInteger ()
Försöker skapa ett nytt tal mellan vissa "standard" gränser. Exemplet visar hur @kasta
kan arbeta med olika typer av objekt, men strängt taget är detta inte den bästa applikationsdesignen, och det är inte heller den mest effektiva användningen av Objective-C: s undantagshanteringsverktyg. Om du verkligen bara planerar att använda det kastade värdet som den tidigare koden gör, skulle du bli bättre med en vanlig gammal villkorlig kontroll med hjälp av NSError
, som diskuteras i nästa avsnitt.
Dessutom, några av Apples kärnramar förvänta en NSException
protestera mot att kastas, var därför försiktig med anpassade objekt när de integreras med standardbiblioteken.
Undantag är utformade för att låta programmerare veta när saker har gått felaktigt, fel är utformade för att vara ett effektivt, enkelt sätt att kontrollera om en åtgärd lyckades eller inte. Till skillnad från undantag, fel är utformad för att användas i dina dagliga kontrollflödesdeklarationer.
Den enda sak som fel och undantag har gemensamt är att de båda implementeras som föremål. De NSError
klassen inkapslar all nödvändig information för att representera fel:
koda
- En NSInteger
som representerar felets unika identifierare.domän
- En förekomst av NSString
definiera domänen för felet (beskrivs närmare i nästa avsnitt).användarinformation
- En förekomst av NSDictionary
som innehåller applikationsspecifik information relaterad till felet. Detta används vanligtvis mycket mer än användarinformation
ordbok av NSException
.Förutom dessa kärnattribut, NSError
lagrar också flera värden som är utformade för att hjälpa till vid återgivning och bearbetning av fel. Alla dessa är faktiskt genvägar till användarinformation
ordbok som beskrivs i föregående lista.
localizedDescription
- En NSString
innehåller den fullständiga beskrivningen av felet, vilket typiskt innehåller orsaken till felet. Detta värde visas normalt för användaren i en varningspanel.localizedFailureReason
- En NSString
innehåller en fristående beskrivning av orsaken till felet. Detta används endast av klienter som vill isolera orsaken till felet från den fullständiga beskrivningen.recoverySuggestion
- En NSString
instruera användaren hur man återställer sig från felet.localizedRecoveryOptions
- En NSArray
av titlar som används för knapparna i feldialogrutan. Om denna matris är tom, en enda ok knappen visas för att avvisa varningen.helpAnchor
- En NSString
att visa när användaren trycker på Hjälp ankerknapp i en varningspanel.Som med NSException
, de initWithDomain: Kod: Userinfo
Metoden kan användas för att initiera anpassade NSError
instanser.
En feldomän är som en namnrymd för felkoder. Koder ska vara unika inom en enda domän, men de kan överlappa koder från andra domäner. Förutom att förhindra kodkollisioner, ger domäner också information om var felet kommer ifrån. De fyra viktigaste inbyggda feldomänerna är: NSMachErrorDomain
, NSPOSIXErrorDomain
, NSOSStatusErrorDomain
, och NSCocoaErrorDomain
. De NSCocoaErrorDomain
innehåller felkoderna för många av Apples standardmål-C-ramar; Det finns emellertid vissa ramar som definierar sina egna domäner (t.ex.., NSXMLParserErrorDomain
).
Om du behöver skapa egna felkoder för dina bibliotek och applikationer, bör du alltid lägga till dem din egen fel domän-aldrig utöka några av de inbyggda domänerna. Att skapa din egen domän är ett relativt trivialt jobb. Eftersom domäner bara är strängar är allt du behöver göra att definiera en strängkonstant som inte strider mot någon av de andra feldomänerna i din applikation. Apple föreslår att domäner tar formen av com.
.
Det finns inga dedikerade språkkonstruktioner för hantering NSError
instanser (även om flera inbyggda klasser är utformade för att hantera dem). De är konstruerade för att användas tillsammans med specialdesignade funktioner som returnerar ett objekt när de lyckas och noll
när de misslyckas Det allmänna förfarandet för att fånga fel är som följer:
NSError
variabel. Du behöver inte ange eller initiera det.NSError
att hantera felet själv eller visa det för användaren.Som du kan se fungerar inte en funktion normalt lämna tillbaka en NSError
objekt-det returnerar vilket värde det är meningen om det lyckas, annars kommer det tillbaka noll
. Du bör alltid använda returvärdet för en funktion för att upptäcka fel - använd aldrig närvaron eller frånvaron av en NSError
objekta för att kontrollera om en åtgärd lyckades. Felaktiga objekt ska bara beskriva ett potentiellt fel, inte berätta om det inträffade.
Följande exempel visar ett realistiskt användningsfall för NSError
. Den använder en filladdningsmetod för NSString
, som faktiskt ligger utanför bokens räckvidd. De IOS Succinctly Boken täcker filhanteringen i djupet, men för närvarande, låt oss bara fokusera på objektiv-C: s felhanteringsfunktioner.
Först genererar vi en filväg som pekar på ~ / Desktop / SomeContent.txt.
Då skapar vi en NSError
referens och skicka den till stringWithContentsOfFile: kodning: error:
Metod för att fånga information om eventuella fel som uppstår vid laddning av filen. Observera att vi passerar en referens till *fel
pekaren, vilket innebär att metoden kräver en pekare till en pekare (dvs en dubbelpekare). Detta gör det möjligt för metoden att fylla variabeln med eget innehåll. Slutligen kontrollerar vi returvärde (inte existensen av fel
variabel) för att se om stringWithContentsOfFile: kodning: error:
lyckades eller inte. Om det gjorde det är det säkert att arbeta med det värde som lagrats i innehåll
variabel; annars använder vi fel
variabel för att visa information om vad som gick fel.
#importeraint main (int argc, const char * argv []) @autoreleasepool // Generera önskad filväg. NSString * filnamn = @ "SomeContent.txt"; NSArray * sökvägar = NSSearchPathForDirectoriesInDomains (NSDesktopDirectory, NSUserDomainMask, YES); NSString * desktopDir = [sökväg objektAtIndex: 0]; NSString * path = [desktopDir stringByAppendingPathComponent: filnamn]; // Försök ladda filen. NSError * fel; NSString * content = [NSString stringWithContentsOfFile: sökvägs kodning: NSUTF8StringEncoding error: & error]; // Kontrollera om det fungerade. om (content == nil) // En viss typ av fel inträffade. NSLog (@ "Fel vid inläsning av fil% @!", Sökväg); NSLog (@ "Description:% @", [error localizedDescription]); NSLog (@ "Anledning:% @", [fel localizedFailureReason]); annat // innehåll laddades framgångsrikt. NSLog (@ "Innehåll laddad!"); NSLog (@ "% @", innehåll); returnera 0;
Sedan ~ / Desktop / SomeContent.txt
filen finns förmodligen inte på din maskin, kommer den här källan sannolikt att resultera i ett fel. Allt du behöver göra för att lasten ska lyckas är skapad SomeContent.txt
på skrivbordet.
Anpassade fel kan konfigureras genom att acceptera en dubbelpekare till en NSError
objekt och befolka det själv. Kom ihåg att din funktion eller metod ska returnera antingen ett objekt eller noll
, beroende på om det lyckas eller misslyckas (returnera inte NSError
referens).
I nästa exempel används ett fel istället för ett undantag för att mildra ogiltiga parametrar i generateRandomInteger ()
fungera. Lägg märke till att **fel
är en dubbelpekare, som låter oss fylla den underliggande variabeln från funktionen. Det är väldigt viktigt att kontrollera att användaren faktiskt godkänt en giltig **fel
parameter med om (fel! = NULL)
. Du bör alltid göra detta i dina egna felgenererande funktioner. Sedan **fel
parametern är en dubbelpekare, vi kan tilldela ett värde till den underliggande variabeln via *fel
. Och igen, vi söker efter fel med hjälp av returvärde (om (resultat == noll)
), inte den fel
variabel.
#importeraNSNumber * generateRandomInteger (int minimum, int maximum, NSError ** fel) om (minimum> = maximum) if (error! = NULL) // Skapa felet. NSString * domain = @ "com.MyCompany.RandomProject.ErrorDomain"; int errorCode = 4; NSMutableDictionary * userInfo = [NSMutableDictionary Dictionary]; [userInfo setObject: @ "Maximal parameter är inte större än minsta parameter" förKey: NSLocalizedDescriptionKey]; // Fyll i felreferensen. * error = [[NSError alloc] initWithDomain: domänkod: errorCode userInfo: userInfo]; returnera nil; // Returnera ett slumpmässigt heltal. returnera [NSNummer nummer medIt: arc4random_uniform ((maximum - minimum) + 1) + minimum]; int huvud (int argc, const char * argv []) @autoreleasepool NSError * error; NSNumber * result = generateRandomInteger (0, -10, & error); om (resultat == noll) // Kontrollera för att se vad som gick fel. NSLog (@ "Ett fel inträffade!"); NSLog (@ "Domän:% @ Kod:% li", [feldomän], [felkod]); NSLog (@ "Description:% @", [error localizedDescription]); else // Säker att använda returvärdet. NSLog (@ "Slumpmässigt nummer:% i", [result intValue]); returnera 0;
Alla localizedDescription
, localizedFailureReason
, och relaterade egenskaper hos NSError
lagras faktiskt i dess användarinformation
ordbok med speciella nycklar definierade av NSLocalizedDescriptionKey
, NSLocalizedFailureReasonErrorKey
, etc. Så, allt vi behöver göra för att beskriva felet är att lägga till några strängar till lämpliga nycklar, som visas i det sista provet.
Vanligtvis vill du definiera konstanter för anpassade feldomäner och koder så att de överensstämmer över klasserna.
Detta kapitel gav en detaljerad diskussion om skillnaderna mellan undantag och fel. Undantag är utformade för att informera programmerare om dödliga problem i sitt program, medan fel representerar en misslyckad användaråtgärd. I allmänhet bör en produktionsklar applikation inte kasta undantag, förutom i fallet med verkligt exceptionella omständigheter (t ex köra ur minne i en anordning).
Vi täckte den grundläggande användningen av NSError
, men kom ihåg att det finns flera inbyggda klasser för att bearbeta och visa fel. Tyvärr är dessa alla grafiska komponenter, och därmed utanför ramen för denna bok. De IOS Succinctly uppföljaren har en dedikerad del att visa och återhämta sig från fel.
I det sista kapitlet av Objective-C Succinctly, vi kommer att diskutera ett av de mer förvirrande ämnena i mål-C. Vi kommer att upptäcka hur block kan vi behandla funktionalitet på samma sätt som vi behandlar data. Detta kommer att ha en omfattande inverkan på vad som är möjligt i en Objective-C-applikation.
Denna lektion representerar ett kapitel från Objective-C Succinctly, en gratis eBook från laget vid Syncfusion.