I have an object which inherits from the MKPlacemark class of MapKit. I have a method launch during viewDidLoad of my ViewController that creates such object (alloc + init) and pass it to a MapView as follows
[self.mapView addAnnotation:<my instance of my class inheriting MKPlacemark>]
However, when I launch my program, I get the following error message:
An instance 0x9a5d650 of class <name of my class> was deallocated while key value
observers were still registered with it. Observation info was leaked, and may even
become mistakenly attached to some other object. Set a breakpoint on
NSKVODeallocateBreak to stop here in the debugger.
Note that I use ARC. Can anyone tell me how can I avoid such deallocation?
Thanks !
EDIT: My problem is not the warning in itself, it is that I do not want this object to be deallocate at that moment...
EDIT2: The code of the class is the following
The .h file looks like this
#interface OPTCreatureMark : MyMark
#property (nonatomic, assign) CLLocationCoordinate2D coordinate;
-(id)initWithCoordinate:(CLLocationCoordinate2D)coordinate;
#end
and the .m like that
#implementation MyMark
#synthesize coordinate;
-(id) initWithCoordinate:(CLLocationCoordinate2D)coordinate_ {
if (self = [super initWithCoordinate:coordinate_ addressDictionary:nil]) {
self.coordinate=coordinate_;
return self;
} else {
return nil;
}
}
#end
If you are indeed using KVO, it sounds like you need to remove the observer in your object's dealloc method like so:
[self removeObserver:self.myDelegate forKeyPath:#"zoom"];
Otherwise messages could be sent to a deallocated instance of your class (which can no longer respond because it's been deallocated), thus causing an exception.
Related
I have some abbreviated iOS Objective-C sample code (simplified from a larger project) that causes a crash in NSUndoManager that I can't explain.
Namely, when an object that is only held onto by the NSUndoManager deallocs (because it's beyond the levels of undo), and, according to the docs calls removeAllActionsWithTarget:self, I get an EXC_BAD_ACCESS.
// SimpleViewController.m
#interface ViewController ()
#property (nonatomic, strong) NSUndoManager *undoManager;
#end
#implementation ViewController
#synthesize undoManager;
// called from a simple button
- (IBAction)doItTapped:(id)sender
{
CoolObject *object = [CoolObject new];
object.undoManager = self.undoManager;
// according to docs, object will be retained by NSUndoManager here
// but target will not (which should be okay)
[self.undoManager registerUndoWithTarget:self selector:#selector(notCool:) object:object];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.undoManager = [NSUndoManager new];
self.undoManager.levelsOfUndo = 3;
}
and
// CoolObject.m
#implementation CoolObject
- (void)dealloc
{
[self.undoManager removeAllActionsWithTarget:self];
}
#end
After the 4th tap of the button (levelsOfUndo + 1), it crashes.
If I swap NSUndoManager with GCUndoManager, no crash.
Tested in iOS 10.2 sim and devices.
Thanks for any ideas!
Their are chances that you might be getting this error because self.undoManager is not retained at that point where you are using it. When the object is already deallocated and you try to access it, you will get bad access exception.
Try to change your code from this:
CoolObject *object = [CoolObject new];
to this:
#interface ViewController (){
CoolObject *object;
}
#property (nonatomic, strong) NSUndoManager *undoManager;
#end
#implementation ViewController
- (IBAction)doItTapped:(id)sender
{
object = [CoolObject new];
object.undoManager = self.undoManager;
// according to docs, object will be retained by NSUndoManager here
// but target will not (which should be okay)
[self.undoManager registerUndoWithTarget:self selector:#selector(notCool:) object:object];
}
#end
Hope this will help.
Just like me, you seem to have misinterpreted the admittedly inaccurately written documentation. The docs talk about "target", "object" and "target object" as if they were different things when they really mean exactly one and the same: the (id)target parameter of -removeAllActionsWithTarget:
In other words, in my opinion you should not need to call -removeAllActionsWithTarget: inside of CoolObject at all because CoolObject has been specified as the object of -registerUndoWithTarget:selector:object: whereas the target is your ViewController.
You may have to call -removeAllActionsWithTarget: in your NSViewController's -dealloc but even that is unnecessary in your example because your NSViewController owns the NSUndoManager and thus ViewController won't go away before undoManager does.
#interface TestObj :NSObject
#property(copy, nonatomic)NSString *name;
#end
#implementation TestObj
- (void)testName{
NSLog(#"name:%#",self.name);
}
#end
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id tclass =[TestObj class];
void * vid = &tclass;
[(__bridge id)vid testName];
}
#end
log :
name: <ViewController: 0x7ff3584b6580>
My understanding is vid is a pointer to the address of the TestObj class object, then why vid can be sent directly to the testName instance method?
Also, why the testName method calls the NSLog will output <ViewController: 0x7ff3584b6580>instead of nil?
Thank.
I think you are basically getting lucky that you aren't crashing with that code.
First, class methods start with a + not a - -- so that is an instance method you are implementing.
#interface TestObj :NSObject
#property(copy, nonatomic)NSString *name;
#end
#implementation TestObj
+ (void)testName{
NSLog(#"name:%#", #"TestObj"); // cannot reference ivars or properties in class method
}
#end
...
Class classObject = [TestObj class];
[classObject testName];
You don't want to take pointers to class objects (or instances either). The Objective-C runtime will dereference the pointer you give it, in order to find the "isa" instance variable, which will be the Class it belongs to. The "isa" of a class object is the "meta-class", which contains the list of class methods, so that is how class method lookup works. In your example, it would dereference the pointer and find the TestObj class, meaning it is thinking it is calling a method on a TestObj instance when it's not -- you have not allocated one, but it's really just a garbage pointer. But, it will still (by complete luck) get into your instance method implementation, but "self" isn't really a valid instance. However, it looks like whatever the value is, just so happens to respond to a -name method and return either an NSString with that value or the UIViewController instance itself. Or maybe it's trying to deference the instance variable based on the garbage pointer and ending up with a pointer to the ViewController instance by happenstance. I think that would try to call a -name method though.
Anyways, tl;dr -- your code is quite wrong (don't use & pointers of classes or instances) and you are just getting lucky you aren't crashing. The (bridge id) cast is hiding the warning which was trying to help you.
I have a situation where I have copied a string in an instance of a helper class, retained it, and later released it during the dealloc of the view controller instance that alloc'd the helper class. This results in the dreaded EXC_BAD_ACCESS. I then went to Instruments to debug zombies. This gave me the following error:
An Objective-C message was sent to a deallocated 'CFString (immutable)' object (zombie) at address: blah blah
When I then look at the allocation summary within Instruments and work backwards from the zombie detection, the first time my code is listed is in the deallocation of the helper class instance. Here is what the helper class looks like. First the .h file:
#interface channelButtonTitles : NSObject {
NSString *channelTitle;
...
}
#property (nonatomic,copy) NSString *channelTitle;
...
#end
Then the .m file:
#implementation channelButtonTitles
#synthesize channelTitle;
...
- (void)dealloc {
[channelTitle release];
...
}
#end
Now relevant code from the view controller that uses the helper class looks like the following. In the .h file I have an array that will hold multiple objects of the helper class as follows:
#interface MyVC : UIViewController {
NSMutableArray *channelTitles;
...
}
#property (retain, nonatomic) NSMutableArray *channelTitles;
Then in the .m code, I synthesize channelTitles. I also have a dealloc method as follows:
- (void)dealloc {
[channelTitles release];
...
}
Finally, I alloc object instances of the helper class and store them in channelTitles with strings stored in the channelTitle elements of channelButtonTitles as follows:
[channelTitles removeAllObjects];
self.channelTitles = nil;
channelTitles = [[NSMutableArray alloc] init];
...
for (int i=0; i<numberOfTitles; i++) {
// For each mediaItem, get the title and subtitle info
channelButtonTitles *aChannelButtonTitle = [[channelButtonTitles alloc] init]; // create an object to hold the title and miscellaneous data
aChannelButtonTitle.channelTitle = #"some title";
[channelTitles addObject: aChannelButtonTitle]; // add the title
[aChannelButtonTitle release];
}
So, this is a technique I have used many times before, but seems to not be happy now. When the view controller is popped and I return to the root view controller, the dealloc method in my view controller is called. That releases channelTitles which results in calling dealloc on the channelButtonTitles helper class objects that are stored in channelTitles.
Since I have used copy in the property of my helper class, I assume I own this string. Hence, I am releasing it. If I comment out the [channelTitle release] line from my dealloc, the EXC_BAD_ACCESS goes away, but I suspect I have a memory leak now. Please help me see what I am doing wrong.
Background info
I have a view controller that is running a cocos2d scene (so I can put UIkit objects on top of the scene).
My app is crashing with the following error:
2014-10-25 11:20:04.426 AppName[24166:992733] -[CCScene avatar]: unrecognized selector sent to instance 0x7c5a3270
2014-10-25 11:20:04.428 AppName[24166:992733] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CCScene avatar]: unrecognized selector sent to instance 0x7c5a3270'
I know that the reason the app is crashing is because its trying to call the getter method avatar on a CCScene, instead of the CHCreateAvatarScene which is a subclass of CCScene. If I look in the debugger, the VC thinks that my currentScene property is of type CCScene, not CHCreateAvatarScene so obviously it can't find the Avatar property. Am I declaring it wrong? I can't figure out why this is the case. I'm also a bit of a programming newbie, just FYI. Its probably an obvious mistake.
CHCreateAvatarViewController.h
#import "CHCreateAvatarViewController.h"
#import "CHCreateAvatar.h"
#import "CHAvatarAttribute.h"
#import "CHAvatarAttributeOption.h"
#import "CHAttributeData.h"
#import "CHCreateAvatarScene.h"
#import "CHAttachment.h"
#interface CHCreateAvatarViewController () <CocosViewControllerDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
...
#property (strong, nonatomic) CHCreateAvatarScene *currentScene;
...
#end
#implementation CHCreateAvatarViewController
...
#pragma mark - CocosViewControllerDelegate
-(CCScene *)cocosViewControllerSceneToRun:(CocosViewController *)cocosViewController
{
//This will load the Spritebuilder file which is a loaded as a CCScene.
// I then told it to expect a CHCreateAvatarScene because otherwise I was getting an 'invalid pointer' error.
// I also tried changing the return type of this method to CHCreateAvatarScene to see if that would have any effect but it didn't, so I changed it back.
self.currentScene = (CHCreateAvatarScene *)[CCBReader loadAsScene:#"CreateAvatarScene"];
[self setupSpritesWithAttachments:self.factory.attachments];
return self.currentScene;
}
...
-(void)setupSpritesWithAttachments:(NSMutableArray *)attachments
{
int i = 0;
//This is where its crashing
for (CCSprite __strong *sprite in self.currentScene.avatar.attachmentSprites) {
CHAttachment *attachment = attachments[i];
sprite.texture = attachment.texture;
i++;
}
}
...
CHCreateAvatarScene
// .h
#import "CCScene.h"
#import "CHAvatar.h"
#interface CHCreateAvatarScene : CCScene
#property (strong, nonatomic) CHAvatar *avatar;
#end
//.m
#import "CHCreateAvatarScene.h"
#implementation CHCreateAvatarScene {
CCNode *avatarNode;
}
-(void)didLoadFromCCB
{
self.avatar = (CHAvatar *)[CCBReader load:#"Avatar"];
[avatarNode addChild:self.avatar];
}
CHAvatar (I don't think its relevant, but included it just in case)
//.h
#import "CCNode.h"
#interface CHAvatar : CCNode
#property (strong, nonatomic) NSMutableArray *attachmentSprites;
#end
//.m
#import "CHAvatar.h"
#implementation CHAvatar {
CCSprite *_shoulders;
CCSprite *_neck;
CCSprite *_head;
}
//Have left off the head for now just to get this working.
-(void)didLoadFromCCB
{
self.attachmentSprites = [#[_shoulders, _neck] mutableCopy];
}
#end
Thanks in advance for any help with this!
The declared type of a variable expresses an intention to the compiler. "I intend to store this type of thing in this storage." The compiler will set aside the proper amount of storage for that type of variable (in this case, a pointer) and it will try to warn about cases where the code is clearly trying to put the wrong type of thing into the variable. But it can only do static checks at compile time. It doesn't put in dynamic checks at run time. It doesn't check what the code is actually doing.
Importantly, the declared type of a pointer variable does not control the actual type of thing being pointed to by any pointer stored into it. Just because you have declared your intent, that doesn't mean your actions (i.e. your code) match that intent.
In your case, the expression [CCBReader loadAsScene:#"CreateAvatarScene"] is actually returning an instance of CCScene, not an instance of CHCreateAvatarScene. You have a type cast to tell the compiler to treat the return value as though it were a pointer to CHCreateAvatarScene. That silences the compiler from complaining, but doesn't actually change the nature of the object the pointer points to.
You wrote in a couple of places that "the view controller thinks" the object is of the wrong class and so can't find the property. This is exactly backward. The code is written to "think" that the object is always of the type CHCreateAvatarScene but it really isn't. The view controller doesn't have to "find" the property. It just acts as though the property exists by calling the getter method. It is the object that has received that message that doesn't know how to respond to it because it's not actually a CHCreateAvatarScene. It's a CCScene object.
The debugger and the error message are both correct about the actual type of the object.
The real question is how +[CCBReader loadAsScene:] works. Why would you expect it to return an instance of CHCreateAvatarScene? Why is it behaving differently than you expect and returning an instance of CCScene?
A friend helped me figure it out, so I'm posting the answer.
Basically, I was mixing up concepts of scenes and nodes in Cocos2d. Here was how I fixed it:
Change CHCreateAvatarViewController's property to type CCScene *currentScene and remove (* CHCreateAvatarScene) casting in the cocosViewControllerSceneToRun: method. In fact, I could probably remove this property all together after this solution is complete.
Rename CHCreateAvatarScene to CHCreateAvatarNode (I was getting mixed up with the concepts of scenes and nodes, so this helped). Change it to be a subclass of CCNode, not CCScene.
Add a CCNode *avatarNode property to the vc. In CCBReaderDidLoad: in the vc, add self.avatarNode = [[CHCreateAvatarNode alloc] init];
In the for loop where the app originally crashed change self.currentScene.avatar.attachmentSprites to self.avatarNode.avatar.attachmentSprites
And voila!
I am developing an ARC enabled project. From a view controller I am pushing MyClass,
- (void)pushMyClass {
MyClass *myClass = [[MyClass alloc] init];
[self.navigationController pushViewController:myClass animated:YES];
}
After doing some operations I am popping MyClass. The problem here is that MyClass is not getting deallocated. Following is how the classes look.
/* MyHelperClassDelegate */
#protocol MyHelperClassDelegate <NSObject>
- (void)helperDidFinishHelping:(MyHelperClass *)helper;
#end
/* MyHelperClass Interface */
#interface MyHelperClass : NSObject {
__weak id <MyHelperDelegate> delegate;
}
#property(nonatomic, weak) id<MyHelperDelegate> delegate;
- (void)startHelping;
#end
/* MyHelperClass Implementation */
#implementation MyHelperClass
#synthesize delegate;
- (void)dealloc {
delegate = nil;
}
/* MyClass */
#interface MyClass : UIViewController <MyHelperClassDelegate> {
MyHelperClass *helper;
}
#implementation MyClass {
- (void)dealloc {
helper.delegate = nil;
}
- (void)getHelp {
helper = [MyHelperClass new];
helper.delegate = self;
[helper startHelping];
}
- (void)helperDidFinishHelping:(MyHelperClass *)helper {
}
}
MyHelperClass calls a web service using NSMutalbleURLRequest & NSURLConnection to fetch some data and saves it to user defaults.
One thing to notice here is, if I comment the line helper.delegate = self;, then MyClass gets deallocated.
What to do to make MyClass get deallocated when it is popped out of navigation controller?
Thanks.
Your delegate code looks correct (except your use of an ivar, you don't show a #synthesize so you may have _delegate and delegate both). Its quite likely that something else is retaining MyClass. What I suggest you do is add a NSLog to your MyClass dealloc. Then push it, and immediately hit the back button and see if its dealloc'd or not. If not, then take a hard look at what you do in viewDidLoad et al and start commenting out sections of that code until you can get the dealloc.
Also, I assume you don't keep a strong reference in the class that pushes the MyClass object.
I agree with Chuck that one cannot say much from the code provided. But one reason why the MyClass object is not deallocated might be that it is retained by your helper object since delegate is declared as strong, and the MyClass object has the property helper also declared as strong. In this case you had a retain cycle, and none of them can be released.
The trick could possibly lie within the fact that you use NSURLConnection. It is not specified how you use this class with the code that you've provided, but please note the special considerations referenced in the NSURLConnection class reference:
Special Considerations: During the download the connection maintains a
strong reference to the delegate. It releases that strong reference
when the connection finishes loading, fails, or is canceled.