Objective C Manual Reference Counting in OSX vs iOS targets; Xcode settings - objective-c

Currently I'm investigating a memory leak issue in some code written in MRC (Manual Reference Counting, non-ARC).
To check Xcode's measurement tool against my code, I intentionally removed "release" from one the class method (stepSimulation, periodically called in the platform) and was hoping to see "memory leak" in the measurement's memory leak monitor.
The thing is, in iOS target, Xcode's measurement correctly detects "memory leak" exactly from the method that misses "release". (The stack trace narrows down to that method correctly.)
However, in OSX target, the measurement doesn't detect any memory leaks. Even after quite long time run, the memory usage doesn't increase at all, it runs just as if it was built with ARC.
Also I have tested few more things in MRC; intentionally over-releasing variable to cause crash. The code behaves correctly as MRC dictates, it crashes in iOS, but it doesn't crash in OSX. I have checked retainCount, too, (I know retainCount not only reflects users' access but also systems', too.) Somehow, the retainCount in OSX never changes, on the contrary it changes as expected in iOS.
Basically the code in iOS target runs exactly as MRC says, but it doesn't in OSX.
I suspect if I had missed any options to build target in OSX, but I am pretty sure that the code is built without ARC in both OSX and iOS, otherwise the compiler would have complained against using "release". Also I have added the compiler flag "-fno-objc-arc" to the "mm file" of that class.
Are there any further options in Xcode that I have to set to build "MRC" binary correctly in OSX?
I know this is a kind of question hard to answer unless a lot of information regarding Xcode settings provided, however, I wonder if there's anyone who had similar issues before and know some tips. If so, much appreciated if I could share the info.
OSX: macOS BigSur v11.2.3, iMac-2017
iOS: v12.5.4, iPhone6
Xcode: v12.5.1(12E507)
#interface SomeClass : NSObject{
NSDate* _lastStepTime;
}
.....
-(id) init;
-(void) stepSimulation; // called periodically at every frame cycle
#end
#implementation SomeClass
-(id) init{
...
_lastStepTime = [[NSDate alloc] init];
...
}
.......
-(void) stepSimulation
{
// rc: currentTime's refererence count
NSDate* currentTime = [[NSDate alloc] init]; // rc = 1
NSTimeInterval timeInterval = [currentTime timeIntervalSinceDate:_lastStepTime];
// do some processing upon the given time interval
someProcessingForTimeInterval(timeInterval);
// update the time mark
[_lastStepTime release];
_lastStepTime = [currentTime retain]; // rc = 2
// removing "release" to intentionally create memory leak;
// [currentTime release];
}
#end

Related

Objective-c example code that intentionally messages a deallocated object

