Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Custom error handling on iOS Michael Ochs, Mobile Developer, HRS

Slide 3

Slide 3 text

Agenda • Responsibility • NSError / NSErrorRecoveryAttempting • Presentation • UIResponder • What’s next?

Slide 4

Slide 4 text

Error handling before

Slide 5

Slide 5 text

How not to do it

Slide 6

Slide 6 text

How not to do it NSError *error = nil;
 BOOL success = [self.data writeToFile:@"my/path"
 options:NSDataWritingAtomic
 error:&error];
 if (!success) {
 // TODO: Implement error handling
 }


Slide 7

Slide 7 text

How not to do it NSError *error = nil;
 BOOL success = [self.data writeToFile:@"my/path"
 options:NSDataWritingAtomic
 error:&error];
 if (!success) {
 // TODO: Implement error handling
 }


Slide 8

Slide 8 text

How not to do it • Implemented in UIViewController subclasses • Custom in each controller • Various error messages for the same error • The same error message for various errors

Slide 9

Slide 9 text

Responsibility

Slide 10

Slide 10 text

Responsibility • Globally in the app • Usable by every potential error receiver • Easy to use • Little setup effort • Error recovery

Slide 11

Slide 11 text

Responsibility ! • what went wrong • does a retry make sense • how to recover ! • what action led to the error • is a retry possible • how to retry Creator of the error knows: Consumer of the error knows:

Slide 12

Slide 12 text

Responsibility Trigger action Create error Present error Recover from error Retry action

Slide 13

Slide 13 text

Responsibility Trigger action Consumer Creator Create error Present error Recover from error Retry action

Slide 14

Slide 14 text

Responsibility Trigger action Create error Present error Recover from error Retry action Controller Data Application

Slide 15

Slide 15 text

NSError

Slide 16

Slide 16 text

NSError • NSLocalizedDescriptionKey • NSLocalizedFailureReasonErrorKey • NSLocalizedRecoverySuggestionErrorKey • NSLocalizedRecoveryOptionsErrorKey • NSRecoveryAttempterErrorKey

Slide 17

Slide 17 text

NSError The corresponding value is a localized string representation of the error that, if present, will be returned by localizedDescription. NSLocalizedDescriptionKey

Slide 18

Slide 18 text

NSError The corresponding value is a localized string representation containing the reason for the failure that, if present, will be returned by localizedFailureReason. This string provides a more detailed explanation of the error than the description. NSLocalizedFailureReasonErrorKey

Slide 19

Slide 19 text

NSError The corresponding value is a string containing the localized recovery suggestion for the error. This string is suitable for displaying as the secondary message in an alert panel. NSLocalizedRecoverySuggestionErrorKey

Slide 20

Slide 20 text

NSError The corresponding value is an array containing the localized titles of buttons appropriate for displaying in an alert panel. The first string is the title of the right-most and default button, the second the one to the left, and so on. The recovery options should be appropriate for the recovery suggestion returned by localizedRecoverySuggestion. NSLocalizedRecoveryOptionsErrorKey

Slide 21

Slide 21 text

NSError The corresponding value is an object that conforms to the NSErrorRecoveryAttempting informal protocol. The recovery attempter must be an object that can correctly interpret an index into the array returned by localizedRecoveryOptions. NSRecoveryAttempterErrorKey

Slide 22

Slide 22 text

NSError • NSLocalizedDescriptionKey • NSLocalizedFailureReasonErrorKey • NSLocalizedRecoverySuggestionErrorKey • NSLocalizedRecoveryOptionsErrorKey • NSRecoveryAttempterErrorKey

Slide 23

Slide 23 text

NSError - (BOOL)doSaveOperation:(NSError **)error {
 
 NSError *error = [NSError errorWithDomain:MyErrorDomain
 code:MyErrorCode
 userInfo:userInfo];
 *error = error;
 
 return NO;
 } Error creation

Slide 24

Slide 24 text

NSError NSDictionary *userInfo = @{
 NSLocalizedFailureReasonErrorKey:
 @"Something went wrong",
 
 NSLocalizedRecoverySuggestionErrorKey:
 @"Not successful. Try again!",
 
 NSLocalizedRecoveryOptionsErrorKey:
 @[ @"Cancel", @"Retry" ],
 
 NSRecoveryAttempterErrorKey:
 recoveryAttempter
 }; Error creation

Slide 25

Slide 25 text

