Cover each line of my code in try catch block - ios

I have been given a task to implement error handling in my app - each and every line of the code has to be checked for error handling. MY friend (who has a background of Java) advised me to put each and every method definition in try-catch blocks; which is not possible in Swift. As we can put try against only those methods that are throwable. So do i need to convert all my methods to throwable and if so, how would i catch exceptions which i am unaware of as that is what we are aiming to achieve.
So how can i cover my entire project in error handling?
Moreover, I am also doubtful of the fact that does Swift checks for
"fatal error : Unexpectedly found nil' exceptions and array out
of bound exceptions.
Please, help me through this!

Yes you can only catch something that can throw error. However, there is no need to catch each and every line of code. If you just creating a variable and assigning some value, there should not be exceptions. Also, it's mostly true that in your code, if some exception happened, the rest of the code should not run. In this situation, you just need a big try catch and inform user when goes wrong in exception handling block.
Swift does not check for force unwrap or index out of bound. It provide you the way to check nil and index using guard let or if let but you are responsible for this checking.

Related

When is it okay to use force unwrapping and/or disabling error propagation in SwiftUI?

As someone who doesn't have too much experience coding apps I've always wondered a lot about this. Everywhere you look you will always find people saying that you should always avoid using ! so as to make your code more safe. This is because crashes, even if they happen 0.001% of the time, are always a big no in programming. However, in some situations I can never really judge if it's okay to use the ! operator. For example, let's say I have a function which updates a document in Cloud Firestore. Now there are two instances where you may possible use !. One is when you force unwrap the documents id property to reference the document and second is when you disable error propagation for setData(from:completion:).
This is my reasoning for using ! in both situations:
Force unwrapping the book's id property is fine because if I've fetched any books from Firestore, or created one using the initialiser, I will always fill in the id field with a value. The only way this may fail is if Firestore is unable to parse it into the field, which to my understanding only happens if you've annotated say a field called name with #DocumentID and the Firestore document has a field called name as well. This should never happen because I would never put a field named id in a book document.
.setData(from:completion:) should never throw either because it only throws if there's an error in encoding a Book instance. Again, this should never happen because firstly it's marked with Codable and also every book I add to the database will be by encoding that struct, guaranteeing it to work every time if it works once.
struct Book: Identifiable, Codable {
#DocumentID var id: String?
var title: String
}
func update(_ book: Book, completion: #escaping (Error?) -> Void) {
let docRef = db.collection("books").document(book.id!) //<<< Force unwrapping
try! docRef.setData(from: book) { error in //<<< Disabling error propagation
completion(error) // Any network related error thrown by Firestore is, however, handled
}
}
Can someone please give me reasons as to why this should be avoided. If you think this has a chance to make the program crash make sure to explain the reason why it will crash as well so that I have a better understanding of these edge-cases.
If you have any personal experiences with anything even remotely related to this please share them as I would love to learn about them.
Thanks in advance!
P.S. This isn't restricted to just Firestore related situations, if you can share any situation in iOS programming where ! can be used without problem (except for the obvious ones such as URL(string: "https://www.apple.com")! or when using .randomElement()!) please do mention them.
First of all, that is a very fair question and can be confusing when you start dealing with optional types. When you are force-unwrapping a value, it means that you are sure that the value should be there. But should doesn't me it will be there.
In some cases, like you pointed out, there could be instances where, for reasons that you can't control like in your Firestore example, force-unwrapping a value could throw an error that you might not be prepared to handle.
After years of working on mobile apps, I learned that you have to use the common sense for such cases. There is no silver-bullet for such cases and it will depend on the risk you can afford to take when doing a force-unwrap.
Things you should consider before doing that:
If you force-unwrap using the ! operator and it throws an error, can you handle the error accordingly and perform other actions to mitigate the issue?
If yes, you can safely do it.
if not, you can consider using a guard statement to protect you against failures and perform any actions where the expected value is nil.
If you force-unwrap a value that your app or view controller needs to function in all cases and there is no way to recover from that, it's better to let it crash than to let the user perform actions in an invalid state. E.g. when a required ID is necessary to perform a transaction.
But like I mentioned before, it will depend on that affordances you can take when you do this kind of operation.

Cannot get completion result when removing file using NSFileManager in Swift

