Help with a memory leak - objective-c

I made a class that uses NSURLConnection and KVC to make objects from plists on a server. Instruments says I have a bunch of memory leaks coming from the function that handles the data returned from the server:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
hasOutstandingCall = NO;
NSError* sError;
NSDictionary* tmpDict = [NSPropertyListSerialization propertyListWithData:receivedData
options:NSPropertyListImmutable
format:NULL error:&sError];
self.lastResponse = [ServerResponse new];
//NSLog(#"server responded: %#",tmpDict);
[lastResponse setValuesForKeysWithDictionary:tmpDict];
if(tmpDict.count == 0){
lastResponse.success = NO;
lastResponse.errorId = -1;
lastResponse.errorMessage = #"Couldn't understand server response";
[[NSNotificationCenter defaultCenter] postNotificationName:#"serverDidRespond" object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:#"requestDidFail" object:self];
NSLog(#"failed to deserialize response");
NSString* serverMessage = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
NSLog(#"SERVER SAID: %#",serverMessage);
[serverMessage release];
[receivedData release];
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"serverDidRespond" object:self];
if(lastResponse.success){
if(lastResponse.resultQuery)
NSLog(#"%# response: query with %d rows",lastFName, lastResponse.resultQuery.count);
else if(lastResponse.resultObject)
NSLog(#"%# response: object",lastFName);
[[NSNotificationCenter defaultCenter] postNotificationName:#"requestDidSucceed" object:self];
}else{
NSLog(#"%# response: ERROR id: %d, message: %#",lastFName, lastResponse.errorId, lastResponse.errorMessage);
[[NSNotificationCenter defaultCenter] postNotificationName:#"requestDidFail" object:self];
}
[receivedData release];
}
Instruments says I am leaking a server response, and a bunch of other things that pass through this function. Does the "Responsable Frame" thing always refer to whatever originally created the leaked object no matter what? Should I just be looking for how this stuff gets leaked down the road or do I have a problem here in this function? From what I understand, tempDict, sError are autoreleased when they come back from serialization. I'm sending the supposedly leaked serverResponse into a synthesized setter that gets released in the dealloc method, so I don't see what the problem is. Anyone got some insight?

self.lastResponse = [ServerResponse new];
That is most likely a double retain, assuming that lastResponse is an #property declared as retain (or your setter retains).
When Instruments identifies a leak, it shows where the item was allocated, but that may not be the cause of the leak. The leak will always be an unbalanced retain, but only sometimes will that retain be on the same line of the allocation (as it was in this case, apparently).

Related

Where to from AppDelegate? how to go back to previous view and access instance

I have an instance in a view called "PayView" and at the meantime I am doing openURL to open a separate app and to pass some data to it. This second app process the information I send and gives the response back.
At Appdelegate.m I have this handleOpenUrl which receives the response sent by the second app. Once I receive the response back I would like to go back to my "PayView" and use the response received from the second app along with already existing values I have in the instance.
My problem is as soon as the response from the second app reaches the appdelegate and reached the section "return yes", my main app goes back to this "PayView" and does nothing.
So how do I use the response object I received from appdelegate at my PayView along with already existing instance values?
I thought about using a global variable to store the "payView" instance/object and initiate a new instance from appdelegate for "Payview" and use the global along with the response json from appdelegate. However, I found many forums advising against using global.
So, ignoring the global and crating a new instance for "payview" causes loss of all previously stored data.
I am not an experienced iOS programmer and just work momentarily on someone else code.So, hope I explained my issue/question.
It would be great if I could get some valuable inputs :)
My appdelegate.m look like this,
-(BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url{
if (!url) {
return NO;
}
// Unencode the URL's query string
NSString *queryString = [[url query] stringByRemovingPercentEncoding];
// Extract the JSON string from the query string
queryString = [queryString stringByReplacingOccurrencesOfString:#"response=" withString:#""];
// Convert the JSON string to an NSData object for serialization
NSData *queryData = [queryString dataUsingEncoding:NSUTF8StringEncoding];
// Serialize the JSON data into a dictionary
NSDictionary* jsonObject = [NSJSONSerialization JSONObjectWithData:queryData options:0 error:nil];
NSString *response = [jsonObject objectForKey:#"Result"]; //Get the response
UIAlertView *alert1 = [[UIAlertView alloc] initWithTitle:#"Result" message:response delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert1 show];
//PayView *pdv = [[PayViewController alloc] init];
//[pdv UseTransactionResult:jsonObject] ;
return YES;
}
and this is how I am calling open url from PayView
[[UIApplication sharedApplication]openURL:[NSURL URLWithString:[NSString stringWithFormat:#"mysecondapp://v2/data/?request=%#",requestEncodedString]] options:#{} completionHandler:^(BOOL success) {
if (success)
{
NSLog(#"Opened url");
}
}];
Edit 1:
Hi #ekscrypto, thanks for your valuable input. It is really helpful. I just have one problem with this. It is working just fine when I do the following in my PayView
Receiver:
[[NSNotificationCenter defaultCenter] addObserverForName:#"ActionIsComplete" object:nil queue:nil usingBlock:^(NSNotification *note){
       
        //Completed Action
        NSString *response = [note.userInfo objectForKey:#"Result"]; //Get the response
        NSLog(response);
        [self dismissViewControllerAnimated:YES completion:nil];
   
    }];
 
However, when I try to do the same in the following  method I get error "unrecognized selector sent to instance"
Receiver:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ReceiveActionIsComplete:) name:#"ActionIsComplete" object:nil];
-(void)ReceiveActionIsComplete:(NSNotification *) notification
{
NSDictionary *jsonObject = (NSDictionary *)notification.userinfo;
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"ActionIsComplete" object:nil];
Status = [jsonObject objectForKey:#"Status"];
Result = [jsonObject objectForKey:#"Result"];
NSLog([NSString stringWithFormat:#"%#%#",#"Status is: ", Status]);
NSLog([NSString stringWithFormat:#"%#%#",#"Result is: ", Result]);
}
in both cases my sender at Appdelegate looks like this.
Sender:
[[NSNotificationCenter defaultCenter] postNotificationName:#"ActionIsComplete" object:nil userInfo:jsonObject];
FYI: I tried sending the object in object instead of userInfo as well.
So what I am doing wrong? could you please help me.
Conclusion:
Sender: (AppDelegate.m)
[[NSNotificationCenter defaultCenter] postNotificationName:#"ActionIsComplete" object:nil userInfo:jsonObject];
Receiver: (PayView.m)
under - (void)viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ReceiveActionIsComplete:) name:#"ActionIsComplete" object:nil];
and the function to receive the results
-(void)ReceiveActionIsComplete:(NSNotification *) notification
{
NSDictionary *jsonObject = (NSDictionary *)notification.userInfo;
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"ActionIsComplete" object:nil];
Status = [jsonObject objectForKey:#"Status"];
Result = [jsonObject objectForKey:#"Result"];
NSLog([NSString stringWithFormat:#"%#%#",#"Status is: ", Status]);
NSLog([NSString stringWithFormat:#"%#%#",#"Result is: ", Result]);
}
You could register a NotificationCenter observer in your PayView, and post a notification from your AppDelegate when the response is received. Via the notification object you can forward any information you need.
Let's assume that you define a struct with the information you want to pass along:
struct PaymentConfirmation {
let amount: Float
let confirmationNumber: String
}
In your PayView:
class PayView: UIView {
...
static let paymentProcessedNotification = NSNotification.Name("PayView.paymentProcessed")
let paymentProcessedObserver: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
paymentProcessedObserver = NotificationCenter.default.addObserver(
forName: PayView.paymentProcessedNotification,
object: nil,
queue: .main) { [unowned self] (notification) in
guard let confirmation = notification.object as PaymentConfirmation else { return }
self.paymentProcessed(confirmation)
}
}
deinit {
NotificationCenter.default.removeObserver(paymentProcessedObserver)
}
func paymentProcessed(_ confirmation: PaymentConfirmation) {
// do stuff
}
Then in your AppDelegate:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
// do stuff required with the callback URL
let confirmation = PaymentConfirmation(
amount: 3.0,
confirmationNumber: "Pay830da08.8380zSomething")
NotificationCenter.default.post(
name: PayView.paymentProcessedNotification,
object: confirmation)
}
For more information, check out https://medium.com/ios-os-x-development/broadcasting-with-nsnotification-center-8bc0ccd2f5c3

why MPMoviePlayerViewController having different behavior with the same URL?

I implemented MPMovieViewController in my app but it has a problem i.e.when i play video in player then first time it is failed to play but next time it will play successfully with the same URL. I am not able to understand this different behavior with the same URL. I copied code here which i used in my app.
NSURL *fileURL = [NSURL URLWithString:#"http://nordenmovil.com/urrea/InstalaciondelavaboURREAbaja.mp4"];
MPMoviePlayerViewController * controller = [[MPMoviePlayerViewController alloc]initWithContentURL:fileURL];
controller.moviePlayer.movieSourceType= MPMovieSourceTypeFile;
[controller.moviePlayer setControlStyle:MPMovieControlStyleDefault];
[controller.moviePlayer prepareToPlay];
[controller.moviePlayer play];
[self presentMoviePlayerViewControllerAnimated:controller];
I suggest to handle the error for see what happens with your URL video (this is a tip that I founded long time ago in this answer)
Add this for capture the end of play:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleMPMoviePlayerPlaybackDidFinish:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
and this for see what happens:
- (void)handleMPMoviePlayerPlaybackDidFinish:(NSNotification *)notification
{
NSDictionary *notificationUserInfo = [notification userInfo];
NSNumber *resultValue = [notificationUserInfo objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey];
MPMovieFinishReason reason = [resultValue intValue];
if (reason == MPMovieFinishReasonPlaybackError)
{
NSError *mediaPlayerError = [notificationUserInfo objectForKey:#"error"];
if (mediaPlayerError)
{
NSLog(#"playback failed with error description: %#", [mediaPlayerError localizedDescription]);
}
else
{
NSLog(#"playback failed without any given reason");
}
}
}
Don't forget to remove the notification handler:
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
With this workaround you should know the error with your stream, I hope this helps!

EXC_BAD_ACCESS error help

I am new to objective-c and i cannot figure out how memory handling works exactly in this language. Here is some code i wrote from a turorial and i am confused why when i uncomment the [filePath release] i get an error even though the method is finished. I read some articles on how memory handling works but i cant see what i am doing wrong here.
#import "saaving_dddaaattaViewController.h"
#implementation saaving_dddaaattaViewController
#synthesize field;
-(NSString *)pathOfFile {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsFolder = [paths objectAtIndex:0];// paths[0] = documents directory
return [documentsFolder stringByAppendingFormat:#"myfile.plist"];
}
-(void)applicationWillTerminate:(NSNotification *)notification {
NSLog(#"Saving data...");
NSMutableArray *array = [[NSMutableArray alloc]init];
[array addObject:field.text];
[array writeToFile:[self pathOfFile] atomically:YES];
[array release];
}
- (void)dealloc {
[field release];
[super dealloc];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#pragma mark - View lifecycle
- (void)viewDidLoad {
NSString *filePath = [self pathOfFile];
if ([[NSFileManager defaultManager]fileExistsAtPath:filePath]) {
NSLog(#"File[%#] does exist.", filePath);
NSArray *array = [[NSArray alloc]initWithContentsOfFile:filePath];
field.text = [array objectAtIndex:0];
[array release];
}
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:app];
//[filePath release];// <--- commented out release
[super viewDidLoad];
}
- (void)viewDidUnload {
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
Memory Management can be confusing at first. Only release objects that you have created. This is almost only if you use the words alloc, init, retain.
The problem in your case is that you do not actually own the object, it is autoreleased, because you did not alloc, init or retain it. This is fine, but it is not your job to release it, so don't worry about it.
If you get "EXC_BAD_ACCESS" errors later, it might be helpful to use NSZombies to help find where you are releasing incorrectly. They work by placing a "zombie" in memory wherever you release an object so it is easier to tell what the problem is.
EDIT: For example, say you have:
NSString *foo = [[NSString alloc] initWithString:#"foo"];
NSString *bar = [NSString stringWithString:#"bar"];
You would have to release foo, by calling: [foo release]; at some point, but you would not have to release bar because it you did not use alloc to allocate memory for it. This goes for any type of object, not just NSString. A great website explaining this can be found here.

Why is my value passed through NSNotifcationCenter not preserved?

I'm trying to send a CGPoint through an NSNotification like this
-(void)setPosition:(CGPoint)point
{
NSString *pointString = NSStringFromCGPoint(point);
NSDictionary *dict = [[NSDictionary alloc]
initWithObjectsAndKeys:#"p", pointString, nil];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"BownceSpriteDidSetPosition"
object:self
userInfo:dict];
[super setPosition:CGPointMake(point.x, point.y)];
}
And I've implemented the observer like this
-(void) init
{
if((self = [self init])){
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(setViewPointCenter:)
name:#"BownceSpriteDidSetPosition"
object:nil];
// I wondered wether 'object' should be something else???
// more code etc....
}
return self
}
-(void) setViewPointCenter:(NSNotification *)notification
{
NSString * val = [[notification userInfo] objectForKey:#"p"];
CGPoint point = CGPointFromString(val);
// trying to debug
NSString debugString = [NSString stringWithFormat:#"YPOS -----> %f", point.y];
NSLog(debugString);
CGPoint centerPoint = ccp(240, 160);
viewPoint = ccpSub(centerPoint, point);
self.position = viewPoint;
}
But it seems that CGPoint is empty, or (0,0) maybe. Either way, it's not having the desired effect, and the debugString is showing point.y to be 0.0.
From all the examples I've found, it looks to me like I'm doing it all right. But obviously I'm not. Can anyone nudge me in the right direction and point out my mistake?
You've got your objects and keys reversed in the dictionary. It should read
NSDictionary *dict = [[NSDictionary alloc]
initWithObjectsAndKeys:pointString,#"p", nil];
Yes, it's exactly backwards of the way you would expect it to be and this bites me about every third time I create a dictionary.
Your problem is here:
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:#"p", pointString, nil];
It should be:
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:pointString, #"p", nil];
"Objects" comes before "Keys" in the selector, so you list your items as ObjectA, KeyForObjectA, ObjectB, KeyForObjectB, etc.
You're also leaking this dictionary, since you alloc/init it, but never release it (I'm assuming you're not using garbage collection).
In new objective-c syntax is better to use:
NSDictionary *dict = #{#"p": [NSValue valueWithCGPoint:point]};
it easier to understend and it use NSValue instead of NSString.
There is also a problem with removing observer. In your code, you only use [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(setViewPointCenter:) name:#"BownceSpriteDidSetPosition" object:nil]; but never call [[NSNotificationCenter defaultCenter] removeObserver:self];, whitch can produce nasty crash, that would be hard to debug. I sugest you using library https://github.com/AllinMobile/AIMObservers that prevents this kind of crash. You could rewrite your code in that way:
__weak __typeof(self) weakSelf = self;
self.observer = [AIMNotificationObserver observeName:#"BownceSpriteDidSetPosition" onChange:^(NSNotification *notification) {
NSValue *valueOfPoint = [notification userInfo][#"p"];
CGPoint point = [valueOfPoint CGPointValue];
CGPoint centerPoint = ccp(240, 160);
viewPoint = ccpSub(centerPoint, point);
//use weakSelf to avoid strong reference cycles
weakSelf.position = viewPoint;
}];

NSFileHandle readInBackgroundAndNotify does not work

Hi I'm using NSFileHandle's readInBackgroundAndNotify method to get notifications when a log file has been updated.
I have the following code:
- (void)startReading
{
NSString *logPath = [NSHomeDirectory() stringByAppendingPathComponent:#"Library/Logs/MyTestApp.log"];
NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:logPath];
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:#selector(getData:)
name:NSFileHandleReadCompletionNotification
object:fh];
[fh readInBackgroundAndNotify];
}
- (void) getData: (NSNotification *)aNotification
{
NSLog(#"notification received");
}
However the selector is never called and the notification is not received.
Add an NSLog to startReading to make sure that's getting called.
Log fh. My guess is that it's nil (most probably because you haven't created MyTestApp.log yet).