NSError NSDictionary *userInfo = @{
 NSLocalizedFailureReasonErrorKey:
 @"Something went wrong",
 
 NSLocalizedRecoverySuggestionErrorKey:
 @"Not successful. Try again!",
 
 NSLocalizedRecoveryOptionsErrorKey:
 [recoveryAttempter localizedRecoveryOptions],
 
 NSRecoveryAttempterErrorKey:
 recoveryAttempter
 }; Error creation

Slide 26

Slide 26 text

NSErrorRecoveryAttempting

Slide 27

Slide 27 text

NSErrorRecoveryAttempting • Informal protocol on NSObject • Integrated with NSError • Handles error recovery

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

NSErrorRecoveryAttempting HRSErrorRecoveryAttempter

Slide 32

Slide 32 text

NSErrorRecoveryAttempting - (void)addRecoveryOptionWithTitle:(NSString *)title
 recoveryAttempt:(BOOL(^)())recoveryBlock;
 
 - (NSArray *)localizedRecoveryOptions; HRSErrorRecoveryAttempter

Slide 33

Slide 33 text

NSErrorRecoveryAttempting - (void)addRecoveryOptionWithTitle:(NSString *)title
 recoveryAttempt:(BOOL(^)())recoveryBlock;
 
 - (NSArray *)localizedRecoveryOptions; HRSErrorRecoveryAttempter

Slide 34

Slide 34 text

NSErrorRecoveryAttempting - (void)addRecoveryOptionWithTitle:(NSString *)title
 recoveryAttempt:(BOOL(^)())recoveryBlock;
 
 - (NSArray *)localizedRecoveryOptions; HRSErrorRecoveryAttempter

Slide 35

Slide 35 text

NSErrorRecoveryAttempting HRSErrorRecoveryAttempter *recoveryAttempter =
 [HRSErrorRecoveryAttempter new];
 
 [recoveryAttempter addCancelRecoveryOption];
 
 [recoveryAttempter addRecoveryOptionWithTitle:@"Retry"
 recoveryAttempt:^{
 return YES;
 }]; HRSErrorRecoveryAttempter

Slide 36

Slide 36 text

NSErrorRecoveryAttempting HRSErrorRecoveryAttempter *recoveryAttempter =
 [HRSErrorRecoveryAttempter new];
 
 [recoveryAttempter addCancelRecoveryOption];
 
 [recoveryAttempter addRecoveryOptionWithTitle:@"Retry"
 recoveryAttempt:^{
 return YES;
 }]; HRSErrorRecoveryAttempter

Slide 37

Slide 37 text

NSErrorRecoveryAttempting HRSErrorRecoveryAttempter *recoveryAttempter =
 [HRSErrorRecoveryAttempter new];
 
 [recoveryAttempter addCancelRecoveryOption];
 
 [recoveryAttempter addRecoveryOptionWithTitle:@"Retry"
 recoveryAttempt:^{
 return YES;
 }]; HRSErrorRecoveryAttempter

Slide 38

Slide 38 text

NSErrorRecoveryAttempting HRSErrorRecoveryAttempter *recoveryAttempter =
 [HRSErrorRecoveryAttempter new];
 
 [recoveryAttempter addCancelRecoveryOption];
 
 [recoveryAttempter addRecoveryOptionWithTitle:@"Retry"
 recoveryAttempt:^{
 return YES;
 }]; HRSErrorRecoveryAttempter

Slide 39

Slide 39 text

Presentation

Slide 40

Slide 40 text

Presentation @interface NSResponder (NSErrorPresentation)
 
 - (BOOL)presentError:(NSError *)anError;
 
 - (void)presentError:(NSError *)error
 modalForWindow:(NSWindow *)aWindow
 delegate:(id)delegate
 didPresentSelector:(SEL)didPresentSelector
 contextInfo:(void *)contextInfo;
 
 - (NSError *)willPresentError:(NSError *)anError;
 
 @end OS X

Slide 41

Slide 41 text

Presentation @interface NSResponder (NSErrorPresentation)
 
 - (BOOL)presentError:(NSError *)anError;
 
 - (void)presentError:(NSError *)error
 modalForWindow:(NSWindow *)aWindow
 delegate:(id)delegate
 didPresentSelector:(SEL)didPresentSelector
 contextInfo:(void *)contextInfo;
 
 - (NSError *)willPresentError:(NSError *)anError;
 
 @end OS X

Slide 42

Slide 42 text

