Friday, July 17, 2020

Stack Overflow and Avoiding the Linker

The question
Long time reader, first-time asker. My question is, "Can an Objective-C implementation be defined in a header file and also be imported by multiple source files?" I posted that question on the Apple Developer Forums and Stack Overflow. On Stack Overflow, many of the replies that said to use a .m file or they wanted to know why I would even try to do that. I didn't expect these responses considering my full question included that "[adding a source file] would break the single-header architecture" and that "[I'm looking for] some way to implement an Objective-C class in a header file." Thankfully, there were people who tried to help me with a workaround.

The Engineer
My experience with the Apple Developer Forums was different. An Apple Engineer gave it to me straight; there is no way to inline a class in Objective-C. So how do we get around the duplicate symbols error without inlining or creating a separate file for implementation? 

A new solution
Lari found a new solution. Using the Objective-C Runtime Library, we can get passed the linking error by creating and defining the Objective-C class at runtime. After modifying my spike solution to use the runtime library, it worked surprisingly well with no issues.

The catch
Around the same time, I got my spike solution working, I also was informed by the engineer at Apple that the runtime library is per process, and as a result, I could get errors with the way I intended to use it. After they clarified about what per process meant, I was able to revise my spike solution and see the issues they were talking about. I wasn't getting any errors, but I was getting unexpected behavior that could lead to them.

Observing the behavior
One of the things I learned with my tests is that any runtime object allocated in one translation unit (TU) could not be allocated in any other translation unit. That is also dependent on which TU is compiled first. For instance, Gateware.h defines an Objective-C runtime object named Foo. Main.mm includes Gateware.h and creates an instance of Foo. Test.mm does the same. If Main.mm is compiled first, it will be able to allocate instances of Foo. Test.mm will have all allocations return nil. The reverse is true if Test.mm is compiled first instead.

Expecting the unexpected
That doesn't mean subsequent TU's compiled can't use runtime objects that already have instances allocated. If Main.mm creates an instance of a runtime object, it can pass it to Test.mm without any issue. Test.mm can then call the runtime object's members and use it normally.

No need to be concerned?
This limitation can be a problem in other projects, but thankfully it's not a limitation in Gateware. If we prevent the end-user from allocating Gateware Objective-C runtime objects, we can ensure there are never any attempts made to allocate them in different TU's. By restricting runtime objects for internal usage in Gateware, we can control how they are used and stay away from unexpected behavior.

Continuing to move forward
The tests I've conducted so far suggest that this is possible. The Objective-C Runtime Library brings some new considerations to keep in mind. There might be other unexpected behavior, but I'm ready to start moving on from my spike solution and start implementing it in Gateware. I'm starting with GWindow_mac because I believe it will be the most challenging to convert to the runtime library than the rest of the libraries that use Objective-C. It uses inheritance, instance variable and methods, overrided methods, static methods, pointers, and threading. If the Objective-C classes in this library can be converted to the Objective-C Runtime Library, then the rest of the libraries shouldn't be an issue. I am optimistic that is the case.

No comments:

Post a Comment