use core::ffi::c_void;
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr;
use core::str;
#[cfg(feature = "malloc")]
use malloc_buf::Malloc;
use std::ffi::{CStr, CString};
#[cfg(feature = "malloc")]
use std::os::raw::c_uint;
pub use super::bool::Bool;
use crate::{ffi, Encode, Encoding, RefEncode};
#[deprecated = "Use `Bool` or `ffi::BOOL` instead"]
#[allow(non_upper_case_globals)]
pub type BOOL = ffi::BOOL;
#[deprecated = "Use `Bool::YES` or `ffi::YES` instead"]
pub const YES: ffi::BOOL = ffi::YES;
#[deprecated = "Use `Bool::NO` or `ffi::NO` instead"]
pub const NO: ffi::BOOL = ffi::NO;
#[repr(transparent)]
pub struct Sel {
ptr: *const ffi::objc_selector,
}
#[repr(C)]
pub struct Ivar(ffi::objc_ivar);
#[repr(C)]
pub struct Method(ffi::objc_method);
#[repr(C)]
pub struct Class(ffi::objc_class);
#[repr(C)]
pub struct Protocol(ffi::Protocol);
#[repr(C)]
pub struct Object(ffi::objc_object);
pub type Imp = unsafe extern "C" fn();
impl Sel {
pub fn register(name: &str) -> Self {
let name = CString::new(name).unwrap();
Self {
ptr: unsafe { ffi::sel_registerName(name.as_ptr()) },
}
}
pub fn name(&self) -> &str {
let name = unsafe { CStr::from_ptr(ffi::sel_getName(self.ptr)) };
str::from_utf8(name.to_bytes()).unwrap()
}
#[inline]
pub unsafe fn from_ptr(ptr: *const c_void) -> Self {
Self {
ptr: ptr as *const _,
}
}
#[inline]
pub fn as_ptr(&self) -> *const c_void {
self.ptr as *const _
}
}
unsafe impl Encode for Sel {
const ENCODING: Encoding<'static> = Encoding::Sel;
}
impl PartialEq for Sel {
fn eq(&self, other: &Sel) -> bool {
self.ptr == other.ptr
}
}
impl Eq for Sel {}
unsafe impl Sync for Sel {}
unsafe impl Send for Sel {}
impl UnwindSafe for Sel {}
impl RefUnwindSafe for Sel {}
impl Copy for Sel {}
impl Clone for Sel {
fn clone(&self) -> Self {
Self { ptr: self.ptr }
}
}
impl fmt::Debug for Sel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
impl Ivar {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_ivar {
self as *const Self as *const _
}
pub fn name(&self) -> &str {
let name = unsafe { CStr::from_ptr(ffi::ivar_getName(self.as_ptr())) };
str::from_utf8(name.to_bytes()).unwrap()
}
pub fn offset(&self) -> isize {
let offset = unsafe { ffi::ivar_getOffset(self.as_ptr()) };
offset as isize
}
pub fn type_encoding(&self) -> &str {
let encoding = unsafe { CStr::from_ptr(ffi::ivar_getTypeEncoding(self.as_ptr())) };
str::from_utf8(encoding.to_bytes()).unwrap()
}
}
unsafe impl Sync for Ivar {}
unsafe impl Send for Ivar {}
impl UnwindSafe for Ivar {}
impl RefUnwindSafe for Ivar {}
impl Method {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_method {
self as *const Self as *const _
}
pub fn name(&self) -> Sel {
Sel {
ptr: unsafe { ffi::method_getName(self.as_ptr()) },
}
}
#[cfg(feature = "malloc")]
pub fn return_type(&self) -> Malloc<str> {
unsafe {
let encoding = ffi::method_copyReturnType(self.as_ptr());
Malloc::from_c_str(encoding).unwrap()
}
}
#[cfg(feature = "malloc")]
pub fn argument_type(&self, index: usize) -> Option<Malloc<str>> {
unsafe {
let encoding = ffi::method_copyArgumentType(self.as_ptr(), index as c_uint);
if encoding.is_null() {
None
} else {
Some(Malloc::from_c_str(encoding).unwrap())
}
}
}
pub fn arguments_count(&self) -> usize {
unsafe { ffi::method_getNumberOfArguments(self.as_ptr()) as usize }
}
pub fn implementation(&self) -> Imp {
unsafe { ffi::method_getImplementation(self.as_ptr()).expect("Null IMP") }
}
}
unsafe impl Sync for Method {}
unsafe impl Send for Method {}
impl UnwindSafe for Method {}
impl RefUnwindSafe for Method {}
impl Class {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_class {
self as *const Self as *const _
}
pub fn get(name: &str) -> Option<&'static Self> {
let name = CString::new(name).unwrap();
unsafe {
let cls = ffi::objc_getClass(name.as_ptr());
if cls.is_null() {
None
} else {
Some(&*(cls as *const Self))
}
}
}
#[cfg(feature = "malloc")]
pub fn classes() -> Malloc<[&'static Self]> {
unsafe {
let mut count: c_uint = 0;
let classes = ffi::objc_copyClassList(&mut count);
Malloc::from_array(classes as *mut _, count as usize)
}
}
pub fn classes_count() -> usize {
unsafe { ffi::objc_getClassList(ptr::null_mut(), 0) as usize }
}
pub fn name(&self) -> &str {
let name = unsafe { CStr::from_ptr(ffi::class_getName(self.as_ptr())) };
str::from_utf8(name.to_bytes()).unwrap()
}
pub fn superclass(&self) -> Option<&Class> {
unsafe {
let superclass = ffi::class_getSuperclass(self.as_ptr());
if superclass.is_null() {
None
} else {
Some(&*(superclass as *const Self))
}
}
}
pub fn metaclass(&self) -> &Self {
unsafe { &*(ffi::object_getClass(self.as_ptr() as *const _) as *const Self) }
}
#[allow(unused)]
fn is_metaclass(&self) -> bool {
unsafe { Bool::from_raw(ffi::class_isMetaClass(self.as_ptr())).is_true() }
}
pub fn instance_size(&self) -> usize {
unsafe { ffi::class_getInstanceSize(self.as_ptr()) as usize }
}
pub fn instance_method(&self, sel: Sel) -> Option<&Method> {
unsafe {
let method = ffi::class_getInstanceMethod(self.as_ptr(), sel.ptr);
if method.is_null() {
None
} else {
Some(&*(method as *const Method))
}
}
}
pub fn instance_variable(&self, name: &str) -> Option<&Ivar> {
let name = CString::new(name).unwrap();
unsafe {
let ivar = ffi::class_getInstanceVariable(self.as_ptr(), name.as_ptr());
if ivar.is_null() {
None
} else {
Some(&*(ivar as *const Ivar))
}
}
}
#[allow(unused)]
fn instance_variable_layout(&self) -> Option<&[u8]> {
let layout = unsafe { ffi::class_getIvarLayout(self.as_ptr()) };
if layout.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(layout as *const _) }.to_bytes())
}
}
#[allow(unused)]
fn class_variable(&self, name: &str) -> Option<&Ivar> {
let name = CString::new(name).unwrap();
let ivar = unsafe { ffi::class_getClassVariable(self.as_ptr(), name.as_ptr()) };
unsafe { ivar.cast::<Ivar>().as_ref() }
}
#[cfg(feature = "malloc")]
pub fn instance_methods(&self) -> Malloc<[&Method]> {
unsafe {
let mut count: c_uint = 0;
let methods = ffi::class_copyMethodList(self.as_ptr(), &mut count);
Malloc::from_array(methods as *mut _, count as usize)
}
}
pub fn conforms_to(&self, proto: &Protocol) -> bool {
unsafe {
Bool::from_raw(ffi::class_conformsToProtocol(self.as_ptr(), proto.as_ptr())).is_true()
}
}
#[cfg(feature = "malloc")]
pub fn adopted_protocols(&self) -> Malloc<[&Protocol]> {
unsafe {
let mut count: c_uint = 0;
let protos = ffi::class_copyProtocolList(self.as_ptr(), &mut count);
Malloc::from_array(protos as *mut _, count as usize)
}
}
#[cfg(feature = "malloc")]
pub fn instance_variables(&self) -> Malloc<[&Ivar]> {
unsafe {
let mut count: c_uint = 0;
let ivars = ffi::class_copyIvarList(self.as_ptr(), &mut count);
Malloc::from_array(ivars as *mut _, count as usize)
}
}
}
unsafe impl Sync for Class {}
unsafe impl Send for Class {}
impl UnwindSafe for Class {}
impl RefUnwindSafe for Class {}
unsafe impl RefEncode for Class {
const ENCODING_REF: Encoding<'static> = Encoding::Class;
}
impl PartialEq for Class {
fn eq(&self, other: &Class) -> bool {
self.as_ptr() == other.as_ptr()
}
}
impl Eq for Class {}
impl fmt::Debug for Class {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
impl Protocol {
pub(crate) fn as_ptr(&self) -> *const ffi::Protocol {
self as *const Self as *const _
}
pub fn get(name: &str) -> Option<&'static Protocol> {
let name = CString::new(name).unwrap();
unsafe {
let proto = ffi::objc_getProtocol(name.as_ptr());
if proto.is_null() {
None
} else {
Some(&*(proto as *const Self))
}
}
}
#[cfg(feature = "malloc")]
pub fn protocols() -> Malloc<[&'static Protocol]> {
unsafe {
let mut count: c_uint = 0;
let protocols = ffi::objc_copyProtocolList(&mut count);
Malloc::from_array(protocols as *mut _, count as usize)
}
}
#[cfg(feature = "malloc")]
pub fn adopted_protocols(&self) -> Malloc<[&Protocol]> {
unsafe {
let mut count: c_uint = 0;
let protocols = ffi::protocol_copyProtocolList(self.as_ptr(), &mut count);
Malloc::from_array(protocols as *mut _, count as usize)
}
}
pub fn conforms_to(&self, proto: &Protocol) -> bool {
unsafe {
Bool::from_raw(ffi::protocol_conformsToProtocol(
self.as_ptr(),
proto.as_ptr(),
))
.is_true()
}
}
pub fn name(&self) -> &str {
let name = unsafe { CStr::from_ptr(ffi::protocol_getName(self.as_ptr())) };
str::from_utf8(name.to_bytes()).unwrap()
}
}
impl PartialEq for Protocol {
fn eq(&self, other: &Protocol) -> bool {
unsafe { Bool::from_raw(ffi::protocol_isEqual(self.as_ptr(), other.as_ptr())).is_true() }
}
}
unsafe impl RefEncode for Protocol {
const ENCODING_REF: Encoding<'static> = Encoding::Object;
}
impl Eq for Protocol {}
impl fmt::Debug for Protocol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
unsafe impl Sync for Protocol {}
unsafe impl Send for Protocol {}
impl UnwindSafe for Protocol {}
impl RefUnwindSafe for Protocol {}
fn get_ivar_offset<T: Encode>(cls: &Class, name: &str) -> isize {
match cls.instance_variable(name) {
Some(ivar) => {
assert!(T::ENCODING.equivalent_to_str(ivar.type_encoding()));
ivar.offset()
}
None => panic!("Ivar {} not found on class {:?}", name, cls),
}
}
impl Object {
pub(crate) fn as_ptr(&self) -> *const ffi::objc_object {
self as *const Self as *const _
}
pub fn class(&self) -> &Class {
unsafe { &*(ffi::object_getClass(self.as_ptr()) as *const Class) }
}
pub unsafe fn get_ivar<T: Encode>(&self, name: &str) -> &T {
let offset = get_ivar_offset::<T>(self.class(), name);
let ptr = self as *const Self as *const u8;
let ptr = unsafe { ptr.offset(offset) } as *const T;
unsafe { &*ptr }
}
pub unsafe fn get_mut_ivar<T: Encode>(&mut self, name: &str) -> &mut T {
let offset = get_ivar_offset::<T>(self.class(), name);
let ptr = self as *mut Self as *mut u8;
let ptr = unsafe { ptr.offset(offset) } as *mut T;
unsafe { &mut *ptr }
}
pub unsafe fn set_ivar<T: Encode>(&mut self, name: &str, value: T) {
unsafe { *self.get_mut_ivar::<T>(name) = value };
}
}
#[cfg(doctest)]
pub struct ObjectNotSendNorSync;
unsafe impl RefEncode for Object {
const ENCODING_REF: Encoding<'static> = Encoding::Object;
}
impl fmt::Debug for Object {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<{:?}: {:p}>", self.class(), self.as_ptr())
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::{Bool, Class, Imp, Ivar, Method, Object, Protocol, Sel};
use crate::test_utils;
use crate::Encode;
#[test]
fn test_ivar() {
let cls = test_utils::custom_class();
let ivar = cls.instance_variable("_foo").unwrap();
assert_eq!(ivar.name(), "_foo");
assert!(<u32>::ENCODING.equivalent_to_str(ivar.type_encoding()));
assert!(ivar.offset() > 0);
#[cfg(feature = "malloc")]
assert!(cls.instance_variables().len() > 0);
}
#[test]
fn test_method() {
let cls = test_utils::custom_class();
let sel = Sel::register("foo");
let method = cls.instance_method(sel).unwrap();
assert_eq!(method.name().name(), "foo");
assert_eq!(method.arguments_count(), 2);
#[cfg(feature = "malloc")]
{
assert!(<u32>::ENCODING.equivalent_to_str(&method.return_type()));
assert!(Sel::ENCODING.equivalent_to_str(&method.argument_type(1).unwrap()));
let methods = cls.instance_methods();
assert!(methods.len() > 0);
}
}
#[test]
fn test_class() {
let cls = test_utils::custom_class();
assert_eq!(cls.name(), "CustomObject");
assert!(cls.instance_size() > 0);
assert!(cls.superclass().is_none());
assert_eq!(Class::get(cls.name()), Some(cls));
let metaclass = cls.metaclass();
assert_eq!(metaclass.superclass().unwrap(), cls);
let subclass = test_utils::custom_subclass();
assert_eq!(subclass.superclass().unwrap(), cls);
}
#[test]
fn test_classes_count() {
assert!(Class::classes_count() > 0);
}
#[test]
#[cfg(feature = "malloc")]
fn test_classes() {
let classes = Class::classes();
assert!(classes.len() > 0);
}
#[test]
fn test_protocol() {
let proto = test_utils::custom_protocol();
assert_eq!(proto.name(), "CustomProtocol");
let class = test_utils::custom_class();
assert!(class.conforms_to(proto));
#[cfg(feature = "malloc")]
assert!(class.adopted_protocols().len() > 0);
}
#[test]
fn test_protocol_method() {
let class = test_utils::custom_class();
let result: i32 = unsafe { msg_send![class, addNumber: 1, toNumber: 2] };
assert_eq!(result, 3);
}
#[test]
fn test_subprotocols() {
let sub_proto = test_utils::custom_subprotocol();
let super_proto = test_utils::custom_protocol();
assert!(sub_proto.conforms_to(super_proto));
#[cfg(feature = "malloc")]
assert_eq!(sub_proto.adopted_protocols()[0], super_proto);
}
#[test]
fn test_protocols() {
let _ = test_utils::custom_protocol();
#[cfg(feature = "malloc")]
assert!(Protocol::protocols().len() > 0);
}
#[test]
fn test_object() {
let mut obj = test_utils::custom_object();
assert_eq!(obj.class(), test_utils::custom_class());
let result: u32 = unsafe {
obj.set_ivar("_foo", 4u32);
*obj.get_ivar("_foo")
};
assert_eq!(result, 4);
}
#[test]
fn test_encode() {
fn assert_enc<T: Encode>(expected: &str) {
assert_eq!(&T::ENCODING.to_string(), expected);
}
assert_enc::<&Object>("@");
assert_enc::<*mut Object>("@");
assert_enc::<&Class>("#");
assert_enc::<Sel>(":");
assert_enc::<Imp>("^?");
assert_enc::<Option<Imp>>("^?");
assert_enc::<&Protocol>("@");
}
#[test]
fn test_send_sync() {
fn assert_send_sync<T: Send + Sync + ?Sized>() {}
assert_send_sync::<Bool>();
assert_send_sync::<Class>();
assert_send_sync::<Ivar>();
assert_send_sync::<Method>();
assert_send_sync::<Protocol>();
assert_send_sync::<Sel>();
}
}