Presentation @interface NSResponder (NSErrorPresentation)
 
 - (BOOL)presentError:(NSError *)anError;
 
 - (void)presentError:(NSError *)error
 modalForWindow:(NSWindow *)aWindow
 delegate:(id)delegate
 didPresentSelector:(SEL)didPresentSelector
 contextInfo:(void *)contextInfo;
 
 - (NSError *)willPresentError:(NSError *)anError;
 
 @end OS X

Slide 43

Slide 43 text

Presentation @interface NSResponder (NSErrorPresentation)
 
 - (BOOL)presentError:(NSError *)anError;
 
 - (void)presentError:(NSError *)error
 modalForWindow:(NSWindow *)aWindow
 delegate:(id)delegate
 didPresentSelector:(SEL)didPresentSelector
 contextInfo:(void *)contextInfo;
 
 - (NSError *)willPresentError:(NSError *)anError;
 
 @end OS X

Slide 44

Slide 44 text

Presentation @interface UIResponder (HRSCustomErrorPresentation)
 
 - (void)presentError:(NSError *)anError
 completionHandler:(void (^)(BOOL didRecover))completionHandler;
 
 - (NSError *)willPresentError:(NSError *)anError;
 
 @end iOS

Slide 45

Slide 45 text

Presentation @interface UIResponder (HRSCustomErrorPresentation)
 
 - (void)presentError:(NSError *)anError
 completionHandler:(void (^)(BOOL didRecover))completionHandler;
 
 - (NSError *)willPresentError:(NSError *)anError;
 
 @end iOS

Slide 46

Slide 46 text

Presentation @interface UIResponder (HRSCustomErrorPresentation)
 
 - (void)presentError:(NSError *)anError
 completionHandler:(void (^)(BOOL didRecover))completionHandler;
 
 - (NSError *)willPresentError:(NSError *)anError;
 
 @end iOS

Slide 47

Slide 47 text

Presentation - (IBAction)save:(id)sender {
 NSError *error = nil;
 BOOL success = [self.data writeToFile:@"my/path"
 options:NSDataWritingAtomic
 error:&error];
 if (!success) {
 [self presentError:error
 completionHandler:^(BOOL didRecover) {
 if (didRecover) {
 [self save:sender];
 }
 }];
 }
 } iOS

Slide 48

Slide 48 text

Presentation - (IBAction)save:(id)sender {
 NSError *error = nil;
 BOOL success = [self.data writeToFile:@"my/path"
 options:NSDataWritingAtomic
 error:&error];
 if (!success) {
 [self presentError:error
 completionHandler:^(BOOL didRecover) {
 if (didRecover) {
 [self save:sender];
 }
 }];
 }
 } iOS

Slide 49

Slide 49 text

Presentation - (IBAction)save:(id)sender {
 NSError *error = nil;
 BOOL success = [self.data writeToFile:@"my/path"
 options:NSDataWritingAtomic
 error:&error];
 if (!success) {
 [self presentError:error
 completionHandler:^(BOOL didRecover) {
 if (didRecover) {
 [self save:sender];
 }
 }];
 }
 } iOS

Slide 50

Slide 50 text

Architecture

Slide 51

Slide 51 text

Architecture - presentError:completionHandler: NSError - doSomething: UIAlertView

Slide 52

Slide 52 text

Architecture - presentError:completionHandler: NSError - doSomething: UIAlertView completionHandler:
 void(^)(BOOL didRecover) HRSErrorRecoveryAttempter HRSErrorPresentationDelegate

Slide 53

Slide 53 text

The path of an error

Slide 54

Slide 54 text

NSError UIAlertView -attemptRecoveryFromError:
 optionIndex: didRecover void (^completionHandler)(BOOL didRecover) The path of an error UIViewController Operation AppDelegate -save: - doSomething: -presentError:completionHandler:

Slide 55

Slide 55 text

UIResponder

Slide 56

Slide 56 text

UIResponder - presentError:completionHandler:

Slide 57

Slide 57 text

UIResponder Subclasses • UIView • UIViewController • UIApplication • • SKNode

Slide 58

Slide 58 text

UIApplication UIWindow UIResponder UIView - (UIResponder *)nextResponder; UIView UIView UIViewController UIView UIViewController

Slide 59

Slide 59 text

UIResponder @interface DataSource : UIResponder 
 @property (nonatomic, weak) id delegate;
 @end
 
 @implementation DataSource
 
 - (UIResponder *)nextResponder {
 if ([self.delegate isKindOfClass:[UIResponder class]]) {
 return self.delegate;
 }
 return nil;
 }
 
 @end

