Skip to content

Commit

Permalink
Merge pull request #234 from madsmtm/class-type
Browse files Browse the repository at this point in the history
Add `ClassType` trait
  • Loading branch information
madsmtm authored Aug 2, 2022
2 parents 6f68cd0 + 577af32 commit b97bcbb
Show file tree
Hide file tree
Showing 35 changed files with 255 additions and 103 deletions.
6 changes: 6 additions & 0 deletions objc2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Added `declare_class!`, `extern_class!` and `ns_string!` macros from
`objc2-foundation`.
* Added helper method `ClassBuilder::add_static_ivar`.
* **BREAKING**: Added `ClassType` trait, and moved the associated `class`
methods that `extern_class!` and `declare_class!` generated to that. This
means you'll have to `use objc2::ClassType` whenever you want to use e.g.
`NSData::class()`.
* Added `Id::into_superclass`.

### Changed
* **BREAKING**: Change selector syntax in `declare_class!` macro to be more Rust-like.
* **BREAKING**: Renamed `Id::from_owned` to `Id::into_shared`.


## 0.3.0-beta.1 - 2022-07-19
Expand Down
2 changes: 2 additions & 0 deletions objc2/CHANGELOG_FOUNDATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
type-safety from this, it makes `NSValue` much more useful in the real
world!
* **BREAKING**: Made `NSArray::new` generic over ownership.
* **BREAKING**: Made `NSObject::is_kind_of` take a generic `T: ClassType`
instead of a `runtime::Class`.

### Fixed
* Made `Debug` impls for all objects print something useful.
Expand Down
14 changes: 11 additions & 3 deletions objc2/examples/class_with_lifetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use objc2::foundation::NSObject;
use objc2::rc::{Id, Owned};
use objc2::runtime::{Class, Object, Sel};
use objc2::{msg_send, msg_send_id, sel};
use objc2::{Encoding, Message, RefEncode};
use objc2::{ClassType, Encoding, Message, RefEncode};

/// Helper type for the instance variable
struct NumberIvar<'a> {
Expand Down Expand Up @@ -62,8 +62,12 @@ impl<'a> MyObject<'a> {
pub fn set(&mut self, number: u8) {
**self.number = number;
}
}