I'm new to objective-c and xcode and an app I'm currently writing is receiving the infamous EXC_BAD_ACCESS error.
Almost everybody recommends starting to solve the problem using NSZombies. I think I have NSZombies working but xcode isn't giving me an alert about a zombie being messaged when my app crashes.
Before I move on with my debugging, I'd like to run some code that should for sure result in a message being sent to a zombie (de-allocated object).
What is a simple code snippet where a message is sent to a deallocated object, causing a scenario where NSZombies should alert me?
For non-ARC code:
- (IBAction) messageZombie:(id)sender {
id a = [[NSObject alloc]init];
[a release];
NSLog(#"%#", [a description]);
}
This will give you EXC_BAD_ACCESS with Zombies off, and a "message sent to deallocated instance" message, with Zombies enabled.
If your project is using ARC, then it's a bit harder to reliably-cause messages to de-allocated objects (that is the point of ARC, after all).
This works:
- (IBAction) messageZombie:(id)sender {
id a = [[NSObject alloc]init];
id __unsafe_unretained b =a;
a=nil;
NSLog(#"%#", [b description]);
}
It's probably not very similar to what your actual code is doing, because who the heck uses __unsafe_unretained, anyway? But if you just want to make sure that you've got NSZombies turned on properly, this should be a reasonable test case.
If you're looking for suspicious places in your code, then for sure look for __unsafe_unretained pointers, though you won't find any*, and double-check that the right casts are used for CoreFoundation objects that are casted to Cocoa objects.
* If your project needs to support OS X versions before 10.7, or iOS versions earlier than 5.0, then you can't use __weak pointers, so in that sort of project, you'd expect to find __unsafe_unretained used more often.
You could create a CF object, bridge it to an Objective-C object, then release it and try to use the bridged object. I think you have to use __bridge to get this to behave the way you want.

How to enforce using `-retainCount` method and `-dealloc` selector under ARC?

Under ARC, compiler will forbid using any method or selector of -retainCount, -retain, -dealloc, -release, and -autorelease.
But sometimes I want to know the retain counts at runtime, or using method swizzling to swap the -dealloc method of NSObject to do something.
Is it possible to suppress (or bypass) the compiler complaining just a couple of lines of code? I don't want to modify ARC environment for whole project or whole file. I think preprocessor can do it, but how?
Additions:
Thanks guys to give me a lesson about the use of -retainCount. But I wonder whether it is possible to enforce invoking/using those forbidden methods/selectors.
I know Instruments is a powerful tool to do this job. But I am still curious about those question.
Why do I want to use -retainCount:
When using block, if you don't specify a __weak identifier on the outside variables, the block will automatically retain those outside objects in the block after the block is copied into the heap. So you need to use a weak self to avoid retain cycle, for example:
__weak typeof(self) weakSelf = self;
self.completionBlock = ^{
[weakSelf doSomething];
};
However, it will still cause retain cycle when you only use instance variables in the copied block (YES, although you didn't use any keyword self in the block).
For example, under Non-ARC:
// Current self's retain count is 1
NSLog(#"self retainCount: %d", [self retainCount]);
// Create a completion block
CompletionBlock completionBlock = ^{
// Using instance vaiable in the block will strongly retain the `self` object after copying this block into heap.
[_delegate doSomething];
};
// Current self's retain count is still 1
NSLog(#"self retainCount: %d", [self retainCount]);
// This will cuase retain cycle after copying the block.
self.completionBlock = completionBlock;
// Current self's retain count is 2 now.
NSLog(#"self retainCount: %d", [self retainCount]);
Without using -retainCount before/after the copied block code, I don't think this retain cycle caused by using instance variables in the completion block will be discovered easily.
Why do I want to use -dealloc:
I want to know whether I can use method swizzling to monitor which object will be deallocated by logging messages on the Xcode console when the -dealloc is invoked. I want to replace the original implementation of -dealloc of NSObject.
That's not recommened at all, I dont know your intentions but they dont sound very safe.
The use of retainCount is not recommended.
From AppleDocs:
This method is of no value in debugging memory management issues.
Because any number of framework objects may have retained an object in
order to hold references to it, while at the same time autorelease
pools may be holding any number of deferred releases on an object, it
is very unlikely that you can get useful information from this method
And, if there's any doubt, check this link:
http://whentouseretaincount.com/
Whatever you are trying to do, please dont.
For future references, I'm going to add some linsk to help you understand the process of how memory works in iOS. Even if you use ARC, this is a must know (remember that ARC is NOT a garbage collector)
Beginning ARC in iOS 5 Tutorial Part 1
Understand memory management under ARC
Memory Management Tutorial for iOS
Advance Memory Managment
And, of course, once you understand how memory works is time to learn how to profile it with instruments:
Instruments User Guide
Agreed 100% with the other commenters about the fact that you do not want to use -retainCount. To your other question, however, about -dealloc:
You also do not want to swizzle -dealloc. If you think you want to swizzle it, you don't understand how it works. There are a lot of optimizations going on there; you can't just mess with it. But, as #bbum hints at, you can easily get notifications when objects are deallocated, and this can be very useful.
You attach an associated object to the thing you want to watch. When the thing you want to watch goes away, so does the associated object, and you can override its dealloc to perform whatever action you want. Obviously you need to be a little careful, because you're in the middle of a dealloc, but you can generally do most anything you'd need to here. Most importantly for many cases, you can put a breakpoint here or add a logging statement, so you can see where the object was released. Here's a simple example.
With ARC
const char kWatcherKey;
#interface Watcher : NSObject
#end
#import <objc/runtime.h>
#implementation Watcher
- (void)dealloc {
NSLog(#"HEY! The thing I was watching is going away!");
}
#end
NSObject *something = [NSObject new];
objc_setAssociatedObject(something, &kWatcherKey, [Watcher new],
OBJC_ASSOCIATION_RETAIN);
Without ARC
const char kWatcherKey;
#interface Watcher : NSObject
- (void)lastRetainDone;
#end
#import <objc/runtime.h>
// turn off ARC!
#implementation Watcher
{
BOOL noMoreRetainsAllowed;
}
- (void)lastRetainDone {
noMoreRetainsAllowed = YES;
}
- (id) retain {
if (noMoreRetainsAllowed) abort();
return [super retain];
}
- (void)dealloc {
NSLog(#"HEY! The thing I was watching is going away!");
[super dealloc];
}
#end
...
NSObject *something = [NSObject new];
Watcher *watcher = [Watcher new];
objc_setAssociatedObject(something, &kWatcherKey, watcher,
OBJC_ASSOCIATION_RETAIN);
[watcher lastRetainDone];
[watcher release];
Now, when something goes away, -[Watcher dealloc] will fire and log for you. Very easy. Completely supported and documented.
EDIT:
Without using -retainCount before/after the copied block code, I don't think this retain cycle caused by using instance variables in the completion block will be discovered easily.
You are somewhat correct here, but there are two lessons to be learned, and neither is to use retainCount (which won't actually help you in this case anyway because retainCount can very often be something you don't expect).
The first lesson is: Do not allow any warnings in ObjC code. The situation you're describing will generally create a compiler warning in recent versions of clang. So it's actually quite easy to discover in many cases. The way you've separated it into multiple assignments, the compiler may miss it, but the lesson there is to change your coding style to help the compiler help you.
The second lesson is: don't access ivars directly outside of init and dealloc. This is one of the many little surprises that can cause. Use accessors.

Cocos2D 2.0 ARC enabled uncontrolled deallocs

I recently migrated an existing Cocos2D project from version 0.8 to 2.0 & enabled ARC.
The way I did it is by Apple's empty application template & then adding the code from the Cocos2d 2.x template since it has major changes. After that I added code from the game & made the necessary changes for the deprecated code & for the ARC issues.
Since that the game is working but not as expected, I had no animations & the game was taking the whole CPU power. From the console I saw that everything gets dealloc-ed right after it's creation. My old code is not the reason for that because it even happens before any of my scenes gets pushed.
EDIT
I also repeated again the whole process & made an ARC-enabled version from the Cocos2D template project, but the same there too.. Is that a normal thing maybe?
That's not normal, although common problem when converting to ARC. ARC will release objects out of scope, whereas under MRC an alloc/init object would stay in memory (and leak). Check where you may need to keep a strong reference.
Here's an example that worked before converting to ARC:
-(void) someMethod
{
id object = [[MyObject alloc] init];
}
Under MRC, object stays in memory (leaks) after someMethod returns. Under ARC, ARC cleans up the object when the method returns. The simplest fix is to turn object into an ivar (aka instance variable, member of class).
Also check singletons. Depending on how its implemented, the Singleton class might dealloc right away. For example if the static instance is declared __weak or __unsafe_unretained.
You should also run the Xcode Analyzer (Build -> Analyze) to get pointers for potential issues.

Memoryleak on navigation of viewcontroller in iOS5

I have done navigation one viewcontroller to another, But when I done navigation multiple times my application shows memory leak, I run application with NSZombie detection ,that shows "Insufficient task_for_pid privileges (LeakAgent)". It is ARC enabled(xcode 4.2, iOS5) Please help.......
My code is here:
self.locationMapDetail = [[LocationMapDetail alloc] init]; //14.3% Leak
self.locationMapDetail.objItem = [self.parsedItems objectAtIndex:selectedIndex]; //5.7% Leak
[self.navigationController pushViewController:locationMapDetail animated:YES]; //80.3% Leak
self.locationMapDetail = nil;
self.locationMapDetail.objItem = nil;
You must release the objects before making it nil.Something like
[self.locationMapDetail release];
self.locationMapDetail = nil;
You should do it for all the objects that you allocate , if you are not using ARC.
In case of strong pointers even if you release the object you might not release it efficiently or it may be only virtually released as a strong pointer can be released only if all the references of the strong pointer has been released.It happens because sometimes the target may have strong pointer reference back to the declared variable.
Also Please mention the method you are using to create your variables..(viewDidLoad or viewWillAppear).It is advisable to create it in viewDidLoad.
I have also faced the same issue, It took me almost 2 days to resolve the issue. Finally got the solution.
Make sure that your profile scheme is in DEBUG mode instead of RELEASE. This will solve the issue.

Double releasing when it shouldn't be happening

I am really puzzled by this. I believe I am managing memory the correct way but executing the code suggests that I am double releasing the object. Here is the code and then I'll explain what is happening.
#protocol SomeDelegate <NSObject>
#required
- (id)initWithCols:(NSUInteger)Cols Rows:(NSUInteger)Rows;
#end
#interface SomeObject : NSObject <SomeDelegate> {
}
- (id)initWithCols:(NSUInteger)Cols Rows:(NSUInteger)Rows;
#end
#interface Layout : UIView {
id<SomeDelegate> someDelegate;
}
#property(retain) id<SomeDelegate> someDelegate;
- (id)initWithFrame:(CGRect)aRect Cols:(NSUInteger)Cols Rows:(NSUInteger)Rows;
#end
#implementation Layout
#synthesize someDelegate;
- (id)initWithFrame:(CGRect)aRect Cols:(NSUInteger)Cols Rows:(NSUInteger)Rows {
if(self = [super initWithFrame:aRect]) {
cols = Cols;
rows = Rows;
id<SomeDelegate> delegate = [[SomeObject alloc] initWithCols:cols Rows:rows];
[self setSomeDelegate:delegate];
//[delegate release];
}
return self;
}
-(void)dealloc {
[someDelegate release];
[super dealloc];
}
#end
Now when I uncomment out the "//[delegate release];" line in the constructor of the Layout class, then I get a "EXC_BAD_ACCESS" error and the application crashes when it attempts to dealloc. I have traced the crash to the release of the someDelegate object in the dealloc method of Layout class. If I leave it commented then the application works fine.
Can someone please explain why this is happening as it appears to be going against everything I have read about memory management in Objective-C.
Just a note that the code example actually works, however my code doesn't which follows the example. Could there be something inside of my actual SomeObject that is causing an autorelease?
Thanks in advance.
First, go back and reread the memory management rules just to make sure you are not missing anything obvious in your use of delegate elsewhere.
Next, turn on NSZombieEnabled (in your executable settings, Arguments panel, add an environment variable NSZombieEnabled set to YES).
Then add a dealloc method to your delagate if it does not have one already (make sure you call [super dealloc]!) and put a break point on there - that will tell you when your delagate is deallocated which will tell you when it is being released.
Alternatively, add trivial release/autorelease methods to your delegate class which do nothing but call through, and then breakpoint them and that will tell you exactly when it is being released.
Three final comments: in the standard naming convention for Objective C/Cocoa, you should have lowercase parameter fields, ie it should be:
- (id)initWithFrame:(CGRect)aRect cols:(NSUInteger)Cols rows:(NSUInteger)Rows;
When your ivar and property are named identically, it is very easy to accidently use the wrong one, so I recommend using a different ivar name and property name to avoid confusion, either use an _ prefix like Apple, or some other prefix to avoid confusion with Apple as well:
id<SomeDelegate> _someDelegate;
#synthesize someDelegate = _someDelegate;
And Apple recomends against using setters/getters in init/dealloc, so your init code should be:
_someDelegate = [[SomeObject alloc] initWithCols:cols Rows:rows];
As alluded to in the comments, the problem does not appear to be in the code posted.
I could ask for more information, but I'm firmly in the teach a man to fish camp....
A crash in -release will often be misleading in that various optimizations -- tail call optimizations, generally -- will make it look like the crash happened a frame or two above the actual call that crashed. When the crash happens, there isn't enough info on the stack to really identify the culprit.
Whenever you suspect you have any kind of a crash in -release or -dealloc, immediately turn on Zombies. This can be done through Instruments or via an environment variable or by calling a function in the Foundation very early in your program's execution.
Search for "Zombies" or "NSZombie" in the documentation included with the development environment (that'd be more of the "teach a man to fish" thing).
The problem was a MutableArray deep in a subclass that was created through a factory (autoreleased) but I was also releasing too. Unfortunately the crash wouldn't indicate which inherited dealloc was causing the crash and just stop on the first overridden dealloc.
The Zombie thing helped a little in that it told me an array was the culprit but not much else. I think there is more to NSZombie and requires more experience to take full advantage of it.