I have a method that accepts an NSString by reference, and the idea is that if an error occurs, the string will contain a specific error message; otherwise, it'll be nil.
-(BOOL)doStuffThatCouldProduceAnError:(NSString *)error {
...
// An error occurred, so set the string
error = #"Foo Bar is invalid"
return NO;
}
But the problem is, the caller of doStuffThatCouldProduceAnError doesn't see the error message:
-(void)someMethod {
NSString *error;
[self doStuffThatCouldProduceAnError:error];
[NSLog #"Message: %#", error]; // Logs "[nil]"
}
I'm not sure how to search for a solution, and what I did try to search on doesn't cover the passing by reference and setting from another method. I've also tried NSMutableString, but that doesn't seem to make any difference.
Thank you in advance!
Edit: I forgot to mention that I've tried using error = [error stringByAppendingString:...], but that didn't work either.
You are not passing by reference.
You got to do
-(BOOL)doStuffThatCouldProduceAnError:(NSString **)error {
*error = #"Foo Bar is invalid"
and
[self doStuffThatCouldProduceAnError:&error];
Related
I have a React Native application which uses React Native Video with iOS caching. I have been working on a method inside RCTVideoCache.m which would manually delete the data of a particular cache key. According to the documentation of SPTPersistentCache, which the video library uses for caching, data can be deleted either by locking/unlocking a file and invoking a wipe or after inspecting the source code of SPTPersistentCache.h with a method named removeDataForKeys.
I have tried both ways, however, unsuccessfully.
In my first try, I am using wipeLockedFiles. I have created a deleteFromCache() method inside RCTVideoCache.m. Since all my video files are unlocked by default, in this method I am trying to lock the file corresponding to my cacheKey and invoke a wipe on all locked files (which would consist of only my target cacheKey file) as it is demonstrated in the documentation. This method looks like:
- (void)deleteFromCache:(NSString *)cacheKey withCallback:(void(^)(BOOL))handler;
{
[self.videoCache lockDataForKeys:#[cacheKey] callback:nil queue:nil];
[self.videoCache wipeLockedFiles];
NSLog(#"Size = %#", #(self.videoCache.totalUsedSizeInBytes));
handler(YES);
}
The following results in two errors during compilation:
/Users/.../MyApp/node_modules/react-native-video/ios/VideoCaching/RCTVideoCache.m:79:20: error: no visible #interface for 'SPTPersistentCache' declares the selector 'lockDataForKeys:callback:queue:'
[self.videoCache lockDataForKeys:#[cacheKey] callback:nil queue:nil];
~~~~~~~~~~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/.../MyApp/node_modules/react-native-video/ios/VideoCaching/RCTVideoCache.m:80:20: error: no visible #interface for 'SPTPersistentCache' declares the selector 'wipeLockedFiles'
[self.videoCache wipeLockedFiles];
~~~~~~~~~~~~~~~ ^~~~~~~~~~~~~~~
I really have no idea why these selectors are not visible from SPTPersistentCache.
In my second try, I am using removeDataForKeys(). Again, I have created a deleteFromCache() method inside RCTVideoCache.m which looks like this:
- (void)deleteFromCache:(NSString *)cacheKey withCallback:(void(^)(BOOL))handler;
{
[self.videoCache removeDataForKeys:#[cacheKey] callback:^(SPTPersistentCacheResponse * _Nonnull response) {
NSLog(#"Result output: %#", response.output);
NSLog(#"Error output: %#", [response.error localizedDescription]);
} onQueue:dispatch_get_main_queue()];
NSLog(#"Size = %#", #(self.videoCache.totalUsedSizeInBytes));
handler(YES);
}
In this second way, there are no errors, however, the data of the key is never deleted. Also, both NSLogs for the response output null inside the terminal.
I am 100% sure that the cacheKey I am providing to my deleteFromCache() method is correct and data corresponding to it exists. However, in both methods NSLog(#"Size = %#", #(self.videoCache.totalUsedSizeInBytes)); does not change and I can also manually verify that the file has not been deleted.
I am really stuck and do not know what is wrong with the code I've written in both cases and why neither of them works. I would appreciate any help on this!
You can delete all sub-folder's files (tmp/rct.video.cache), iterating each one:
+ (void)deleteFromCache
{
NSArray* tmpDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.temporaryCachePath error:NULL];
for (NSString *file in tmpDirectory) {
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:#"%#%#", self.temporaryCachePath, file] error:NULL];
}
}
I ran your example and discovered that you are using incorrect method signatures. These methods simply don't exist in the caching library, their signatures are different.
Try something like this:
- (void)deleteFromCache:(NSString *)cacheKey withCallback:(void(^)(BOOL))handler;
{
NSLog(#"Size before = %#", #(self.videoCache.totalUsedSizeInBytes));
[self.videoCache lockDataForKeys:#[cacheKey] callback:nil onQueue:nil];
[self.videoCache wipeLockedFilesWithCallback:^(SPTPersistentCacheResponse * _Nonnull response) {
NSLog(#"Size after = %#, response = %#", #(self.videoCache.totalUsedSizeInBytes), response);
// Call handler after the files are wiped
handler(YES);
} onQueue:nil];
}
I have no idea why the second approach doesn't work, but NSLog(#"Size = %#", #(self.videoCache.totalUsedSizeInBytes)); is for sure called before the actual deletion happens. In the example I posted above, I have moved the logging statement into the callback closure, so that it reports the size before and after the deletion takes place.
With google cast iOS SDK, The GCKMediaControlChannel's sendTextMessage method is straightforward and it's hard to mis-use so I am guessing this may be a bug in the SDK ... hopefully someone will prove me wrong so I can get back to work!
Here's the code:
NSDictionary *messageDict = #{
#"message": #"blah",
#"num":[NSNumber numberWithInt:2]
};
NSError *error;
NSData *msgData = [NSJSONSerialization dataWithJSONObject:messageDict
options:0
error:&error];
NSString *message = #"" ;
if (!msgData) {
DDLogError(#"ERROR serializing message: %#", error);
return NO ;
} else {
message = [[NSString alloc] initWithData:msgData encoding:NSUTF8StringEncoding];
[self sendTextMessage:message] ;
}
...the receiver produces this error when the message is received [cast.receiver.mediaManager] Ignoring request, requestId is not an integer: undefined
At first view it seems like GCKMediaControlChannel inherits directly its sendTextMessage method from the GCKCastChannel, failing to implement some of the messaging aspects specific to the media channel (in particular failing to wrap the message in a media-style envelope with the requestId and mediaSessionID attributes)
Has anybody else encountered this? Am I missing something? Is there a workaround?
I followed the recommendation on the ticket I created, messaging to the receiver media app using a custom namespace using GCKCastChannel instead of he dedicated GCKMediaControlChannel to work around the issue. The ticket response confirms "don't use sendTextMessage directly with the GCKMediaControlChannel"
I have created a class NetCalculator which I am calling when a button is pressed. The method calculate network it gets 2 NSStrings and returns an id object (either "Network" object or "UIAlertView". Then I am checking which object is and I present the data. When I am using the UIAlertView the app is crashing after showing 2-3 alerts.
Any ides why this happens? On terminal it doesnt show any error just some random hexadecimal.
-(IBAction)calculate:(id)sender {
id result;
Network *network = [[Network alloc]init];
NetCalculator *netCalculated = [[NetCalculator alloc] init];
result = [netCalculated calculateNetworkWithIP:ipLabel.text andSubnet:subnetLabel.text];
if([result isKindOfClass:[Network class]]){
network = result;
NSLog(#"network %#",network.networkIP);
}
else if([result isKindOfClass:[UIAlertView class]]) {
UIAlertView *alert;
alert = result;
[alert show];
}
};
Your code is quite strange to me. Your method calculateNetworkWithIP could return a Network result or a UIAlertView result. I wouldn't follow such an approach.
If the problem relies on memory you should show us hot that method is implemented.
Anyway, I would propose some changes (The following code does not take into account ARC or non ARC code). In particular, I would modify the calculateNetworkWithIP to return a Network result. An error will populated if a problem arises and it is passed as an argument.
- (Network*) calculateNetworkWithIP:(NSString *)ip subnet:(NSString*)subnet error:(NSError**)error
If all is ok, the result would be be a Network and so print it or reused it somewhere. Otherwise an NSError would be returned and based on that create and show an alert view.
So, here pseudo code to do it.
NetCalculator *netCalculated = [[NetCalculator alloc] init];
NSError* error = nil;
Network* networkResult = [netCalculated calculateNetworkWithIP:ipLabel.text subnet:subnetLabel.text error:&error];
if(error != nil) {
// create and show an alert view with the error you received
} else {
// all ok so, for example, save the result in a instance variable
}
To follow a similar approach you can take a look at why is "error:&error" used here (objective-c).
I've developed some iOS 6.1 code to deal with NSError. But, I'm not happy with it. It is at best a hack:
-(bool) reptErrAtModule: (NSString *) module
atSubr: (NSString *) subr
atFunc: (NSString *) func
withErr: (NSError *) err
{
id value = [[err userInfo] objectForKey: NSUnderlyingErrorKey];
NSString * errDesc = (value != nil) ?
[value localizedDescription]:
(NSString *)[[err userInfo] objectForKey: #"reason"];
NSLog( #"ERR -> %#",[NSString stringWithFormat:
#"(%#>%#) %# failed! %#",module,subr,func,errDesc] );
}
I had a simpler form (without the (NSString *)[[err userInfo] objectForKey: #"reason"] case) and it worked for errors that I got back from calls to removeItemAtPath.
But then I got an error back from this code:
NSPersistentStore * entStor =
[myPerStoCor addPersistentStoreWithType: NSSQLiteStoreType
configuration: nil
URL: [NSURL fileURLWithPath: Path]
options: nil
error: &err];
And my routine failed to extract the error. So I added the #"reason" logic because I could see the text I wanted in the Info data in the debugger.
Now the code works with both types of errors but I'm thinking this is not the way to do this. There must be a better, more generic way to deal with all the types of errors stuff the system can give you back in NSError.
I use this:
NSString *description = error.localizedDescription;
NSString *reason = error.localizedFailureReason;
NSString *errorMessage = [NSString stringWithFormat:#"%# %#", description, reason];
For debugging purposes, you ideally want to log out the entire contents of the error. Roughly speaking this is the domain, code, and userInfo. Bear in mind that userInfo might well include an underlying error, which you want to apply the same logic to. And in some cases, the error might supply a description (or failure reason etc.) which isn't present in the userInfo.
If you scroll down my blog post at http://www.mikeabdullah.net/easier-core-data-error-debugging.html, there's a snippet there showing how to generate a dictionary representation of an NSError object, and then get a string representation of that. This is pretty handy for debugging/logging purposes.
For presentation to users though, -[NSError localizedDescription] is expressly designed for such purposes. -localizedFailureReason serves a similar role, tending to specify what went wrong, without the context of the operation being tried. (One way to think of it is localizedDescription = task desceription + localizedFailureReason)
I'm trying to test the behavior of my app when connection fails. I am testing on an iPad with wifi turned off. When Restkit attempts a web service call, I get the following error:
CPL[7713:6203] E restkit.network:RKRequest.m:545 Failed to send request to https://xxxxxxxx/APNS_WebService/rest/operations/initializeDevice?deviceID=c4a17f855d3cc824b174b71908480d4e505ebfb221cb4643da9270a07344c367 due to unreachable network.
The problem is that I would like to handle this situation in a delegate callback method, but none of the delegate methods are being called. I have set the delegate on the request, and have requestDidFailLoadWithError, requestDidCancelLoad, requestDidTimeout, and objectLoaderDidFailWithError implemented. None of these are called.
Why aren't my delegates being called?
EDIT: After setting a breakpoint inside RKRequest.m, I see that the following line is in fact being executed:
[self performSelector:#selector(didFailLoadWithError:) withObject:error afterDelay:0];
However, my delegate methods are not getting called.
Here's where I set the delegate:
request = [client requestWithResourcePath:[NSString stringWithFormat:#"/initializeDevice?deviceID=%#",deviceID]];
request.delegate=self;
[request sendAsynchronously];
EDIT 2: Actually, the line in RKRequest.m that I posted above is just calling another method in RKRequest, except that it's not. Putting a breakpoint in didFailLoadWithError shows that this code is never reached. I don't get why that's not working.
Changing the performSelector to a regular method call appears on the surface to give me the behavior I'm looking for. Is this going to break anything? I guess I'm not sure why performSelector is being used to call a method in the same class.
EDIT 3: As requested, here's my delegate method:
-(void)request:(RKRequest *)request didFailLoadWithError:(NSError *)error{
NSLog(error.domain);
NSLog([NSString stringWithFormat:#"%d",error.code]);
NSLog(error.localizedDescription);
NSLog(error.localizedFailureReason);
[request reset];
[request send];
}
EDIT:
Actually, the line in RKRequest.m that I posted above is just calling another method in RKRequest, except that it's not. Putting a breakpoint in didFailLoadWithError shows that this code is never reached. I don't get why that's not working.
This is really strange. I would try doing a full clean of the project and rebuild.
As to what entails a direct call instead of using performSelector, you can see that afterDelay:
[self performSelector:#selector(didFailLoadWithError:) withObject:error afterDelay:0];
this will make the didFailLoadWithError: method be called at the next iteration of the run loop. I would keep this way of calling it.
You could try, though, with this alternative:
dispatch_async(dispatch_get_current_queue(), ^() {
[self didFailLoadWithError:error]; } );
I would suggest setting a breakpoint inside of the RestKit method you are using (I suppose sendAsynchronously) and check what happens. If you look into the method definition, the call to the delegate is effectively there:
} else {
self.loading = YES;
RKLogError(#"Failed to send request to %# due to unreachable network. Reachability observer = %#", [[self URL] absoluteString], self.reachabilityObserver);
NSString* errorMessage = [NSString stringWithFormat:#"The client is unable to contact the resource at %#", [[self URL] absoluteString]];
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
errorMessage, NSLocalizedDescriptionKey,
nil];
NSError* error = [NSError errorWithDomain:RKErrorDomain code:RKRequestBaseURLOfflineError userInfo:userInfo];
[self performSelector:#selector(didFailLoadWithError:) withObject:error afterDelay:0];
}