Can an Objective-C implementation be defined in a header file and also be imported by multiple source files?

I have a header file that defines the interface and implementation of an Objective-C class.

Person.h
Code Block Objective-C
#ifndef Person_h
#define Person_h
@interface Person : NSObject
-(void)speak;
@end
@implementation Person
-(void)speak
{
// Say something
}
@end
#endif /* Person_h */

I also have two source files that both include the header file.

Main.mm
Code Block Objective-C
@import Foundation;
#import "Person.h"
int main(int argc, const char * argv[])
{
// Do nothing
}

Test.mm
Code Block Objective-C
@import Foundation;
#import "Person.h"

When the project is built, I get duplicate symbol errors.
Code Block
duplicate symbol '_OBJC_CLASS_$_Person' in:
/Debug/TestBox.build/Objects-normal/x86_64/main.o
/Debug/TestBox.build/Objects-normal/x86_64/test.o
duplicate symbol '_OBJC_METACLASS_$_Person' in:
/Debug/TestBox.build/Objects-normal/x86_64/main.o
/Debug/TestBox.build/Objects-normal/x86_64/test.o
ld: 2 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

I want to be able to include the same header file in multiple source files. In C++, I can inline the implementation into the header file like this:
Code Block C++
#ifndef Person_h
#define Person_h
class Person
{
public:
void speak()
{
// Say something
}
};
#endif /* Person_h */

However, I haven't been able to find a way to do that with Objective-C. I'm using Objective-C so I can subclass events from NSWindowDelegate and NSResponder.

I tried searching for solutions. Most of them said to separate the implementation into a source file, but that would break the single-header architecture. One suggestion is to use the Objective-C runtime library and create my classes at runtime. This appears to give me the results I'm looking for, but I'm wondering if there is a simpler way.

Is there some way to implement an Objective-C class in a header file so that can be included by multiple source files? Or is dynamically creating the classes at runtime my best option?

Replies

I have a header file that defines the interface and implementation of an Objective-C class.

Why? What problem are you trying to solve here? This approach is most unusual and it’s clear that you have a reason for doing this but I’m mystified as to what that is )-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
The project I am working on is a cross-platform collection of libraries. The single-header design makes it easy to manage distribution. It saves us time not compiling different configurations of dll's and lib's for different operating systems.

It also makes it easy for the end-user to add to their project. We want to make it as simple as possible to get started using our libraries. All they need to do is include the one file, and they are ready to use it.
The approach your attempting just doesn’t work for Objective-C. There’s actually two problems here. The first is that, if two clients in the same Mach-O image try to use this header, they’ll run into the duplicate symbol problem that you’ve described.

The second one is considerably gnarlier. The Objective-C runtime is per process, so if two clients in different Mach-O images use this header then you’ll get snarky error messages from the Objective-C runtime.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"

The Objective-C runtime is per process, so if two clients in different Mach-O images use this header then you’ll get snarky error messages from the Objective-C runtime.

@eskimo, that is something we're concerned with. Could we randomly generate the class name to get around this issue?
I modified my code to use the runtime library and I'm not getting any errors.

People.h
Code Block
@import Foundation;
#import <objc/objc-runtime.h>
#ifndef Person_h
#define Person_h
static Class Person;
inline void PersonSpeak(id self, SEL _cmd)
{
NSLog(@"%Hello");
}
inline void CreatePersonClass()
{
Person = objc_allocateClassPair([NSObject class], "Person", 0);
class_addMethod(Person, @selector(speak), (IMP)PersonSpeak, "@@");
objc_registerClassPair(Person);
}
#endif /* Person_h */

Main.mm
Code Block
@import Foundation;
#import "Person.h"
int main(int argc, const char * argv[])
{
@autoreleasepool
{
CreatePersonClass();
id person = [[Person alloc] init];
// Loop used to test for runtime errors when multiple instances are running.
while (true)
{
[person performSelector:@selector(speak)];
}
}
return 0;
}

