Skip to content

Commit b97bcbb

Browse files
authored
Merge pull request #234 from madsmtm/class-type
Add `ClassType` trait
2 parents 6f68cd0 + 577af32 commit b97bcbb

35 files changed

+255
-103
lines changed

objc2/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1515
* Added `declare_class!`, `extern_class!` and `ns_string!` macros from
1616
`objc2-foundation`.
1717
* Added helper method `ClassBuilder::add_static_ivar`.
18+
* **BREAKING**: Added `ClassType` trait, and moved the associated `class`
19+
methods that `extern_class!` and `declare_class!` generated to that. This
20+
means you'll have to `use objc2::ClassType` whenever you want to use e.g.
21+
`NSData::class()`.
22+
* Added `Id::into_superclass`.
1823

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

2228

2329
## 0.3.0-beta.1 - 2022-07-19

objc2/CHANGELOG_FOUNDATION.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
3030
type-safety from this, it makes `NSValue` much more useful in the real
3131
world!
3232
* **BREAKING**: Made `NSArray::new` generic over ownership.
33+
* **BREAKING**: Made `NSObject::is_kind_of` take a generic `T: ClassType`
34+
instead of a `runtime::Class`.
3335

3436
### Fixed
3537
* Made `Debug` impls for all objects print something useful.

objc2/examples/class_with_lifetime.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use objc2::foundation::NSObject;
1212
use objc2::rc::{Id, Owned};
1313
use objc2::runtime::{Class, Object, Sel};
1414
use objc2::{msg_send, msg_send_id, sel};
15-
use objc2::{Encoding, Message, RefEncode};
15+
use objc2::{ClassType, Encoding, Message, RefEncode};
1616

