Skip to content

Commit

Permalink
Merge pull request #153 from madsmtm/better-declaration
Browse files Browse the repository at this point in the history
Slightly better class declaration
  • Loading branch information
madsmtm authored Jun 6, 2022
2 parents d3dc9e5 + 92f85da commit 20eb41c
Show file tree
Hide file tree
Showing 15 changed files with 245 additions and 78 deletions.
3 changes: 1 addition & 2 deletions objc2-encode/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,7 @@ encode_pointer_impls!(
///
/// Ideally we'd implement it for all function pointers, but due to coherence
/// issues, see <https://github.com/rust-lang/rust/issues/56105>, function
/// pointers that take arguments with "special lifetimes" (don't know the
/// termonology) don't get implemented properly.
/// pointers that are higher-ranked over lifetimes don't get implemented.
///
/// We could fix it by adding those impls and allowing `coherence_leak_check`,
/// but it would have to be done for _all_ references, `Option<&T>` and such as
Expand Down
28 changes: 18 additions & 10 deletions objc2-foundation/examples/class_with_lifetime.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#![deny(unsafe_op_in_unsafe_fn)]
use std::marker::PhantomData;
use std::sync::Once;

use objc2::declare::ClassDecl;
use objc2::declare::ClassBuilder;
use objc2::rc::{Id, Owned, Shared};
use objc2::runtime::{Class, Object, Sel};
use objc2::{msg_send, sel};
Expand Down Expand Up @@ -47,23 +48,30 @@ impl<'a> MyObject<'a> {
fn class() -> &'static Class {
MYOBJECT_REGISTER_CLASS.call_once(|| {
let superclass = NSObject::class();
let mut decl = ClassDecl::new("MyObject", superclass).unwrap();
decl.add_ivar::<Option<&mut u8>>("_number_ptr");

extern "C" fn init_with_ptr(this: &mut Object, _cmd: Sel, ptr: *mut u8) -> *mut Object {
unsafe {
this.set_ivar("_number_ptr", ptr);
let mut builder = ClassBuilder::new("MyObject", superclass).unwrap();
builder.add_ivar::<Option<&mut u8>>("_number_ptr");

unsafe extern "C" fn init_with_ptr(
this: *mut Object,
_cmd: Sel,
ptr: *mut u8,
) -> *mut Object {
let this: *mut Object = unsafe { msg_send![super(this, NSObject::class()), init] };
if let Some(this) = unsafe { this.as_mut() } {
unsafe {
this.set_ivar("_number_ptr", ptr);
}
}
this
}

unsafe {
let init_with_ptr: extern "C" fn(&mut Object, Sel, *mut u8) -> *mut Object =
let init_with_ptr: unsafe extern "C" fn(*mut Object, Sel, *mut u8) -> *mut Object =
init_with_ptr;
decl.add_method(sel!(initWithPtr:), init_with_ptr);
builder.add_method(sel!(initWithPtr:), init_with_ptr);
}

decl.register();
builder.register();
});

Class::get("MyObject").unwrap()
Expand Down
26 changes: 12 additions & 14 deletions objc2-foundation/examples/custom_class.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::sync::Once;

use objc2::declare::ClassDecl;
use objc2::declare::ClassBuilder;
use objc2::rc::{Id, Owned};
use objc2::runtime::{Class, Object, Sel};
use objc2::{msg_send, sel};
Expand Down Expand Up @@ -39,28 +39,26 @@ impl MYObject {
fn class() -> &'static Class {
MYOBJECT_REGISTER_CLASS.call_once(|| {
let superclass = NSObject::class();
let mut decl = ClassDecl::new("MYObject", superclass).unwrap();
decl.add_ivar::<u32>("_number");
let mut builder = ClassBuilder::new("MYObject", superclass).unwrap();
builder.add_ivar::<u32>("_number");

// Add ObjC methods for getting and setting the number
extern "C" fn my_object_set_number(this: &mut Object, _cmd: Sel, number: u32) {
unsafe {
this.set_ivar("_number", number);
}
extern "C" fn my_object_set_number(this: &mut MYObject, _cmd: Sel, number: u32) {
this.set_number(number);
}

extern "C" fn my_object_get_number(this: &Object, _cmd: Sel) -> u32 {
unsafe { *this.ivar("_number") }
extern "C" fn my_object_get_number(this: &MYObject, _cmd: Sel) -> u32 {
this.number()
}

unsafe {
let set_number: extern "C" fn(&mut Object, Sel, u32) = my_object_set_number;
decl.add_method(sel!(setNumber:), set_number);
let get_number: extern "C" fn(&Object, Sel) -> u32 = my_object_get_number;
decl.add_method(sel!(number), get_number);
let set_number: extern "C" fn(&mut MYObject, Sel, u32) = my_object_set_number;
builder.add_method(sel!(setNumber:), set_number);
let get_number: extern "C" fn(&MYObject, Sel) -> u32 = my_object_get_number;
builder.add_method(sel!(number), get_number);
}

decl.register();
builder.register();
});

Class::get("MYObject").unwrap()
Expand Down
5 changes: 4 additions & 1 deletion objc2-foundation/src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,10 @@ impl<T: Message, O: Ownership> NSMutableArray<T, O> {
NSComparisonResult::from((*closure)(obj1, obj2))
}

let f: extern "C" fn(_, _, _) -> _ = compare_with_closure::<T, F>;
// We can't name the actual lifetimes in use here, so use `_`.
// See also https://github.com/rust-lang/rust/issues/56105
let f: extern "C" fn(_, _, *mut c_void) -> NSComparisonResult =
compare_with_closure::<T, F>;

// Grab a type-erased pointer to the closure (a pointer to stack).
let mut closure = compare;
Expand Down
5 changes: 5 additions & 0 deletions objc2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Consistently allow trailing commas in `msg_send!`.
* Added `msg_send_bool!`, a less error-prone version of `msg_send!` for
Objective-C methods that return `BOOL`.
* Implemented `MethodImplementation` for `unsafe` function pointers.

### Changed
* **BREAKING**: Changed signature of `Id::new` and `Id::retain` from
Expand All @@ -33,6 +34,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
let obj: *mut Object = unsafe { msg_send![class!(NSObject), new] };
let obj = unsafe { Id::new(obj) }.expect("Failed to allocate object.");
```
* Allow specifying any receiver `T: Message` for methods added with
`ClassBuilder::add_method`.
* Renamed `ClassDecl` and `ProtocolDecl` to `ClassBuilder` and
`ProtocolBuilder`. The old names are kept as deprecated aliases.

### Fixed
* Properly sealed the `MessageArguments` trait (it already had a hidden
Expand Down
68 changes: 40 additions & 28 deletions objc2/src/declare.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Functionality for declaring Objective-C classes.
//!
//! Classes can be declared using the [`ClassDecl`] struct. Instance variables
//! Classes can be declared using the [`ClassBuilder`] struct. Instance variables
//! and methods can then be added before the class is ultimately registered.
//!
//! # Example
Expand All @@ -11,11 +11,11 @@
//!
//! ```no_run
//! use objc2::{class, sel};
//! use objc2::declare::ClassDecl;
//! use objc2::declare::ClassBuilder;
//! use objc2::runtime::{Class, Object, Sel};
//!
//! let superclass = class!(NSObject);
//! let mut decl = ClassDecl::new("MyNumber", superclass).unwrap();
//! let mut decl = ClassBuilder::new("MyNumber", superclass).unwrap();
//!
//! // Add an instance variable
//! decl.add_ivar::<u32>("_number");
Expand Down Expand Up @@ -78,6 +78,10 @@ macro_rules! method_decl_impl {
($($t:ident),*) => (
method_decl_impl!(-T, R, extern "C" fn(&T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(-T, R, extern "C" fn(&mut T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(-T, R, unsafe extern "C" fn(*const T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(-T, R, unsafe extern "C" fn(*mut T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(-T, R, unsafe extern "C" fn(&T, Sel $(, $t)*) -> R, $($t),*);
method_decl_impl!(-T, R, unsafe extern "C" fn(&mut T, Sel $(, $t)*) -> R, $($t),*);
);
}

Expand Down Expand Up @@ -120,10 +124,14 @@ fn log2_align_of<T>() -> u8 {
/// A type for declaring a new class and adding new methods and ivars to it
/// before registering it.
#[derive(Debug)]
pub struct ClassDecl {
pub struct ClassBuilder {
cls: NonNull<Class>,
}

#[doc(hidden)]
#[deprecated = "Use `ClassBuilder` instead."]
pub type ClassDecl = ClassBuilder;

// SAFETY: The stuff that touch global state does so using locks internally.
//
// Modifying the class itself can only be done through `&mut`, so Sync is
Expand All @@ -134,11 +142,11 @@ pub struct ClassDecl {
// when doing so...).
//
// Finally, there are no requirements that the class must be registered on the
// same thread that allocated it.
unsafe impl Send for ClassDecl {}
unsafe impl Sync for ClassDecl {}
// same thread that allocated it (so Send is safe).
unsafe impl Send for ClassBuilder {}
unsafe impl Sync for ClassBuilder {}

impl ClassDecl {
impl ClassBuilder {
fn as_ptr(&self) -> *mut ffi::objc_class {
self.cls.as_ptr().cast()
}
Expand All @@ -150,16 +158,16 @@ impl ClassDecl {
NonNull::new(cls.cast()).map(|cls| Self { cls })
}

/// Constructs a [`ClassDecl`] with the given name and superclass.
/// Constructs a [`ClassBuilder`] with the given name and superclass.
///
/// Returns [`None`] if the class couldn't be allocated, or a class with
/// that name already exist.
pub fn new(name: &str, superclass: &Class) -> Option<Self> {
Self::with_superclass(name, Some(superclass))
}

/// Constructs a [`ClassDecl`] declaring a new root class with the given
/// name.
/// Constructs a [`ClassBuilder`] declaring a new root class with the
/// given name.
///
/// Returns [`None`] if the class couldn't be allocated.
///
Expand All @@ -173,11 +181,10 @@ impl ClassDecl {
/// Functionality it expects, like implementations of `-retain` and
/// `-release` used by ARC, will not be present otherwise.
pub fn root(name: &str, intitialize_fn: extern "C" fn(&Class, Sel)) -> Option<Self> {
let mut decl = Self::with_superclass(name, None);
if let Some(ref mut decl) = decl {
unsafe { decl.add_class_method(sel!(initialize), intitialize_fn) };
}
decl
Self::with_superclass(name, None).map(|mut this| {
unsafe { this.add_class_method(sel!(initialize), intitialize_fn) };
this
})
}

/// Adds a method with the given name and implementation.
Expand All @@ -191,9 +198,10 @@ impl ClassDecl {
///
/// The caller must ensure that the types match those that are expected
/// when the method is invoked from Objective-C.
pub unsafe fn add_method<F>(&mut self, sel: Sel, func: F)
pub unsafe fn add_method<T, F>(&mut self, sel: Sel, func: F)
where
F: MethodImplementation<Callee = Object>,
T: Message + ?Sized, // TODO: Disallow `Class`
F: MethodImplementation<Callee = T>,
{
let encs = F::Args::ENCODINGS;
let sel_args = count_args(sel);
Expand Down Expand Up @@ -290,8 +298,8 @@ impl ClassDecl {

// fn add_property(&self, name: &str, attributes: &[ffi::objc_property_attribute_t]);

/// Registers the [`ClassDecl`], consuming it, and returns a reference to
/// the newly registered [`Class`].
/// Registers the [`ClassBuilder`], consuming it, and returns a reference
/// to the newly registered [`Class`].
pub fn register(self) -> &'static Class {
// Forget self, otherwise the class will be disposed in drop
let cls = ManuallyDrop::new(self).cls;
Expand All @@ -300,7 +308,7 @@ impl ClassDecl {
}
}

impl Drop for ClassDecl {
impl Drop for ClassBuilder {
fn drop(&mut self) {
unsafe { ffi::objc_disposeClassPair(self.as_ptr()) }
}
Expand All @@ -309,20 +317,24 @@ impl Drop for ClassDecl {
/// A type for declaring a new protocol and adding new methods to it
/// before registering it.
#[derive(Debug)]
pub struct ProtocolDecl {
pub struct ProtocolBuilder {
proto: NonNull<Protocol>,
}

// SAFETY: Similar to ClassDecl
unsafe impl Send for ProtocolDecl {}
unsafe impl Sync for ProtocolDecl {}
#[doc(hidden)]
#[deprecated = "Use `ProtocolBuilder` instead."]
pub type ProtocolDecl = ProtocolBuilder;

// SAFETY: Similar to ClassBuilder
unsafe impl Send for ProtocolBuilder {}
unsafe impl Sync for ProtocolBuilder {}

impl ProtocolDecl {
impl ProtocolBuilder {
fn as_ptr(&self) -> *mut ffi::objc_protocol {
self.proto.as_ptr().cast()
}

/// Constructs a [`ProtocolDecl`] with the given name.
/// Constructs a [`ProtocolBuilder`] with the given name.
///
/// Returns [`None`] if the protocol couldn't be allocated.
pub fn new(name: &str) -> Option<Self> {
Expand Down Expand Up @@ -386,7 +398,7 @@ impl ProtocolDecl {
}
}

/// Registers the [`ProtocolDecl`], consuming it and returning a reference
/// Registers the [`ProtocolBuilder`], consuming it and returning a reference
/// to the newly registered [`Protocol`].
pub fn register(self) -> &'static Protocol {
unsafe {
Expand Down
Loading

0 comments on commit 20eb41c

Please sign in to comment.