-
-
Notifications
You must be signed in to change notification settings - Fork 786
Add support for Objective-C class implementation #5064
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
… used or not. Allow pseudo-fields for ivar access.
…onvention. Use @objc_context_provider to provide a context for a type.
After playing around a bit with the last state of things, using an intrinsic manually to access fields with the object's ivar was quite cumbersome. I solved both issues by adding 2 new features to complete the desired functionality:
The former works by simply emitting the The second uses a new attribute
During generation, when a method with Odin calling convention is encountered, the wrapper procedure is adjusted to first set a context by calling the context provider procedure with the 'self' argument. If the self argument is the Ivar type, it first emits an Example: main :: proc() {
// Set initial context
context.user_index = 777
foo := MyFoo.alloc()->initWithContext(context)
// Ivar pseudo-field access
name := foo.name
fmt.printfln("I am '%v'", name)
foo.name = "Not_Fooey"
fmt.printfln("Now I am '%v'", foo.name)
// Odin calling convention call
foo->sayHello()
}
@private
get_ctx :: proc "c" ( self: ^MyFooT ) -> runtime.Context {
return self.ctx
}
@(objc_class="MyFoo", objc_implement, objc_superclass=NS.Object, objc_ivar=MyFooT, objc_context_provider=get_ctx)
MyFoo :: struct { using _: NS.Object }
MyFooT :: struct {
ctx: runtime.Context,
name: string,
age: int,
}
@(objc_type=MyFoo, objc_name="alloc", objc_implement, objc_is_class_method=true)
MyFoo_alloc :: proc "c" () -> ^MyFoo {
return msgSend(^MyFoo, MyFoo, "alloc")
}
@(objc_type=MyFoo, objc_implement, objc_name="initWithContext")
MyFoo_initWithContext :: proc "c" (self: ^MyFoo, ctx: runtime.Context) -> ^MyFoo {
// Set initial context via ivar explicitly
context = ctx
vself: ^MyFooT = ivar_get(self, MyFooT)
self.ctx = ctx
self.name = "Fooey"
self.age = 32
fmt.printfln("Set name to %v", self.name)
return self
}
@(objc_type=MyFoo, objc_implement, objc_name="sayHello")
MyFoo_sayHello :: proc (self: ^MyFoo) {
fmt.printfln("Hello, I am %v :: %v", self.name, context.user_index)
} |
I am guessing this PR could completely replace the code written such as Application delegate that created a class during runtime |
Indeed. And it facilitates being able to have more than 1 protocol implemented in a single class, as well as replacing base class functionality in the hierarchy. You could do the latter with darwodin but it's painful and bloated. There are also cases where you need to provide a separate object with a specific selector, such as when using GestureRecognizers, and this would have meant generating it all and registering it at runtime manually. |
That will be super cool. I would mark the those procedures as deprecated and point people to this stuff instead |
src/check_decl.cpp
Outdated
|
||
// TODO(harold): Is this the right way to do this??? The referenced entity must be already resolved | ||
// so that we can access its objc_superclass attribute | ||
check_single_global_entity(ctx->checker, super->Named.type_name, super->Named.type_name->decl_info); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking it again should not be a problem, but is this what you wanted since you reassigned super
above. This seems a bit unsafe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed—But is this the correct location for this check?
Cleanup ObjC superclass resolution.
…ions and `objc_ivar_get` intrinsic
All checker spec implemented except for I don't know if this is already handled somewhere since that attribute predates this PR. I will try to finish that off tomorrow. What's left is unconditional exporting and TODOs cleanup/clarifications. |
I am leaving what I mentioned there as outside of the scope of this PR as I'd like to not keep increasing the scope to an already massive PR. Hopefully we can discuss more at length later and agree on what is to be done. |
I've implemented both. There's now a class name re-use check for all classes using I've moved setting the implicit export and linkage to a better location outside of the attribute resolution stage, where it won't get overwritten. All that's left is clearing up a couple of minor TODOs |
This is now ready for review. |
…and minor fixes.
I'm not a huge fan of the |
That's unfortunate. It is certainly useful when filling out large protocols or implementing full classes completely in Odin. I modeled it after the What do you think about having calls to ObjC methods be ObjC-runtime dispatch only as I specified here? Meaning, whenever these methods are invoked directly from the Odin side (which should be the rare case), they would unconditionally be dispatched via |
Also note that |
I'm merging this PR even if I still not sure about the |
Add support for Objective-C class implementation
The purpose of this work is to add full Odin-native support for creating Objective-C classes, not just consuming them.
Doing so without modifying syntax, only leveraging attributes and new compiler intrinsics. I also attempt to leverage existing attributes and augment them when necessarry to add the desired new functionality.
This facilitates creating new classes where needed, for things like delegates, event handlers, etc. without ugly runtime workarounds, or leaving Odin to provide it in a different language.
This PR has the following requirements:
What it does not aim to do (at least not this PR):
Attempt to automatically propagate the Odin state (context) into the Objective-C runtime flow.I found a clean way to do this in the same vein as thedeferred_in/out
attributes. See@objc_context_provider
Requirements
Implement
@objc_implement
attribute for structsImplement
@objc_superclass
attribute for procsImplement
@objc_ivar
attributeLimit generating the global ivar offset to intrinsic usage?
Implement
@objc_selector
attributeImplement
@objc_context_provider
which specifies a procedure that provider a context for methods with the Odin calling conventionAdd pseudo-field resolution for Ivar types associated with an Objective-C class
Apply a legitimate strategy for export/linkage
If the@export
attribute is applied to an Objective-C class, then all methods are exported. Currently all are exported unconditionally (@export
is implied)@(objc_implement)
should have their class registered and their methods emitted. Because of the dynamic nature of the Objective-C runtime, this makes the most sense. Especially after testing for a week, there are occasions that a class is expected to be present but it is not because it was not referenced throughmsgSend
in an alloc call or similar with an intrinsic that takes anobjc_Class
.Checker (See comment below):
Proper, understandable error messages
Resolve all pending TODOs
State and discussion
At the time of this writing the full example below works as all the main requirements have been implemented. It will only work with a single module as there's still issues with the changes. Due to lack of experience with the codebase there needs to be some guidance from the team to get this to a proper shippable state.
I'd like to get feedback on attribute and intrinsic naming in addition to the code changes itself.
Practical Explanation & Usage
Creating a new Objective-C class looks like it does when consuming an existing one, except we add the
@objc_implement
attribute:Typically you also want to specify a super/base class as well:
Finally you may want to associate a type to serve as the class' Ivar:
You can add methods to it the same way you do when consuming existing Objective-C methods, simply add the same
@objc_implement
attribute:The compiler will generate a hidden function that conforms to the signature that the Objective-C runtime expects, which simply forwards the call to our method
proc
:Any arguments will be forwarded:
Notice the use of the new intrinsic
intrinsics.ivar_get
. This intrinsic will obtain a pointer to the Type's data of the Ivar from an instance, if one is associated with the class. This is implemented by recording the offset to the ivar at startup, then it simply offsets from the instance pointer during the call to the intrinsic.You can also specify the selector name to be used for a method. By default the value of
@objc_name
will be used, if one is not specified. But there will be many cases when you may want to specify one manually, as it is when implementing a protocol. Specify it via the@objc_selector
attribute:Full working example of an
NSAppDelegate
(partially) implemented in Odin: