6

I'm trying to make a key for use in objc_setAssociatedObject, as in this question:

How do I use objc_setAssociatedObject/objc_getAssociatedObject inside an object?

I have a MyConstants.h file which defines

static NSString *myConstant = @"MyConstant";

This file is then included in MyFramework via MyFramework.h

#import ...;
#import "MyConstants.h"
#import ...;

Then in my project, the framework is included in all files via the .pch header.

#import <Cocoa/Cocoa.h>
#import <MyFramework/MyFramework.h>

This all works as expected. The problem is that when I call objc_setAssociatedObject or objc_getAssociatedObject with myConstant as the key, it doesn't work. The reason is clear enough from the log:

- (void)doSomething:(id)key { //Gets called with myConstant
    NSLog(@"Associating with key at %p", &key);
    objc_setAssociatedObject(self, &key, object, policy);
}
- (void)doSomethingElse:(id)key { //Gets called with myConstant
    NSLog(@"Seeking association for key at %p", &key);
    NSLog(@"Will find %@", objc_getAssociatedObject(self, &key));
}

Which logs:

Associating with key at 0x7fff5fbfecdf
Seeking association for key at 0x7fff5fbfecef
Will find (null)

You'll notice that the pointers shift.

However, calling the same method again shows the same pointers. It's as if the constant IS a constant, in the file that imports it, but not for the whole project.

Q: How can I correctly define and import headers with constants so that a constant is actually at a constant place in memory?

Community
  • 1
  • 1
SG1
  • 2,871
  • 1
  • 29
  • 41

2 Answers2

9

If two files have static char something, each one will have its own copy--they do not represent the same piece of memory. If you want two files to have access to the key, you must instead do this in your header:

extern const char MyConstantKey;

And this in one implementation file (.c or .m):

const char MyConstantKey;

This defines a single one-char-wide location in memory, to which all files will point when you use &MyConstantKey.

Jonathan Grynspan
  • 43,286
  • 8
  • 74
  • 104
  • I only define the static char or static NSString * in one file. I'm not entirely sure how many times that variable gets actually created though during compile or runtime when other files #import it or when the runtime loads framework bundles. – SG1 Jul 28 '11 at 03:18
  • Whenever it's included in another file, it duplicates the symbol. The solution is to make it `extern` instead of `static`. – Jonathan Grynspan Jul 28 '11 at 16:10
  • It's never #included, only #imported. I've been reading everything I can on const vs static and extern, but I can't see how what you're describing works. Can you amend your answer to more clearly describe the issue you're identifying? Specifically, I have two comments: 1) the compiler compains that const char is not of type (void *), but static char is fine, and 2) the larger issue is that you explicitly state "if two files have static char", but only one file actually has this defined. If the "import" command can insert a file into the header, then how can I import only once? – SG1 Jul 30 '11 at 02:37
  • `#include` and `#import` mean the same thing in this situation. The difference between them does not affect linkage. – Jonathan Grynspan Jul 30 '11 at 02:45
  • And the solution to the duplicate symbol problem is to *use `extern` linkage instead of `static` linkage*. – Jonathan Grynspan Jul 30 '11 at 02:46
0

I have edited the original post to show what I think the error was. The other poster's answer may well have also been an excellent solution, but how I resolved the problem was like so:

As the &key indicates, I was passing key directly to the methods, as in myMethod:(id)key. Somehow, de-referencing this pointer was different in different cases. By simply changing all the methods to myMethod:(void *)key and passing in &key, the problem immediately went away. Or ensuring I was using a static char and not a static NSString *

I don't understand it fully, but it works.

SG1
  • 2,871
  • 1
  • 29
  • 41
  • 2
    That works because the compiler optimizes memory usage. It makes a single Objective-C object out of the two constant instances of `@"MyConstant"` but the underlying problem (marking the constant as `static` rather than `extern`) remains and the compiler could, in theory, make *two* strings in memory, thus breaking your code. – Jonathan Grynspan Jul 28 '11 at 16:11
  • Hi Jonathan, thanks for trying to help get this right for posterity. It sounds like you're suggesting that I define an "extern const char" in the file in question, then "const char" in the header file, but that I never #import "MyConstants.h" That could be exactly what you're suggesting, but it seems quite counter-intuitive. If I want to change the name of the constant I have to do so in every file which mentions it... – SG1 Jul 30 '11 at 02:45
  • You have to do that anyway with a named constant regardless of what kind of linkage it has. – Jonathan Grynspan Jul 30 '11 at 02:47