Test.mm
Code Block
@import Foundation;
#import "Person.h"


The Objective-C runtime is per process, so if two clients in different Mach-O images use this header then you’ll get snarky error messages from the Objective-C runtime.


@eskimo, I tried to get the runtime errors you mentioned by creating a second target in Xcode with the same code from the original target. When I have both targets running at the same time, I don't get any errors.

I have also tried running multiple instances of the build from the debug folder. They all ran fine, without any noticeable issues.

Based on what you said about the runtime errors, I'm cautious about accepting this solution if my program will crash under certain conditions because of it. Can you suggest a way to create the runtime errors you mentioned?

When I have both targets running at the same time

Two targets means two processes and, as I mentioned, the Objective-C runtime is per process, so you have two instances of the Objective-C runtime. To see the problem in action you need two Mach-O images that use this technique in the same process. You can do this, for example, by using it in the main app and in a framework that the main app links to.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"

To see the problem in action you need two Mach-O images that use this technique in the same process. You can do this, for example, by using it in the main app and in a framework that the main app links to.

@eskimo, thanks again for your help and for clarifying how to produce the Mach-O issue. I was able to produce errors by doing the following:

Main.mm
Code Block
@import Foundation;
#import "Person.h"
#import "Test.h"
int main(int argc, const char * argv[])
{
@autoreleasepool
{
id person = [[PersonClass() alloc] init];
//id person = CreatePersonInstance();
if (person != nil)
NSLog(@"Main: Person instance created");
else
NSLog(@"Main: Error");
Test test;
}
return 0;
}

Person.h
Code Block
@import Foundation;
#import <objc/objc-runtime.h>
#ifndef Person_h
#define Person_h
// Creates the Person class. If called from outside of Person.h, this only works in the first .mm it is called from.
static Class& PersonClass()
{
static Class Person;
if (Person == nil)
{
Person = objc_allocateClassPair([NSObject class], "Person", 0);
if (Person != nil)
objc_registerClassPair(Person);
}
return Person;
}
// Creates an instance of a Person. Works regardless of which .mm it is called from.
inline id CreatePersonInstance()
{
Class Person = PersonClass();
if (Person == nil)
return nil;
return [[Person alloc] init];
}
#endif /* Person_h */

Test.h
Code Block
class Test
{
public:
Test();
};

Test.mm
Code Block
@import Foundation;
#import "Person.h"
#import "Test.h"
Test::Test()
{
id person = [[PersonClass() alloc] init];
//id person = CreatePersonInstance();
if (person != nil)
NSLog(@"Test: Person instance created");
else
NSLog(@"Test: Error");
}

The output depends on which .mm calls PersonClass() first. If Main.mm calls it first, the output is:
Code Block
Main: Person instance created
Test: Error

Otherwise, if Test.mm calls PersonClass() first, the output is:
Code Block
Test: Person instance created
Main: Error

This issue can be resolved if Main.mm and Test.mm are modified to call CreatePersonInstance() instead, which handles class creation and instance allocation inside Person.h. By doing it this way, instances are created regardless of compile order.
Code Block
Main: Person instance created
Test: Person instance created

In the project I'm working on, any instances of a class created with the Objective-C Runtime Library are only used internally within the single-header. Based on my tests, as long as class creation and instance allocation are kept within the single-header, there won't be any issues between Mach-O's. We have integrated this solution into the project, and so far, we haven't encountered problems.

The project I am working on is a cross-platform collection of libraries. The single-header design makes it easy to manage distribution. It saves us time not compiling different configurations of dll's and lib's for different operating systems. 

It also makes it easy for the end-user to add to their project. We want to make it as simple as possible to get started using our libraries. All they need to do is include the one file, and they are ready to use it.

While this approach may save you some time, it is only going to confuse your users and cause problems for them. And if those users come here asking for support and describe this kind of approach, we are going to tell them to run far, far away from your product.

If you are distributing the source in the header, then just distribute the source and let the end users build it for their own configurations.