unsafe impl<'a> ClassType for MyObject<'a> {
type Superclass = NSObject;

pub fn class() -> &'static Class {
fn class() -> &'static Class {
// TODO: Use std::lazy::LazyCell
static REGISTER_CLASS: Once = Once::new();

Expand Down Expand Up @@ -119,14 +123,18 @@ fn main() {
let mut number = 54;
let mut obj = MyObject::new(&mut number);

// It is not possible to convert to `Id<NSObject, Owned>` since that would
// loose the lifetime information that `MyObject` stores
// let obj = Id::into_superclass(obj);

println!("Number: {}", obj.get());

obj.set(7);
// Won't compile, since `obj` holds a mutable reference to number
// println!("Number: {}", number);
println!("Number: {}", obj.get());

let obj = Id::from_owned(obj);
let obj = Id::into_shared(obj);
let obj2 = obj.clone();

// We gave up ownership above, so can't edit the number any more!
Expand Down
13 changes: 5 additions & 8 deletions objc2/examples/delegate.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
#[cfg(all(feature = "apple", target_os = "macos"))]
use objc2::{
declare_class, extern_class,
foundation::NSObject,
msg_send, msg_send_id,
rc::{Id, Shared},
runtime::{Bool, Object},
};
#![cfg_attr(not(all(feature = "apple", target_os = "macos")), allow(unused))]
use objc2::foundation::NSObject;
use objc2::rc::{Id, Shared};
use objc2::runtime::{Bool, Object};
use objc2::{declare_class, extern_class, msg_send, msg_send_id, ClassType};

#[cfg(all(feature = "apple", target_os = "macos"))]
#[link(name = "AppKit", kind = "framework")]
Expand Down
3 changes: 1 addition & 2 deletions objc2/examples/introspection.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use objc2::foundation::NSObject;
use objc2::runtime::Class;
use objc2::sel;
use objc2::Encode;
use objc2::{sel, ClassType, Encode};

fn main() {
// Get the class representing `NSObject`
Expand Down
2 changes: 1 addition & 1 deletion objc2/examples/nspasteboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::mem::ManuallyDrop;
use objc2::foundation::{NSArray, NSDictionary, NSInteger, NSObject, NSString};
use objc2::rc::{Id, Shared};
use objc2::runtime::{Class, Object};
use objc2::{extern_class, msg_send, msg_send_bool, msg_send_id};
use objc2::{extern_class, msg_send, msg_send_bool, msg_send_id, ClassType};

type NSPasteboardType = NSString;
type NSPasteboardReadingOptionKey = NSString;
Expand Down
2 changes: 1 addition & 1 deletion objc2/examples/speech_synthethis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std::time::Duration;

use objc2::foundation::{NSObject, NSString};
use objc2::rc::{Id, Owned};
use objc2::{extern_class, msg_send, msg_send_bool, msg_send_id, ns_string};
use objc2::{extern_class, msg_send, msg_send_bool, msg_send_id, ns_string, ClassType};

#[cfg(all(feature = "apple", target_os = "macos"))]
mod appkit {
Expand Down
1 change: 1 addition & 0 deletions objc2/src/__macro_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ mod tests {
use crate::foundation::NSZone;
use crate::rc::{Allocated, Owned, RcTestObject, Shared, ThreadTestData};
use crate::runtime::Object;
use crate::ClassType;
use crate::{class, msg_send_id};

#[test]
Expand Down
77 changes: 77 additions & 0 deletions objc2/src/class_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::runtime::Class;
use crate::Message;

/// Marks types that represent specific classes.
///
/// Usually it is enough to generically know that a type is messageable, e.g.
/// [`rc::Id`][crate::rc::Id] works with any type that implements the
/// [`Message`] trait. But often, you have an object that you know represents
/// a specific Objective-C class - this trait allows you to communicate that
/// to the rest of the type-system.
///
/// This is implemented automatically by the
/// [`declare_class!`][crate::declare_class] and
/// [`extern_class!`][crate::extern_class] macros.
///
///
/// # Safety
///
/// The class returned by [`Self::class`] must be a subclass of the class that
/// [`Self::Superclass`] represents.
///
/// In pseudocode:
/// ```ignore
/// Self::class().superclass() == <Self::Superclass as ClassType>::class()
/// ```
///
///
/// # Examples
///
/// Use the trait to access the [`Class`] of different objects.
///
/// ```
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// use objc2::ClassType;
/// use objc2::foundation::NSObject;
/// // Get a class object representing `NSObject`
/// let cls = <NSObject as ClassType>::class(); // Or just `NSObject::class()`
/// ```
///
/// Use the [`extern_class!`][crate::extern_class] macro to implement this
/// trait for a type.
///
/// ```ignore
/// use objc2::{extern_class, ClassType};
///
/// extern_class! {
/// unsafe struct MyClass: NSObject;
/// }
///
/// let cls = MyClass::class();
/// ```
pub unsafe trait ClassType: Message {
/// The superclass of this class.
///
/// If you have implemented [`Deref`] for your type, it is highly
/// recommended that this is equal to [`Deref::Target`].
///
/// This may be [`runtime::Object`] if the class is a root class.
///
/// [`Deref`]: std::ops::Deref
/// [`Deref::Target`]: std::ops::Deref::Target
/// [`runtime::Object`]: crate::runtime::Object
type Superclass: Message;

/// Get a reference to the Objective-C class that this type represents.
///
/// May register the class with the runtime if it wasn't already.
///
///
/// # Panics
///
/// This may panic if something went wrong with getting or declaring the
/// class, e.g. if the program is not properly linked to the framework
/// that defines the class.
fn class() -> &'static Class;
}
2 changes: 1 addition & 1 deletion objc2/src/declare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
//! methods and methods for interfacing with the number.
//!
//! ```
//! use objc2::{class, sel, msg_send, msg_send_id};
//! use objc2::declare::ClassBuilder;
//! use objc2::foundation::NSObject;
//! use objc2::rc::{Id, Owned};
//! use objc2::runtime::{Class, Object, Sel};
//! use objc2::{class, sel, msg_send, msg_send_id, ClassType};
//! # #[cfg(feature = "gnustep-1-7")]
//! # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
//!
Expand Down
9 changes: 4 additions & 5 deletions objc2/src/foundation/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ use super::{
};
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
use crate::runtime::{Class, Object};
use crate::Message;
use crate::{__inner_extern_class, msg_send, msg_send_id};
use crate::{ClassType, Message, __inner_extern_class, msg_send, msg_send_id};

__inner_extern_class! {
/// An immutable ordered collection of objects.
Expand Down Expand Up @@ -374,7 +373,7 @@ mod tests {

#[test]
fn test_retains_stored() {
let obj = Id::from_owned(RcTestObject::new());
let obj = Id::into_shared(RcTestObject::new());
let mut expected = ThreadTestData::current();

let input = [obj.clone(), obj.clone()];
Expand Down Expand Up @@ -414,7 +413,7 @@ mod tests {

#[test]
fn test_nscopying_uses_retain() {
let obj = Id::from_owned(RcTestObject::new());
let obj = Id::into_shared(RcTestObject::new());
let array = NSArray::from_slice(&[obj]);
let mut expected = ThreadTestData::current();

Expand All @@ -428,7 +427,7 @@ mod tests {

#[test]
fn test_iter_no_retain() {
let obj = Id::from_owned(RcTestObject::new());
let obj = Id::into_shared(RcTestObject::new());
let array = NSArray::from_slice(&[obj]);
let mut expected = ThreadTestData::current();

Expand Down
6 changes: 3 additions & 3 deletions objc2/src/foundation/attributed_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use super::{
};
use crate::rc::{DefaultId, Id, Shared};
use crate::runtime::Object;
use crate::{extern_class, msg_send, msg_send_id};
use crate::{extern_class, msg_send, msg_send_id, ClassType};

extern_class! {
/// A string that has associated attributes for portions of its text.
Expand Down Expand Up @@ -176,11 +176,11 @@ mod tests {
// NSAttributedString performs this optimization in GNUStep's runtime,
// but not in Apple's; so we don't test for it!
// assert_eq!(Id::as_ptr(&s1), Id::as_ptr(&s2));
assert!(s2.is_kind_of(NSAttributedString::class()));
assert!(s2.is_kind_of::<NSAttributedString>());

let s3 = s1.mutable_copy();
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3).cast());
assert!(s3.is_kind_of(NSMutableAttributedString::class()));
assert!(s3.is_kind_of::<NSMutableAttributedString>());
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion objc2/src/foundation/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use core::slice::{self, SliceIndex};
use super::{NSCopying, NSMutableCopying, NSMutableData, NSObject};
use crate::rc::{DefaultId, Id, Shared};
use crate::runtime::{Class, Object};
use crate::{extern_class, msg_send, msg_send_id};
use crate::{extern_class, msg_send, msg_send_id, ClassType};

extern_class! {
/// A static byte buffer in memory.
Expand Down
2 changes: 1 addition & 1 deletion objc2/src/foundation/dictionary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use core::ptr;

use super::{NSArray, NSCopying, NSEnumerator, NSFastEnumeration, NSObject};
use crate::rc::{DefaultId, Id, Owned, Shared, SliceId};
use crate::{__inner_extern_class, msg_send, msg_send_id, Message};
use crate::{ClassType, __inner_extern_class, msg_send, msg_send_id, Message};

__inner_extern_class! {
#[derive(PartialEq, Eq, Hash)]
Expand Down
3 changes: 1 addition & 2 deletions objc2/src/foundation/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};

use super::{NSCopying, NSDictionary, NSObject, NSString};
use crate::extern_class;
use crate::ffi::NSInteger;
use crate::rc::{Id, Shared};
use crate::{msg_send, msg_send_id};
use crate::{extern_class, msg_send, msg_send_id, ClassType};

extern_class! {
/// Information about an error condition including a domain, a
Expand Down
4 changes: 2 additions & 2 deletions objc2/src/foundation/exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use super::{NSCopying, NSDictionary, NSObject, NSString};
use crate::exception::Exception;
use crate::rc::{Id, Shared};
use crate::runtime::Object;
use crate::{extern_class, msg_send, msg_send_id, sel};
use crate::{extern_class, msg_send, msg_send_id, sel, ClassType};

extern_class! {
/// A special condition that interrupts the normal flow of program
Expand Down Expand Up @@ -94,7 +94,7 @@ impl NSException {
// SAFETY: We only use `isKindOfClass:` on NSObject
let obj: *const Exception = obj;
let obj = unsafe { obj.cast::<NSObject>().as_ref().unwrap() };
obj.is_kind_of(Self::class())
obj.is_kind_of::<Self>()
} else {
false
}
Expand Down
25 changes: 25 additions & 0 deletions objc2/src/foundation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,31 @@
//! [pull requests]: https://github.com/madsmtm/objc2/pulls
//! [`Message`]: crate::Message
//! [`msg_send!`]: crate::msg_send
//!
//!
//! # Use of `Deref`
//!
//! `objc2::foundation` uses the [`Deref`] trait in a bit special way: All
//! objects deref to their superclasses. For example, `NSMutableArray` derefs
//! to `NSArray`, which in turn derefs to `NSObject`.
//!
//! Note that this is explicitly recommended against in [the
//! documentation][`Deref`] and [the Rust Design patterns
//! book][anti-pattern-deref] (see those links for details).
//!
//! Due to Objective-C objects only ever being accessible behind pointers in
//! the first place, the problems stated there are less severe, and having the
//! implementation just means that everything is much nicer when you actually
//! want to use the objects!
//!
//! All objects also implement [`AsRef`] and [`AsMut`] to their superclass,
//! and can be used in [`Id::into_superclass`], so if you favour explicit
//! conversion, that is a possibility too.
//!
//! [`Deref`]: std::ops::Deref
//! [`ClassType`]: crate::ClassType
//! [anti-pattern-deref]: https://rust-unofficial.github.io/patterns/anti_patterns/deref.html
//! [`Id::into_superclass`]: crate::rc::Id::into_superclass
// TODO: Remove these
#![allow(missing_docs)]
Expand Down
3 changes: 1 addition & 2 deletions objc2/src/foundation/mutable_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ use super::{
NSObject,
};
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
use crate::Message;
use crate::{__inner_extern_class, msg_send, msg_send_id};
use crate::{ClassType, Message, __inner_extern_class, msg_send, msg_send_id};

__inner_extern_class! {
/// A growable ordered collection of objects.
Expand Down
6 changes: 3 additions & 3 deletions objc2/src/foundation/mutable_attributed_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use core::fmt;

use super::{NSAttributedString, NSCopying, NSMutableCopying, NSObject, NSString};
use crate::rc::{DefaultId, Id, Owned, Shared};
use crate::{extern_class, msg_send, msg_send_id};
use crate::{extern_class, msg_send, msg_send_id, ClassType};

extern_class! {
/// A mutable string that has associated attributes.
Expand Down Expand Up @@ -100,10 +100,10 @@ mod tests {
let s1 = NSMutableAttributedString::from_nsstring(&NSString::from_str("abc"));
let s2 = s1.copy();
assert_ne!(Id::as_ptr(&s1).cast(), Id::as_ptr(&s2));
assert!(s2.is_kind_of(NSAttributedString::class()));
assert!(s2.is_kind_of::<NSAttributedString>());

let s3 = s1.mutable_copy();
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3));
assert!(s3.is_kind_of(NSMutableAttributedString::class()));
assert!(s3.is_kind_of::<NSMutableAttributedString>());
}
}
2 changes: 1 addition & 1 deletion objc2/src/foundation/mutable_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::io;
use super::data::with_slice;
use super::{NSCopying, NSData, NSMutableCopying, NSObject, NSRange};
use crate::rc::{DefaultId, Id, Owned, Shared};
use crate::{extern_class, msg_send, msg_send_id};
use crate::{extern_class, msg_send, msg_send_id, ClassType};

extern_class! {
/// A dynamic byte buffer in memory.
Expand Down
Loading

0 comments on commit b97bcbb

Please sign in to comment.