views:

411

answers:

4

I have defined a protocol that all my plug-ins must implement. I would also like the plug-ins to all use certain strings, like MyPluginErrorDomain. With integers this is quite easily achieved in an enum, but I can't figure out how to do the same with strings. Normally, in classes I would define

extern NSString * const MyPluginErrorDomain;

in the .h file and in the .m file:

NSString * const MyPluginErrorDomain = @"MyPluginErrorDomain";

but that doesn't work very well in a protocol, because then each plug-in would have to provide its own implementation which defeats the purpose of having a constant.

I then tried

#define MYPLUGIN_ERROR_DOMAIN @"MyPluginErrorDomain"

but the implementing classes in the plug-in can't seem to see the #define. Who knows a good solution?

+3  A: 

You can declare them in the header with the protocol (but outside the protocol interface itself), then define them in an implementation file for the protocol (obviously it wouldn't have an @implementation section - just your NSString definitions).

Or have a separate .h/.m pair that is just for the string constants (the protocol header can import the string constants header).

Phil Nash
That was quick and simple, thank you.
Elise van Looij
+4  A: 

In C++, I would declare them in a header like this:

const char * const MYPLUGIN_ERROR_DOMAIN = "MyPluginErrorDomain";
const char * const MYPLUGIN_FOO_DOMAIN = "MyPluginFooDomain";

Note that as the pointers are const, they will be local to the translation units the header is #included in, and so there will be no need to use extern to prevent multiple definition errors.

anon
Is *local to the translation units* valid for all const pointer definitions?
Georg Fritzsche
It is true for all const definitions - pointers are just one case. Note that the pointer itself has to be const, so const char * p = "foo"; would not do.
anon
Great, learned something new today.
Georg Fritzsche
For those who, like me, found this surprising: "A name having namespace scope has internal linkage if it is the name of .. an object or reference that is explicitly declared const and neither explicitly declared extern .." [3.5/3]
Roger Pate
I'm suprised people don't know about this - it's one of the nicer features of C++. Maybe I've found my next blog topic :-)
anon
@Roger: thanks, didn't have the time to look.
Georg Fritzsche
@Neil: sounds good, please do.
Georg Fritzsche
@Neil: loved the 'const char *const' which I've since learned "is an immutable pointer to an immutable object" [Clark S. Cox, III on http://objectmix.com/c/177819-nsstring-char.html], but I've also found that it doesn't play well with NSString. I will remove the c++ tag to keep my confusion from spreading. Do blog on this subject, as you can see there's a big hole in the market here.
Elise van Looij
+3  A: 

You keep the .h definition:

extern NSString * const MyPluginErrorDomain;

but put this part into a separate .m file that gets included in your framework:

NSString * const MyPluginErrorDomain = @"MyPluginErrorDomain";

So plug-ins can still implement the interface but when compiling they link or compile in your other .m file, so they will see the value of MyPluginErrorDomain.

stefanB
+1. It might also be easier to add a comment mentioning the value - so the user doesn't have to hex-examine, or write debug programs to find out what the string says.
Fox
@Fox: good point, I will. @stefanB: I think @Phil Nash gave the same answer, but since he was very fast, he gets the green V.
Elise van Looij
+1  A: 

You should implement it as extern strings as in your example:

extern NSString * const MyPluginErrorDomain;

or provide extern functions which return static storage data. For example:

 /* h */ 

 extern NSString * MyPluginErrorDomain();

 /* m */ 

 NSString * MyPluginErrorDomain() {
    static NSString * const s = @"MyPluginErrorDomain";
    return s;
 }

The reason is that strings and keys are often used and compared by pointer value or hash value, rather than true string comparison (isEqualToString:).

At the implementation level, there is a big difference between:

In code, that means that when the strings compared are defined in multiple binaries:

Say 'MyPluginErrorDomain' and 'key' have identical string values, but are defined in different binaries (i.e. on in the plugin host, one in the plugin).

/////// Pointer comparison (NSString)
BOOL a = [MyPluginErrorDomain isEqualToString:key];
BOOL b = MyPluginErrorDomain == key;

// c may be false because a may be true, in that they represent the same character sequence, but do not point to the same object
BOOL c = a == b;


/////// Hash use (NSString)
// This is true
BOOL d = [MyPluginErrorDomain hash] == [key hash];

// This is indicative if true
BOOL e = [MyPluginErrorDomain hash] == [someOtherStringKey hash];

// because
BOOL f = [MyPluginErrorDomain isEqualToString:someOtherStringKey];

// g may be false (though the hash code is 'generally' correct)
BOOL g = e == f;

It is therefore necessary to provide the keys in many cases. It may seem like a trivial point, but it is hard to diagnose some of the problems associated with the difference.

Hash codes and pointer comparisons are used throughout Foundation and other objc technologies in the internals of dictionary storage, key value coding... If your dictionary is going straight out to xml, that's one thing, but runtime use is another and there are a few caveats in the implementation and runtime details.

Justin
`extern` strings won't work as you don't need to link to a binary to communicate with it via a `protocol` - think *remote messaging*
Georg Fritzsche
@gf It's not uncommon for a plugin to link to a library specific to the plugin interface.
Justin
*"Not uncommon"* isn't *"always"* - remote messaging is also *"common"* with Cocoa.
Georg Fritzsche
@Justin: could you perhaps clarify your answer? I don't know what you mean by "implement it as extern strings which your clients link". Nor do I understand "strings/keys are often used/compared as hash values by pointer value/hash code". The plug-ins will use the string constants as keys in dictionaries.
Elise van Looij
@Elise I rewrote the response to clarify hash/pointer/string comparison and use differences
Justin
@Justin, thank you. Your writing style rather obscures the importance of your answer: until now I had not realized the importance of hash in comparisons. Reading further into the subject matter, I came across Apple's <a href="http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/CodingGuidelines/Articles/FrameworkImpl.html">Coding Guidelines for Cocoa</a> >> Languaage Issues >> Object comparison which advocates using isEqual: for "objects that are put in a hash-based Cocoa collection such as an NSDictionary or NSSet is that if [A isEqual:B] == YES, then [A hash] == [B hash]."
Elise van Looij
@Elise You're welcome, I'm glad it helped. Many people don't care for a response that is a page long, and I'm usually distracted by SO when I respond, so I tend to be brief :)
Justin