0

Here's how I set the associated object:

objc_setAssociatedObject(navigationItem, "rightButton", leftButtonView, OBJC_ASSOCIATION_RETAIN);

and here's how I get it:

UIButton *favoriteButton = (UIButton *)objc_getAssociatedObject(self.navigationItem, "rightButton");

The object is a UIButton in both instances, and I have the #import "objc/runtime.h" in both classes.

The thing is, it used to work, but now for some reason it returns null. Any ideas?

EDIT

I'm setting the associated object in another class by passing the navigationItem as an argument:

+ (void)setNavigationBarTitle:(NSString *)title 
                andLeftButton:(NSString *)leftButtonTitle 
                 withSelector:(SEL)leftSelector 
               andRightButton:(NSString *)rightButtonTitle 
                 withSelector:(SEL)rightSelector 
                    andTarget:(id)target 
            forNavigationItem:(UINavigationItem *)navigationItem;

I use this convenience method in my utility class to set up a navigation bar for each new view controller in my app. When creating the navigation bar buttons, I associate them with the navigation item - my first line of code in the question.

artooras
  • 6,315
  • 9
  • 45
  • 78
  • 1
    Also, `navigationItem` and `self.navigationItem` aren't necessarily the same thing. It's impossible to tell without more context. – godel9 Nov 12 '13 at 15:24
  • Damn it, I copied the wrong line... I am actually doing this for both left and right button, so there is a set object command for rightButton as well - edited. So, the problem is not there, but thanks for noticing :) – artooras Nov 12 '13 at 19:25
  • Have you verified that `navigationItem` is the same object (see godel9's comment above)? Note that each view controller has its own navigation item! – Martin R Nov 12 '13 at 19:55
  • Yes, it's the same `self.navigationItem`. I'm referring to it without `self` because I'm passing it as a method argument to another class, that is a class method of a utility class that does not get released, or so I assume that it doesn't. I have added the method with some more explanation in my edit. – artooras Nov 12 '13 at 20:04
  • 1
    Just to make sure: You are not calling `objc_removeAssociatedObjects()` anywhere, are you? – herzbube Nov 12 '13 at 20:29
  • Bingo - that's it! I do actually call it, although it's in yet another class. `objc_removeAssociatedObjects()` takes an argument `id object`, and it's not `navigationItem` that I pass, and certainly not _that_ `navigationItem`. Either way, removing this call solved the problem. If you would put an explanation into an answer, I'd be happy to accept it. And thanks again! – artooras Nov 12 '13 at 20:43

3 Answers3

5

This is a shot in the dark, because I could not reproduce the issue. The problem might be that the key argument of objc_set/getAssociatedObject() is a unique pointer.

So your code would not work if the two different classes use strings "rightButton" at different memory locations. The strings have the same contents, but the pointer is different.

In any case, you should ensure that the identical key is used for setting and retrieving the associated object:

  • In a common header file (or in the .pch file) put an extern declaration:

    extern const void *rightButtonKey;
    
  • In exactly one .m file define the variable. The "self-reference" guarantees that the pointer has a unique value:

    const void *rightButtonKey = &rightButtonKey;
    
  • Then use it like

    objc_setAssociatedObject(navigationItem, rightButtonKey, leftButtonView, OBJC_ASSOCIATION_RETAIN);
    
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thanks Martin, I am using the same key. The solution appeared to be different - see comments below my question. – artooras Nov 12 '13 at 20:44
  • @artooras: OK, that was a good catch of "herzbube" and I am glad that your problem is solved. - But even if I repeat myself: Two string literals `"rightButton"` in two different files do not necessarily have the same address. So even if it is a problem *now*, I would recommend to change the code. - Compare http://stackoverflow.com/questions/19088153/addresses-of-two-pointers-are-same. – Martin R Nov 12 '13 at 21:17
  • Thanks Martin, I obviously didn't think that far. Would using a string `#define`d in `Project-Prefix.pch` be a move in the right direction? As I'm using this key in multiple different classes/instances. – artooras Nov 12 '13 at 21:36
  • @artooras: No, using `#define RIGHT_KEY "rightButton"` would make no difference, because that is expanded by the preprocessor. - Usually constant strings with the same contents are put at the same memory address by the linker, but you cannot rely on that. – Martin R Nov 12 '13 at 21:42
  • Thanks Martin, this is definitely outside my humble amateurish knowledge, but I'll follow your suggestion and refactor my code. – artooras Nov 12 '13 at 21:55
2

You use different keys: you set object for key "leftButton" and get for "rightButton".

Sviatoslav Yakymiv
  • 7,887
  • 2
  • 23
  • 43
2

To quote a famous person:

Once you eliminate the impossible, whatever remains, no matter how improbable, must be the truth.

Since all the other guesses were wrong, the only other source of the problem that I can think of is that you are calling objc_removeAssociatedObjects() at some point. In your question you say that your code used to work, so we must assume that somewhere something changed, causing the execution path to suddenly take a different turn.

  • Possibility 1: The execution path now goes through that call to objc_removeAssociatedObjects() where before it didn't.
  • Possibility 2: You already called objc_removeAssociatedObjects() before, but now you are passing a different value to the function.

It's impossible to say which one of those two it is.

You say in your comment that you do not pass the navigationItem in question and ask for an explanation. Unfortunately I cannot provide one. With the knowledge I currently have, I must assume that either your claim or the Apple docs for the function are incorrect. Since objc_removeAssociatedObjects() has worked for me in the past, I naturally tend to believe Apple :-)

I might ask: How did you verify that you do not pass the wrong object to the function? My suggestion: Add two NSLog statements to the code, just before you call objc_getAssociatedObject() and objc_removeAssociatedObjects(), where you print the pointer of the object that you are passing to the two functions.

herzbube
  • 13,158
  • 9
  • 45
  • 87
  • The reason I know I'm not passing the same object is because I am passing an `NSDate` object to `objc_removeAssociatedObjects()` which is obviously not a `navigationItem` :) Anyway, thanks again for finding my problem. – artooras Nov 12 '13 at 21:33