This could be rather broad problem but I could not find any online resource addressing or explaining this matter.
The question is after creating NSFileHandle *writer = [NSFileHandle fileHandleForWritingAtPath:"path"] and when you use [writer writedata:"NSData"] there are sine possible exception that could fire according to the apple doc.
"This method raises an exception if the file descriptor is closed or is
not valid, if the receiver represents an unconnected pipe or socket
endpoint, if no free space is left on the file system, or if any other
writing error occurs." - APPLE DOC
All I want to know is is there any way we can handle or validate these issues without using any try catch or checking for each error in a condition check before write. Any possible way we can use NSError to handle this ?
I would say "No". If you did manage to find a test that covered all possible failures before writing, then there is nothing to say that the write operation might fail after this initial test (think about writing to a filesystem with 1KB free and you want to write 4KB).
Therefore wrapping your calls to these methods inside a #try/#catch block would seem to me to be the best approach. These wrappers could then return an NSError ** if you want details of the failure (which you most certainly should want).
- (BOOL)writeData:(NSData *)data
toFileHandle:(NSFileHandle *)fileHandler
error:(NSError **)error
{
#try
{
[fileHandler writeData:data];
}
#catch (NSException *e)
{
if (error != NULL)
{
NSDictionary *userInfo = #{
NSLocalizedDescriptionKey : #"Failed to write data",
// Other stuff?
};
*error = [NSError errorWithDomain:#"MyStuff" code:123 userInfo:userInfo];
}
return NO;
}
return YES;
}
You will certainly want to get the reason for the failure into the NSError, but it's not immediately obvious to me how to go about doing this.
Related
Found this interesting bit of code in an Objective-C category which is used to catch NSExceptions and pass them as NSErrors to Swift code.
What I don't understand about it is:
1) Why does it even compile? If an exception is thrown, there is never a return value.
2) Why is the return value different when compiling with debug (optimization level none) and with release (optimization level smallest/fastest)?
- (BOOL)catchException:(void(^)())tryBlock error:(__autoreleasing NSError **)error {
#try {
tryBlock();
return YES;
}
#catch (NSException *exception) {
NSMutableDictionary *userInfo = [exception.userInfo mutableCopy];
if (!userInfo) userInfo = [NSMutableDictionary new];
userInfo[NSLocalizedDescriptionKey] = exception.reason;
*error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:userInfo];
}
// Note the missing return value outside the try-catch
}
Invoking the function:
NSError *error;
BOOL result = [self catchException:^{
#throw [NSException exceptionWithName:#"Exception" reason:#"WTF?" userInfo:nil];
} error:&error];
NSLog(#"Result: %#", result ? #"YES" : #"NO");
When compiling and running with the Debug scheme, we get:
2017-02-09 10:01:39.695 Compile Test[23129:630118] Result: NO
And when doing the same with the Release scheme:
2017-02-09 10:01:39.695 Compile Test[23129:630118] Result: YES
So in both cases there appears to be a return value, even though there is no returned value outside the try-catch block, and the return value inside the try-catch is never reached. We're all very confused over here ?!
That is either a compiler bug or the "check control flow to make sure return value is present" option is turned off (if there is one).
The differing return value is because the behavior is undefined.
Basically, whatever happens to be in the slot -- likely a register, might be on the stack, depends on the ABI of the targeted CPU -- that holds the return value at the time the function returns will be used.
Without optimization, the compiler won't reuse registers and the stack; every variable gets its own space and it is preserved to the end of the function. With optimization, the compiler will reuse memory proactively, causing the behavior change. This is also why debugging optimized code is such a pain; 'p myVariable' might print something unexpected simply because 'myVariable' has been recycled.
I have a code for fetching json. It works perfectly fine, when my device is connected to internet, but it crashes if there is no internet connection.
I have surrounded that particular line of code with try / catch block, but it doesn't seem to do the trick.
Of course, I can do a workaround, and first check if there is internet connection and then call the method I need, but I want to understand this.
Why #catch isn't triggered in this case, and what to do to handle this exception in this case?
Here is the code:
#try {
NSError *error;
NSMutableDictionary* json = [NSJSONSerialization
JSONObjectWithData:_jsonData
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:&error];
...
#catch (NSException *exception) {
[_indicator stopAnimating];
_indicator.hidden = YES;
[self popUp];
}
So, the exception occurs when trying to populate json dictionary.
Your JSON data _jsonData seems to be obtained before try~catch block, and then the source of the issue should be out of the block. Then you need to find out where the data is obtained from the internet, and install the try~catch block at there.
I have an app that's using background downloads with the new NSURLSession APIs. When a download cancels or fails in such a way that NSURLSessionDownloadTaskResumeData is provided, I store the data blob so that it can be resumed later. A very small amount of the time I am noticing a crash in the wild:
Fatal Exception: NSInvalidArgumentException
Invalid resume data for background download. Background downloads must use http or https and must download to an accessible file.
The error occurs here, where resumeData is the NSData blob and session is an instance of NSURLSession:
if (resumeData) {
downloadTask = [session downloadTaskWithResumeData:resumeData];
...
The data is provided by the Apple APIs, is serialized, and is then deserialized at a later point in time. It may be corrupted, but it is never nil (as the if statement checks).
How can I check ahead of time that the resumeData is invalid so that I do not let the app crash?
This is the workaround suggested by Apple:
- (BOOL)__isValidResumeData:(NSData *)data{
if (!data || [data length] < 1) return NO;
NSError *error;
NSDictionary *resumeDictionary = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:&error];
if (!resumeDictionary || error) return NO;
NSString *localFilePath = [resumeDictionary objectForKey:#"NSURLSessionResumeInfoLocalPath"];
if ([localFilePath length] < 1) return NO;
return [[NSFileManager defaultManager] fileExistsAtPath:localFilePath];
}
Edit (iOS 7.1 is not NDA'd anymore): I got this from a Twitter exchange with an Apple engineer, he suggested what to do, and I wrote the above implementation
I have not found an answer to how to tell if the data is valid ahead of time.
However, I am presently working around the issue like so:
NSData *resumeData = ...;
NSURLRequest *originalURLRequest = ...;
NSURLSessionDownloadTask *downloadTask = nil;
#try {
downloadTask = [session downloadTaskWithResumeData:resumeData];
}
#catch (NSException *exception) {
if ([NSInvalidArgumentException isEqualToString:exception.name]) {
downloadTask = [session downloadTaskWithRequest:originalURLRequest];
} else {
#throw exception; // only swallow NSInvalidArgumentException for resumeData
}
}
actually, the resume data is a plist file.
it contains the follows key:
NSURLSessionDownloadURL
NSURLSessionResumeBytesReceived
NSURLSessionResumeCurrentRequest
NSURLSessionResumeEntityTag
NSURLSessionResumeInfoTempFileName
NSURLSessionResumeInfoVersion
NSURLSessionResumeOriginalRequest
NSURLSessionResumeServerDownloadDate
so the steps u need to do are:
check the data is a valid plist;
check the plist have keys as above;
check the temp file is exist;
My application, during a process I've written, is rising in memory and seems to not be releasing it.
The first thing I'd like to mention is that the basic outline of what I've written is this:
- Request a url (fetching data using NSData -initWithContentsOfURL)
- Parse NSData into NSArray of NSDictionarys using NSJSONSerialization +JSONObjectWithStream
- Loop through decoded NSArray inserting/updating/deleting records in a sqlite database using the FMDB framework with the data decoded
The application does the above, however it does it in a loop for an undetermined period of time, in which the application displays a "Loading" HUD. I thought it may be worth mentioning this, although I find it insignificant how many times it does this process, as that shouldn't affect the memory usage if it were releasing properly. If I am wrong here, please advise me.
My code works fine, well, it does what it's intended to do. However, when I profile the application code, the memory seems to just keep rising. It does drop in segments throughout, but overall it keeps rising (IE doesn't release in full what it previously used).
I have, as previously stated, profiled the application with Allocations, Leaks, VM Tracker, and used Trace Highlights.
Trace Highlights: Shows that the memory usage is gradually going up, but dropping some memory (not all) meaning if the process is running for long enough the memory will reach high usage and terminate.
Allocations: Seems OK. The allocations has spikes but always comes back down to where it started. I took heapshots and they always drop down leaving maximum 500-700kb per segment (left for about 10 minutes)
VM Tracker: Proves to show that memory consistently rises, and is not releasing full memory (as discovered in trace highlights). Resident seems to get really high
Leaks: No leaks found in application
Here's some screenshots of Allocations/VM Tracker running:
It's worth noting that I have in fact tried:
- Adding autoreleasepools
- "force releasing" by assigning each properties; such as NSURLs, NSRequests, etc; to nil
My questions:
- Should I be doing something special to release the memory?
- How could I further debug this issue?
- How best can I find out what's wrong from the data Instruments gives me?
---- EDIT: ----
Here's the code that sends the url request to fetch the data.:
- (void) requestAndParse : (NSString *)url
{
NSURL *theURL;
ASIHTTPRequest *request;
NSData *collectedData;
NSError *error;
#try {
// File cache the NSData
theURL = [[NSURL alloc] initWithString: url];
request = [ASIHTTPRequest requestWithURL: theURL];
[request setDownloadDestinationPath: [[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingString:#"/cachefile.txt"]];
[request startSynchronous];
[request waitUntilFinished];
collectedData = [[NSData alloc] initWithContentsOfFile:[[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingString:#"/cachefile.txt"]];
if ([collectedData length] > 0) {
records = [NSJSONSerialization JSONObjectWithData:collectedData options:NSJSONReadingMutableContainers error:&error];
}
}
#catch (NSException *exception) {
// Failed
NSLog(#"Parse error: %#", error);
}
#finally {
// DB updates with the records here
...
// remove file
[[NSFileManager defaultManager] removeItemAtPath:[[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingString:#"/cachefile.txt"] error:nil];
// release properties used
collectedData = nil;
request = nil;
theURL = nil;
}
}
This above method is called from within a while loop in the Application Delegate. The while loop is an undetermined length, as previously mentioned.
--- EDIT 2: ---
The following is what happens within the #finally statement (updating the SQLite database using FMDB). There are a lot of these methods in my class, one for each table. They all follow the same pattern though, as they are all duplicated from the first one:
-(BOOL) insertBatchOfRecords:(NSArray *)records {
__block BOOL queueReturned = YES;
#autoreleasepool {
FMDatabaseQueue *dbQueue = [self instantiateDatabaseQueue];
[dbQueue inTransaction:^(FMDatabase *tdb, BOOL *rollback) {
if (![tdb open]) {
NSLog(#"Couldn't open DB inside Transaction");
queueReturned = NO;
*rollback = YES;
return;
}
for (NSDictionary *record in records) {
[tdb executeUpdate:#"INSERT OR REPLACE INTO table (attr1, attr2) VALUES (?,?)", [record valueForKey:#"attr1"], [record valueForKey:#"attr2"]];
if ([tdb hadError]) {
queueReturned = NO;
*rollback = YES;
NSLog(#"Failed to insert records because %#", [tdb lastErrorMessage]);
return;
}
}
}];
[dbQueue close];
dbQueue = nil;
}
return queueReturned;
}
And follows is the -instantiateDatabaseQueue method:
-(FMDatabaseQueue *) instantiateDatabaseQueue {
#autoreleasepool {
return [FMDatabaseQueue databaseQueueWithPath: [self.getDocumentsDirectory stringByAppendingPathComponent:#"localdb.db"]];
}
}
The autoreleasepools may make it messy, but the code originally did not have these. I implemented them in various locations to see if there was any improvement (there was not).
--- EDIT 3 ---
I have been profiling the application the past few days, and still have no luck in finding an answer. I have separated the part of the app in question to a separate project of it's own, to make sure that it is indeed this causing the memory usage. This proved to be correct, as the app is still acting the same.
I have taken further profiling pictures, and am still having a hard time identifying what is actually wrong. See below that the allocations looks OK (the VM also doesn't look too bad to me?), and there's still no leaks (no picture of this, because there's none!!)
However, when I profiled on Trace Highlights, the memory usage just keeps going up, until reaching too much usage (around 70+MB on a 3GS) and then crashes due to using so much memory.
I reduced the problem by using ASIHTTPRequest for grabbing the NSData (stores to file instead). Please see revised code above. However, problem still persists, just takes longer to happen!
As per originally, question:
- Is there something wrong with the second part of this app process?
Using try / catch in iOS with ARC can cause memory leaks, and is best avoided.
an alternative approach is to use an async NSURLConnection, or an NSOperation with a synch NSURLConnection.
What kind of errors can -[NSManagedObjectContext executeFetchRequest:error:] and -[NSFetchedResultsController performFetch:] return to the user and how should they be handled? I cannot find anything in the documentation about the possible errors for these methods. Also none of the error codes defined in CoreData/CoreDataErrors.h seem to apply to fetching.
Right now my error handling for Core Data fetches is just a NSAssert like this:
NSError *fetchError = nil;
NSArray *fetchedResults = [context executeFetchRequest: request error: &fetchError];
NSAssert( fetchedResults, #"Error fetching: %#", fetchError );
While testing I never had this assertion fail, but that doesn’t mean that this cannot fail. What are the best practices to handle those errors gracefully?
When executing a fetch request, I populate the expected array with an empty array instead of leaving it nil.
NSError *error;
NSArray *array = [context executeFetchRequest:request error:&error];
if (array == nil)
{
NSLog(#"Error retrieving array of values %#", error);
array = [NSArray array];
}
If you ever want to test your error handling, this answer details how to implement an NSPeristentStore that will give you an error every time.
Core Data Unit Testing - Unsure how to trigger error case in executeFetchRequest:error:
You already found CoreDataErrors.h, also see the Core Data Constants Reference
Possible errors that could occur would be SQLite query errors for example. I vaguely remember seeing something like this, when I used an operation in a predicate that was translated into something not supported by the SQLite version backing core-data.
If caught during development you use the NSError purely to debug. If this happens at run time in an already released application an option would be to fail gracefully and if possible be ask the user to specify a different search format.
First thing is to capture the error, but it entirely depends on the context of your code as to when you want to handle it gracefully or when you want to assert and stop anything else happening if things are going wrong.
Also remember that fetchedResults can return not nil, with no results (count == 0), which is not an error but you obviously might want to code against that.
NSError *fetchError = nil;
NSArray *fetchedResults = [context executeFetchRequest: request error: &fetchError];
if (fetchError) {
NSLog(#"Error with fetch: %#",error);
// Assert or do whatever is required ..
}
// Continue as normal ..
I always pass these errors up to the NSResponder chain, thus:
NSManagedDocument _document;
NSManagedObjectContext _moc;
NSError *error = nil;
NSArray *result = [_managedObjectContext executeFetchRequest:fr error:&error];
if (fetchedResults == nil && error) {
[_document presentError:error];
// or, if this isn't a document-based app, you can do
// [NSApp presentError:error];
// or, if this method is in an IBAction you can just do
// [sender presentError:error];
// and it'll just do the right thing
}
The default implementation in NSManagedDocument does an okay job of presenting these errors, except for situations when you are saving a doc and get multiple validation errors, in which case you need to write something special.
When in doubt, present NSError as soon as possible, and if you find yourself hard-coding a lot of retval checking for NSError, you problem might be more in what you're sending the error-returning function in the first place.
NSErrors, in general, are for users to resolve; NSExceptions are what the framework uses to let developers know what they need to deal with.