Slide 60

Slide 60 text

UIResponder @interface DataSource : UIResponder 
 @property (nonatomic, weak) id delegate;
 @end
 
 @implementation DataSource
 
 - (UIResponder *)nextResponder {
 if ([self.delegate isKindOfClass:[UIResponder class]]) {
 return self.delegate;
 }
 return nil;
 }
 
 @end

Slide 61

Slide 61 text

UIResponder @interface DataSource : UIResponder 
 @property (nonatomic, weak) id delegate;
 @end
 
 @implementation DataSource
 
 - (UIResponder *)nextResponder {
 if ([self.delegate isKindOfClass:[UIResponder class]]) {
 return self.delegate;
 }
 return nil;
 }
 
 @end

Slide 62

Slide 62 text

UIResponder UIResponder (HRSCustomErrorPresentation)

Slide 63

Slide 63 text

UIResponder - (NSError *)willPresentError:(NSError *)anError {
 return anError;
 } UIResponder (HRSCustomErrorPresentation)

Slide 64

Slide 64 text

UIResponder - (void)presentError:(NSError *)anError
 completionHandler:(void (^)(BOOL didRecover))completionHandler {
 
 anError = [self willPresentError:anError];
 if (anError == nil) {
 return;
 }
 
 UIResponder *nextResponder = ([self nextResponder] ?:
 [UIApplication sharedApplication]);
 [nextResponder presentError:anError
 completionHandler:completionHandler];
 } UIResponder (HRSCustomErrorPresentation)

Slide 65

Slide 65 text

UIResponder - (void)presentError:(NSError *)anError
 completionHandler:(void (^)(BOOL didRecover))completionHandler {
 
 anError = [self willPresentError:anError];
 if (anError == nil) {
 return;
 }
 
 UIResponder *nextResponder = ([self nextResponder] ?:
 [UIApplication sharedApplication]);
 [nextResponder presentError:anError
 completionHandler:completionHandler];
 } UIResponder (HRSCustomErrorPresentation)

Slide 66

Slide 66 text

UIResponder - (void)presentError:(NSError *)anError
 completionHandler:(void (^)(BOOL didRecover))completionHandler {
 
 anError = [self willPresentError:anError];
 if (anError == nil) {
 return;
 }
 
 UIResponder *nextResponder = ([self nextResponder] ?:
 [UIApplication sharedApplication]);
 [nextResponder presentError:anError
 completionHandler:completionHandler];
 } UIResponder (HRSCustomErrorPresentation)

Slide 67

Slide 67 text

UIResponder - (void)presentError:(NSError *)anError
 completionHandler:(void (^)(BOOL didRecover))completionHandler {
 
 anError = [self willPresentError:anError];
 if (anError == nil) {
 return;
 }
 
 UIResponder *nextResponder = ([self nextResponder] ?:
 [UIApplication sharedApplication]);
 [nextResponder presentError:anError
 completionHandler:completionHandler];
 } UIResponder (HRSCustomErrorPresentation)

Slide 68

Slide 68 text

UIResponder AppDelegate (HRSCustomErrorPresentation)

Slide 69

Slide 69 text

UIResponder - (NSError *)willPresentError:(NSError *)anError {
 if (anError.recoveryAttempter == nil) {
 NSDictionary *userInfo = @{ … };
 anError = [NSError errorWithDomain:anError.domain
 code:anError.code
 userInfo:userInfo];
 }
 return anError;
 } AppDelegate (HRSCustomErrorPresentation)

Slide 70

Slide 70 text

UIResponder - (NSError *)willPresentError:(NSError *)anError {
 if (anError.recoveryAttempter == nil) {
 NSDictionary *userInfo = @{ … };
 anError = [NSError errorWithDomain:anError.domain
 code:anError.code
 userInfo:userInfo];
 }
 return anError;
 } AppDelegate (HRSCustomErrorPresentation)

Slide 71

Slide 71 text

UIResponder - (NSError *)willPresentError:(NSError *)anError {
 if (anError.recoveryAttempter == nil) {
 NSDictionary *userInfo = @{ … };
 anError = [NSError errorWithDomain:anError.domain
 code:anError.code
 userInfo:userInfo];
 }
 return anError;
 } AppDelegate (HRSCustomErrorPresentation)

Slide 72

Slide 72 text

