Skip to content

Slightly better class declaration #153

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

Merged
merged 5 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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