1717
/// Helper type for the instance variable
1818
struct NumberIvar<'a> {
@@ -62,8 +62,12 @@ impl<'a> MyObject<'a> {
6262
pub fn set(&mut self, number: u8) {
6363
**self.number = number;
6464
}
65+
}
66+
67+
unsafe impl<'a> ClassType for MyObject<'a> {
68+
type Superclass = NSObject;
6569

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

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

126+
// It is not possible to convert to `Id<NSObject, Owned>` since that would
127+
// loose the lifetime information that `MyObject` stores
128+
// let obj = Id::into_superclass(obj);
129+
122130
println!("Number: {}", obj.get());
123131

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

129-
let obj = Id::from_owned(obj);
137+
let obj = Id::into_shared(obj);
130138
let obj2 = obj.clone();
131139

132140
// We gave up ownership above, so can't edit the number any more!

objc2/examples/delegate.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
#[cfg(all(feature = "apple", target_os = "macos"))]
2-
use objc2::{
3-
declare_class, extern_class,
4-
foundation::NSObject,
5-
msg_send, msg_send_id,
6-
rc::{Id, Shared},
7-
runtime::{Bool, Object},
8-
};
1+
#![cfg_attr(not(all(feature = "apple", target_os = "macos")), allow(unused))]
2+
use objc2::foundation::NSObject;
3+
use objc2::rc::{Id, Shared};
4+
use objc2::runtime::{Bool, Object};
5+
use objc2::{declare_class, extern_class, msg_send, msg_send_id, ClassType};
96

107
#[cfg(all(feature = "apple", target_os = "macos"))]
118
#[link(name = "AppKit", kind = "framework")]

objc2/examples/introspection.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use objc2::foundation::NSObject;
22
use objc2::runtime::Class;
3-
use objc2::sel;
4-
use objc2::Encode;
3+
use objc2::{sel, ClassType, Encode};
54

65
fn main() {
76
// Get the class representing `NSObject`

objc2/examples/nspasteboard.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::mem::ManuallyDrop;
99
use objc2::foundation::{NSArray, NSDictionary, NSInteger, NSObject, NSString};
1010
use objc2::rc::{Id, Shared};
1111
use objc2::runtime::{Class, Object};
12-
use objc2::{extern_class, msg_send, msg_send_bool, msg_send_id};
12+
use objc2::{extern_class, msg_send, msg_send_bool, msg_send_id, ClassType};
1313

1414
type NSPasteboardType = NSString;
1515
type NSPasteboardReadingOptionKey = NSString;

objc2/examples/speech_synthethis.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::time::Duration;
1515

1616
use objc2::foundation::{NSObject, NSString};
1717
use objc2::rc::{Id, Owned};
18-
use objc2::{extern_class, msg_send, msg_send_bool, msg_send_id, ns_string};
18+
use objc2::{extern_class, msg_send, msg_send_bool, msg_send_id, ns_string, ClassType};
1919

2020
#[cfg(all(feature = "apple", target_os = "macos"))]
2121
mod appkit {

objc2/src/__macro_helpers.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ mod tests {
255255
use crate::foundation::NSZone;
256256
use crate::rc::{Allocated, Owned, RcTestObject, Shared, ThreadTestData};
257257
use crate::runtime::Object;
258+
use crate::ClassType;
258259
use crate::{class, msg_send_id};
259260

260261
#[test]

objc2/src/class_type.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use crate::runtime::Class;
2+
use crate::Message;
3+
4+
/// Marks types that represent specific classes.
5+
///
6+
/// Usually it is enough to generically know that a type is messageable, e.g.
7+
/// [`rc::Id`][crate::rc::Id] works with any type that implements the
8+
/// [`Message`] trait. But often, you have an object that you know represents
9+
/// a specific Objective-C class - this trait allows you to communicate that
10+
/// to the rest of the type-system.
11+
///
12+
/// This is implemented automatically by the
13+
/// [`declare_class!`][crate::declare_class] and
14+
/// [`extern_class!`][crate::extern_class] macros.
15+
///
16+
///
17+
/// # Safety
18+
///
19+
/// The class returned by [`Self::class`] must be a subclass of the class that
20+
/// [`Self::Superclass`] represents.
21+
///
22+
/// In pseudocode:
23+
/// ```ignore
24+
/// Self::class().superclass() == <Self::Superclass as ClassType>::class()
25+
/// ```
26+
///
27+
///
28+
/// # Examples
29+
///
30+
/// Use the trait to access the [`Class`] of different objects.
31+
///
32+
/// ```
33+
/// # #[cfg(feature = "gnustep-1-7")]
34+
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
35+
/// use objc2::ClassType;
36+
/// use objc2::foundation::NSObject;
37+
/// // Get a class object representing `NSObject`
38+
/// let cls = <NSObject as ClassType>::class(); // Or just `NSObject::class()`
39+
/// ```
40+
///
41+
/// Use the [`extern_class!`][crate::extern_class] macro to implement this
42+
/// trait for a type.
43+
///
44+
/// ```ignore
45+
/// use objc2::{extern_class, ClassType};
46+
///
47+
/// extern_class! {
48+
/// unsafe struct MyClass: NSObject;
49+
/// }
50+
///
51+
/// let cls = MyClass::class();
52+
/// ```
53+
pub unsafe trait ClassType: Message {
54+
/// The superclass of this class.
55+
///
56+
/// If you have implemented [`Deref`] for your type, it is highly
57+
/// recommended that this is equal to [`Deref::Target`].
58+
///
59+
/// This may be [`runtime::Object`] if the class is a root class.
60+
///
61+
/// [`Deref`]: std::ops::Deref
62+
/// [`Deref::Target`]: std::ops::Deref::Target
63+
/// [`runtime::Object`]: crate::runtime::Object
64+
type Superclass: Message;
65+
66+
/// Get a reference to the Objective-C class that this type represents.
67+
///
68+
/// May register the class with the runtime if it wasn't already.
69+
///
70+
///
71+
/// # Panics
72+
///
73+
/// This may panic if something went wrong with getting or declaring the
74+
/// class, e.g. if the program is not properly linked to the framework
75+
/// that defines the class.
76+
fn class() -> &'static Class;
77+
}

objc2/src/declare.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
//! methods and methods for interfacing with the number.
1616
//!
1717
//! ```
18-
//! use objc2::{class, sel, msg_send, msg_send_id};
1918
//! use objc2::declare::ClassBuilder;
2019
//! use objc2::foundation::NSObject;
2120
//! use objc2::rc::{Id, Owned};
2221
//! use objc2::runtime::{Class, Object, Sel};
22+
//! use objc2::{class, sel, msg_send, msg_send_id, ClassType};
2323
//! # #[cfg(feature = "gnustep-1-7")]
2424
//! # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
2525
//!

objc2/src/foundation/array.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ use super::{
1010
};
1111
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
1212
use crate::runtime::{Class, Object};
13-
use crate::Message;
14-
use crate::{__inner_extern_class, msg_send, msg_send_id};
13+
use crate::{ClassType, Message, __inner_extern_class, msg_send, msg_send_id};
1514

1615
__inner_extern_class! {
1716
/// An immutable ordered collection of objects.
@@ -374,7 +373,7 @@ mod tests {
374373

375374
#[test]
376375
fn test_retains_stored() {
377-
let obj = Id::from_owned(RcTestObject::new());
376+
let obj = Id::into_shared(RcTestObject::new());
378377
let mut expected = ThreadTestData::current();
379378

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

415414
#[test]
416415
fn test_nscopying_uses_retain() {
417-
let obj = Id::from_owned(RcTestObject::new());
416+
let obj = Id::into_shared(RcTestObject::new());
418417
let array = NSArray::from_slice(&[obj]);
419418
let mut expected = ThreadTestData::current();
420419

@@ -428,7 +427,7 @@ mod tests {
428427

429428
#[test]
430429
fn test_iter_no_retain() {
431-
let obj = Id::from_owned(RcTestObject::new());
430+
let obj = Id::into_shared(RcTestObject::new());
432431
let array = NSArray::from_slice(&[obj]);
433432
let mut expected = ThreadTestData::current();
434433

objc2/src/foundation/attributed_string.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use super::{
66
};
77
use crate::rc::{DefaultId, Id, Shared};
88
use crate::runtime::Object;
9-
use crate::{extern_class, msg_send, msg_send_id};
9+
use crate::{extern_class, msg_send, msg_send_id, ClassType};
1010

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

181181
let s3 = s1.mutable_copy();
182182
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3).cast());
183-
assert!(s3.is_kind_of(NSMutableAttributedString::class()));
183+
assert!(s3.is_kind_of::<NSMutableAttributedString>());
184184
}
185185

186186
#[test]

objc2/src/foundation/data.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use core::slice::{self, SliceIndex};
99
use super::{NSCopying, NSMutableCopying, NSMutableData, NSObject};
1010
use crate::rc::{DefaultId, Id, Shared};
1111
use crate::runtime::{Class, Object};
12-
use crate::{extern_class, msg_send, msg_send_id};
12+
use crate::{extern_class, msg_send, msg_send_id, ClassType};
1313

1414
extern_class! {
1515
/// A static byte buffer in memory.

objc2/src/foundation/dictionary.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use core::ptr;
88

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

1313
__inner_extern_class! {
1414
#[derive(PartialEq, Eq, Hash)]

objc2/src/foundation/error.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ use core::fmt;
22
use core::panic::{RefUnwindSafe, UnwindSafe};
33

44
use super::{NSCopying, NSDictionary, NSObject, NSString};
5-
use crate::extern_class;
65
use crate::ffi::NSInteger;
76
use crate::rc::{Id, Shared};
8-
use crate::{msg_send, msg_send_id};
7+
use crate::{extern_class, msg_send, msg_send_id, ClassType};
98

109
extern_class! {
1110
/// Information about an error condition including a domain, a

objc2/src/foundation/exception.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use super::{NSCopying, NSDictionary, NSObject, NSString};
66
use crate::exception::Exception;
77
use crate::rc::{Id, Shared};
88
use crate::runtime::Object;
9-
use crate::{extern_class, msg_send, msg_send_id, sel};
9+
use crate::{extern_class, msg_send, msg_send_id, sel, ClassType};
1010

1111
extern_class! {
1212
/// A special condition that interrupts the normal flow of program
@@ -94,7 +94,7 @@ impl NSException {
9494
// SAFETY: We only use `isKindOfClass:` on NSObject
9595
let obj: *const Exception = obj;
9696
let obj = unsafe { obj.cast::<NSObject>().as_ref().unwrap() };
97-
obj.is_kind_of(Self::class())
97+
obj.is_kind_of::<Self>()
9898
} else {
9999
false
100100
}

objc2/src/foundation/mod.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,31 @@
2121
//! [pull requests]: https://github.com/madsmtm/objc2/pulls
2222
//! [`Message`]: crate::Message
2323
//! [`msg_send!`]: crate::msg_send
24+
//!
25+
//!
26+
//! # Use of `Deref`
27+
//!
28+
//! `objc2::foundation` uses the [`Deref`] trait in a bit special way: All
29+
//! objects deref to their superclasses. For example, `NSMutableArray` derefs
30+
//! to `NSArray`, which in turn derefs to `NSObject`.
31+
//!
32+
//! Note that this is explicitly recommended against in [the
33+
//! documentation][`Deref`] and [the Rust Design patterns
34+
//! book][anti-pattern-deref] (see those links for details).
35+
//!
36+
//! Due to Objective-C objects only ever being accessible behind pointers in
37+
//! the first place, the problems stated there are less severe, and having the
38+
//! implementation just means that everything is much nicer when you actually
39+
//! want to use the objects!
40+
//!
41+
//! All objects also implement [`AsRef`] and [`AsMut`] to their superclass,
42+
//! and can be used in [`Id::into_superclass`], so if you favour explicit
43+
//! conversion, that is a possibility too.
44+
//!
45+
//! [`Deref`]: std::ops::Deref
46+
//! [`ClassType`]: crate::ClassType
47+
//! [anti-pattern-deref]: https://rust-unofficial.github.io/patterns/anti_patterns/deref.html
48+
//! [`Id::into_superclass`]: crate::rc::Id::into_superclass
2449
2550
// TODO: Remove these
2651
#![allow(missing_docs)]

objc2/src/foundation/mutable_array.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ use super::{
1111
NSObject,
1212
};
1313
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
14-
use crate::Message;
15-
use crate::{__inner_extern_class, msg_send, msg_send_id};
14+
use crate::{ClassType, Message, __inner_extern_class, msg_send, msg_send_id};
1615

1716
__inner_extern_class! {
1817
/// A growable ordered collection of objects.

objc2/src/foundation/mutable_attributed_string.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use core::fmt;
22

33
use super::{NSAttributedString, NSCopying, NSMutableCopying, NSObject, NSString};
44
use crate::rc::{DefaultId, Id, Owned, Shared};
5-
use crate::{extern_class, msg_send, msg_send_id};
5+
use crate::{extern_class, msg_send, msg_send_id, ClassType};
66

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

105105
let s3 = s1.mutable_copy();
106106
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3));
107-
assert!(s3.is_kind_of(NSMutableAttributedString::class()));
107+
assert!(s3.is_kind_of::<NSMutableAttributedString>());
108108
}
109109
}

objc2/src/foundation/mutable_data.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::io;
99
use super::data::with_slice;
1010
use super::{NSCopying, NSData, NSMutableCopying, NSObject, NSRange};
1111
use crate::rc::{DefaultId, Id, Owned, Shared};
12-
use crate::{extern_class, msg_send, msg_send_id};
12+
use crate::{extern_class, msg_send, msg_send_id, ClassType};
1313

1414
extern_class! {
1515
/// A dynamic byte buffer in memory.

0 commit comments

Comments
 (0)