Objective-C Runtime bindings and wrapper for Rust.
Objective-C objects can be messaged using the msg_send!
macro:
rust
let cls = Class::get("NSObject").unwrap();
let obj: *mut Object = msg_send![cls, new];
let hash: usize = msg_send![obj, hash];
let is_kind: BOOL = msg_send![obj, isKindOfClass:cls];
// Even void methods must have their return type annotated
let _: () = msg_send![obj, release];
Objective-C objects are reference counted; to ensure that they are retained and
released at the proper times, we can use the Id
struct.
To enforce aliasing rules, an Id
can be either owned or shared; if it is
owned, meaning the Id
is the only reference to the object, it can be mutably
dereferenced. An owned Id
can be downgraded to a ShareId
which can be cloned to allow multiple references.
Weak references may be created using the WeakId
struct.
``` rust let cls = Class::get("NSObject").unwrap(); let obj: Id
// share the object so we can clone it let obj = obj.share(); let anotherref = obj.clone(); // dropping our other reference will decrement the retain count drop(anotherref);
let weak = WeakId::new(&obj); assert!(weak.load().issome()); // After the object is deallocated, our weak pointer returns none drop(obj); assert!(weak.load().isnone()); ```
The Block
struct is used for invoking blocks from Objective-C. For example,
consider this Objective-C function:
objc
int32_t sum(int32_t (^block)(int32_t, int32_t)) {
return block(5, 8);
}
We could write it in Rust as the following:
rust
fn sum(block: &mut Block<(i32, i32), i32>) -> i32 {
block.call((5, 8))
}
Note the extra parentheses in the call
method, since the arguments must be
passed as a tuple.
Creating a block to pass to Objective-C can be done with the ConcreteBlock
struct. For example, to create a block that adds two i32
s, we could write:
rust
let block = ConcreteBlock::new(|a: i32, b: i32| a + b);
let mut block = block.copy();
assert!(block.call((5, 8)) == 13);
It is important to copy your block to the heap (with the copy
method) before
passing it to Objective-C; this is because our ConcreteBlock
is only meant
to be copied once, and we can enforce this in Rust, but if Objective-C code
were to copy it twice we could have a double free.
Classes can be declared using the ClassDecl
struct. Instance variables and
methods can then be added before the class is ultimately registered.
The following example demonstrates declaring a class named MyNumber
that has
one ivar, a u32
named _number
and a number
method that returns it:
``` rust let superclass = Class::get("NSObject").unwrap(); let mut decl = ClassDecl::new(superclass, "MyNumber").unwrap();
// Add an instance variable
decl.addivar::
// Add an ObjC method for getting the number extern fn mynumberget(this: &Object, cmd: Sel) -> u32 { unsafe { *this.getivar("number") } } unsafe { decl.addmethod(sel!(number), mynumberget as extern fn(&Object, Sel) -> u32); }
decl.register(); ```