I'm trying to remove a video that the user recorded and now has decided to delete. I have both a file URL and then obviously a path too.
I have tried using the removal methods of the NSFileManager class for both the file and the path, but I'm having trouble getting a completion result to confirm whether the file has actually been deleted or not.
Here is an example of how I'm trying to remove the file in Swift:
let deleted = try! NSFileManager.defaultManager().removeItemAtURL(self.fileURL)
This will give me a warning of Constant 'deleted' inferred to have type '()', which may be unexpected
Using removeItemAtPath produces the same warning. If I run the code, deleted simply logs as ()
If I look at the method signatures for these two methods it's clear that they do not return a result, but take for example the documentation for the removeItemAtURL method: true if the item was removed successfully or if URL was nil. Returns false if an error occurred. If the delegate aborts the operation for a file, this method returns true. However, if the delegate aborts the operation for a directory, this method returns false.
It also mentions taking an error parameter but doesn't have one. And then finally in the last sentence it says: Returns YES if the item was removed successfully or if URL was nil.
As a last resort I figured I could just become the delegate for NSFileManager, but it's delegate protocol does not offer any completion methods.
How can I properly remove a file or path and then verify that it has actually been deleted?
You're right that in Swift it returns Void (I believe the docs include the return value description for when you have Objective-C or Both turned on, as opposed to just Swift -- they've got a lot of work to do updating the docs).
If you continue reading the documentation for that function, you'll see a section titled "Handling Errors in Swift", which says:
In Swift, this method returns Void and is marked with the throws
keyword to indicate that it throws an error in cases of failure.
You call this method in a try expression and handle any errors in the
catch clauses of a do statement, as described in Error Handling in The
Swift Programming Language (Swift 2.1) and Error Handling in Using
Swift with Cocoa and Objective-C (Swift 2.1).
So wrap your call in a try/catch and handle the error if there is one. If there wasn't an error, it succeeded.

Method mysteriously exits execution in the middle to resume the rest of the program?

So i've got this code that tries to find an unused upload name, using the user's email and a number at its end. It does this with a list of uploaded objects we've already collected, the user's email.(upload_name), and the
current number that might be open (it is incremented when a match is found).
The list is not sorted, and it's pretty tricky to sort for a few reasons, so I'm having the method read through the list again if it reaches the end and the upload_number has changed.
- (NSString*)findUnusedUploadNameWithPreviousUploads:(NSMutableArray*)objects withBaseUploadName:(NSString*)upload_name {
previous_upload_number = upload_number;
for (NSString *key in objects) {
// the component of the object name before the first / is the upload name.
NSLog([key componentsSeparatedByString:#"/"][1]);
if ([[key componentsSeparatedByString:#"/"][1]
isEqualToString:([NSString stringWithFormat:#"%#_%ld", S3KeyUploadName1, upload_number])]) {
upload_number++;
NSLog([NSString stringWithFormat:#"upload name: %#_%ld", S3KeyUploadName1, upload_number]);
}
NSLog(#"pang");
}
NSLog(#"ping");
if (previous_upload_number == upload_number) {
return [NSString stringWithFormat:#"%#%ld", upload_name, upload_number];
}
return [self findUnusedUploadNameWithPreviousUploads:objects withBaseUploadName:upload_name];
}
The problem is, the program never reads the "ping". it just leaves the method after the first for loop is done.
Edit: No the NSlogs are fine, you can do simple string OR StringWithFormat.
Edit: Don't mind the unnecessary use of recursion, I did this because the simple way was having the same problem and i wanted to see if a different (albeit unnecessarily recursive) way would share that problem. It does.
Edit: I set a breakpoint in the for loop, and I set a break point at the "ping". It does not reach the ping. It completes the for loop and the ditches the whole thing.
Edit: Please try to help me figure out why it's exiting the the method immediately after the for loop. I'm aware this is stylistically meh, and I promise I'll make it shiny and spotless when it works. =]
Edit: to be clear, the method DOES exit. it does so early I know this because the rest of the program following this method (which is not threaded such that it wouldn't have to wait for it) runs AFTER this for loop, consistently.
There are a couple of possible explanations for the described behavior:
The method never exits. For some reason it blocks or performs infinitely somewhere in the loop. Make sure this is not the case by setting a breakpoint after the place where the message is called (i.e. the place to where it should return).
The method, or some method it calls, throws an exception. While seldom and unsupported in productive Cocoa code it could be some misbehaving 3rd party library or just a simple programmer error, where Cocoa actually does throw. Make sure this does not happen by setting an exception breakpoint in Xcode.
Undefined behavior. This is, sadly, part of official C and heavily exploited by modern compilers. It basically means: Anything can happen, if there's something in your code, where the standard says that the behavior is not defined. One example would be accessing a deallocated object. Another fine reason of undefined behavior can be threads accessing common data in an unsynchronized way.
Other than exceptions there's no explanation for a method to "exit early". If you still can't find the reason I suggest you invest some time to learn the debugger. Single stepping, like Aris suggested, might be a way to find out what's going on.

Accessing Class in a Breakpoint Conditional

I have a method that I want to debug:
-(void)doAThingWithObject:(BaseDataObject *)dataObject //called VERY often
And I have an Xcode breakpoint inside this method which I want to only break on a certain subclass of BaseDataObject, so I add a breakpoint w/conditional to check for that class:
[dataObject isKindOfClass:[SubClassOfBaseDataObject class]]
However, doing so results in a parse error!
Stopped due to an error evaluating condition of breakpoint 11.1: "[dataObject isKindOfClass:[SubClassOfBaseDataObject class]]"
Couldn't parse conditional expression:
error: no known method '+class'; cast the message send to the method's return type
error: 1 errors parsing expression
I have made sure to import all classes in the file, but the debugger does not know what class I'm referencing in the conditional.
However, creating a temp variable of said Class inside the method before the breakpoint:
Class subClassCheck = [SubClassOfBaseDataObject class];
And updating the breakpoint conditional to reference the temp variable:
[dataObject isKindOfClass:subClassCheck]
Throws no errors.
I'm a bit of a novice when it comes to breakpoint conditionals, can someone explain why my first approach doesn't work?
One complication with debugging code that is based on big frameworks like Cocoa is that it is not practical for the compiler to emit or the debugger to consume every type and function in the whole closure of frameworks you include. So the compiler uses some heuristics to reduce the amount of debug information generated. It will emit type information only for types that you actually use, and function/ObjC method information where the method is defined (as opposed to declared in a header file.) There's another little subtlety that lldb will read the type information for methods out of the ObjC runtime, though this information is not complete, since it is meant for the runtime not for debuggers... So we sometimes seem to know things about ObjC methods that violate the previous rule.
Another important thing to note is that the calling conventions for functions that return something larger than a pointer (like NSMakeRect, etc) are such that if the debugger calls a function thinking it returns a pointer and it actually returns a bigger structure, that act will cause stack corruption in your program. If you are lucky you will crash right away when you continue, but if you are unlucky it will just change some data value and cause you to spend hours trying to chase down some funny behavior that is actually caused by the debugger. So the debugger will refuse to call functions whose return type it can't determine.
Anyway the error you got is because the debugger couldn't find debug information for the "+class" method on your object. That is not altogether surprising, since "class" is a method on NSObject and not your class. I'm not sure why we couldn't find it in the runtime, maybe because it is a class method? That's worth a bug. We obviously did get the type of isKindOfClass: from the runtime, or your workaround would have also failed.
In this case, since you actually know the return type of the class method, you can work around the debugger's lack of knowledge by explicitly casting it in your breakpoint expression. Casting a function return in the debugger's expression parser serves two purposes, one is the regular C language function, and the other is telling the debugger the return type of a function it wouldn't otherwise be able to figure out. A sort of short-hand prototype only for the return type.
So something like:
[dataObject isKindOfClass: (Class) [SubClassOfBaseDataObject class]]
should work without having to alter your code.
Note also, the breakpoint conditions are run using the same mechanism as the "expr" or "print" command. So the easiest way to experiment with breakpoint commands is to set an unconditional breakpoint, hit it, then go to the lldb console and play around with "print" till you get something that works.

Debugging NSManagedObjectContext

At debugging time, if I have an NSManagedObjectContext, is there a way to look inside to see what objects are in it.
Basically I'm having a save error since there's a CGColor being saved which is not NSCoding compliant. But I have no idea where this CGColor comes from.
Well, step back for a second and think about where your error is stemming from.
You're trying to encode a CGColorRef via the NSCoding mechanism. This is obviously not supported and will cause an exception to be thrown. You should add an exception breakpoint in your debugger to introspect where this faulty assignment is being executed. You should then be able to figure out your issue.
If you find that this is somehow unrelated to your problem, then you can indeed introspect the objects which are laying around in your context via the -registeredObjects method.
I agree with JR (below) that you should set an exception breakpoint to get a stack trace at the point of failure.
One other thought: although autosaving is convenient, it doesn't always happen at the best time for debugging. You might find it helpful to put in a debugging operation that forces an explicit save when you want to validate your object:
[self.document closeWithCompletionHandler:^(BOOL success) {
if (!success) NSLog(#“failed to close document %#”, self.document.localizedName);
}];
With this, or something like it, you can initiate saving at various points to see when your object becomes corrupted. Do keep in mind that saving is asynchronous.

Resources