UIResponder - (void)presentError:(NSError *)anError
 completionHandler:(void (^)(BOOL))completionHandler {
 
 anError = [self willPresentError:anError];
 if (anError == nil) {
 return;
 } AppDelegate (HRSCustomErrorPresentation)

Slide 73

Slide 73 text

UIResponder - (void)presentError:(NSError *)anError
 completionHandler:(void (^)(BOOL))completionHandler {
 
 anError = [self willPresentError:anError];
 if (anError == nil) {
 return;
 } AppDelegate (HRSCustomErrorPresentation)

Slide 74

Slide 74 text

UIResponder - (void)presentError:(NSError *)anError
 completionHandler:(void (^)(BOOL))completionHandler {
 
 anError = [self willPresentError:anError];
 if (anError == nil) {
 return;
 } AppDelegate (HRSCustomErrorPresentation)

Slide 75

Slide 75 text

UIResponder HRSErrorPresentationDelegate *delegate = …;
 
 UIAlertView *alertView = [[UIAlertView alloc]
 initWithTitle:[anError localizedFailureReason]
 message:[anError localizedRecoverySuggestion]
 delegate:delegate
 cancelButtonTitle:nil
 otherButtonTitles:nil];
 
 for (NSString *title in [anError localizedRecoveryOptions]) {
 [alertView addButtonWithTitle:title];
 }
 
 [alertView show]; AppDelegate (HRSCustomErrorPresentation)

Slide 76

Slide 76 text

UIResponder HRSErrorPresentationDelegate *delegate = …;
 
 UIAlertView *alertView = [[UIAlertView alloc]
 initWithTitle:[anError localizedFailureReason]
 message:[anError localizedRecoverySuggestion]
 delegate:delegate
 cancelButtonTitle:nil
 otherButtonTitles:nil];
 
 for (NSString *title in [anError localizedRecoveryOptions]) {
 [alertView addButtonWithTitle:title];
 }
 
 [alertView show]; AppDelegate (HRSCustomErrorPresentation)

Slide 77

Slide 77 text

UIResponder HRSErrorPresentationDelegate *delegate = …;
 
 UIAlertView *alertView = [[UIAlertView alloc]
 initWithTitle:[anError localizedFailureReason]
 message:[anError localizedRecoverySuggestion]
 delegate:delegate
 cancelButtonTitle:nil
 otherButtonTitles:nil];
 
 for (NSString *title in [anError localizedRecoveryOptions]) {
 [alertView addButtonWithTitle:title];
 }
 
 [alertView show]; AppDelegate (HRSCustomErrorPresentation)

Slide 78

Slide 78 text

UIResponder HRSErrorPresentationDelegate *delegate = …;
 
 UIAlertView *alertView = [[UIAlertView alloc]
 initWithTitle:[anError localizedFailureReason]
 message:[anError localizedRecoverySuggestion]
 delegate:delegate
 cancelButtonTitle:nil
 otherButtonTitles:nil];
 
 for (NSString *title in [anError localizedRecoveryOptions]) {
 [alertView addButtonWithTitle:title];
 }
 
 [alertView show]; AppDelegate (HRSCustomErrorPresentation)

Slide 79

Slide 79 text

UIResponder HRSErrorPresentationDelegate *delegate = …;
 
 UIAlertView *alertView = [[UIAlertView alloc]
 initWithTitle:[anError localizedFailureReason]
 message:[anError localizedRecoverySuggestion]
 delegate:delegate
 cancelButtonTitle:nil
 otherButtonTitles:nil];
 
 for (NSString *title in [anError localizedRecoveryOptions]) {
 [alertView addButtonWithTitle:title];
 }
 
 [alertView show]; AppDelegate (HRSCustomErrorPresentation)

Slide 80

Slide 80 text

UIResponder objc_setAssociatedObject(alertView,
 DelegateAssociation,
 delegate,
 OBJC_ASSOCIATION_RETAIN);
 
 } AppDelegate (HRSCustomErrorPresentation)

Slide 81

Slide 81 text

What is next?

Slide 82

Slide 82 text

What’s next? • Handle common errors • Provide custom error presentation UI • Add specific error recovery attempter • This is all loosely coupled

Slide 83

Slide 83 text

What’s next? • Available open source • Currently considered beta • Internal interaction might still change • Feedback welcome

Slide 84

Slide 84 text

Related topics • Realmac’s “Cocoa error handling and recovery”
 http://realmacsoftware.com/blog/cocoa-error-handling-and-recovery

Slide 85

Slide 85 text

Feedback / Questions @_mochs [email protected] ios-coding.com

Slide 86

Slide 86 text

Thank you