Skip to content

Commit

Permalink
Add support for protocols, methods for interacting with protocols, an…
Browse files Browse the repository at this point in the history
…d tests
  • Loading branch information
burtonageo committed Apr 21, 2016
1 parent c53507c commit e0a0547
Show file tree
Hide file tree
Showing 3 changed files with 258 additions and 4 deletions.
79 changes: 78 additions & 1 deletion src/declare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use std::ffi::CString;
use std::mem;
use std::ptr;

use runtime::{Class, Imp, NO, Object, Sel, self};
use runtime::{BOOL, Class, Imp, NO, Object, Protocol, Sel, self};
use {Encode, EncodeArguments, Encoding, Message};

/// Types that can be used as the implementation of an Objective-C method.
Expand Down Expand Up @@ -216,6 +216,13 @@ impl ClassDecl {
assert!(success != NO, "Failed to add ivar {}", name);
}

/// Adds a protocol to self. Panics if the protocol wasn't successfully
/// added
pub fn add_protocol(&mut self, proto: &Protocol) {
let success = unsafe { runtime::class_addProtocol(self.cls, proto) };
assert!(success != NO, "Failed to add protocol {:?}", proto);
}

/// Registers self, consuming it and returning a reference to the
/// newly registered `Class`.
pub fn register(self) -> &'static Class {
Expand All @@ -237,6 +244,76 @@ impl Drop for ClassDecl {
}
}

/// A type for declaring a new protocol and adding new methods to it
/// before registering it.
pub struct ProtocolDecl {
proto: *mut Protocol
}

impl ProtocolDecl {
/// Constructs a `ProtocolDecl` with the given name. Returns `None` if the
/// protocol couldn't be allocated.
pub fn new(name: &str) -> Option<ProtocolDecl> {
let c_name = CString::new(name).unwrap();
let proto = unsafe {
runtime::objc_allocateProtocol(c_name.as_ptr())
};
if proto.is_null() {
None
} else {
Some(ProtocolDecl { proto: proto })
}
}

fn add_method_description_common<Args, Ret>(&mut self, sel: Sel, is_required: bool,
is_instance_method: bool)
where Args: EncodeArguments,
Ret: Encode {
let encs = Args::encodings();
let encs = encs.as_ref();
let sel_args = count_args(sel);
assert!(sel_args == encs.len(),
"Selector accepts {} arguments, but function accepts {}",
sel_args, encs.len(),
);
let types = method_type_encoding(&Ret::encode(), encs);
unsafe {
runtime::protocol_addMethodDescription(
self.proto, sel, types.as_ptr(), is_required as BOOL, is_instance_method as BOOL);
}
}

/// Adds an instance method declaration with a given description to self.
pub fn add_method_description<Args, Ret>(&mut self, sel: Sel, is_required: bool)
where Args: EncodeArguments,
Ret: Encode {
self.add_method_description_common::<Args, Ret>(sel, is_required, true)
}

/// Adds a class method declaration with a given description to self.
pub fn add_class_method_description<Args, Ret>(&mut self, sel: Sel, is_required: bool)
where Args: EncodeArguments,
Ret: Encode {
self.add_method_description_common::<Args, Ret>(sel, is_required, false)
}

/// Adds a requirement on another protocol.
pub fn add_protocol(&mut self, proto: &Protocol) {
unsafe {
runtime::protocol_addProtocol(self.proto, proto);
}
}

/// Registers self, consuming it and returning a reference to the
/// newly registered `Protocol`.
pub fn register(self) -> &'static Protocol {
unsafe {
runtime::objc_registerProtocol(self.proto);
&*self.proto
}
}
}

#[cfg(test)]
mod tests {
use test_utils;
Expand Down
131 changes: 130 additions & 1 deletion src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ pub struct Class {
_priv: PrivateMarker,
}

/// A type that represents an Objective-C protocol.
#[repr(C)]
pub struct Protocol {
_priv: PrivateMarker
}

/// A type that represents an instance of a class.
#[repr(C)]
pub struct Object {
Expand All @@ -83,6 +89,9 @@ extern {
pub fn class_copyIvarList(cls: *const Class, outCount: *mut c_uint) -> *mut *const Ivar;
pub fn class_addMethod(cls: *mut Class, name: Sel, imp: Imp, types: *const c_char) -> BOOL;
pub fn class_addIvar(cls: *mut Class, name: *const c_char, size: usize, alignment: u8, types: *const c_char) -> BOOL;
pub fn class_addProtocol(cls: *mut Class, proto: *const Protocol) -> BOOL;
pub fn class_conformsToProtocol(cls: *const Class, proto: *const Protocol) -> BOOL;
pub fn class_copyProtocolList(cls: *const Class, outCount: *mut c_uint) -> *mut *const Protocol;

pub fn objc_allocateClassPair(superclass: *const Class, name: *const c_char, extraBytes: usize) -> *mut Class;
pub fn objc_disposeClassPair(cls: *mut Class);
Expand All @@ -95,6 +104,18 @@ extern {
pub fn objc_getClassList(buffer: *mut *const Class, bufferLen: c_int) -> c_int;
pub fn objc_copyClassList(outCount: *mut c_uint) -> *mut *const Class;
pub fn objc_getClass(name: *const c_char) -> *const Class;
pub fn objc_getProtocol(name: *const c_char) -> *const Protocol;
pub fn objc_copyProtocolList(outCount: *mut c_uint) -> *mut *const Protocol;
pub fn objc_allocateProtocol(name: *const c_char) -> *mut Protocol;
pub fn objc_registerProtocol(proto: *mut Protocol);

pub fn protocol_addMethodDescription(proto: *mut Protocol, name: Sel, types: *const c_char, isRequiredMethod: BOOL,
isInstanceMethod: BOOL);
pub fn protocol_addProtocol(proto: *mut Protocol, addition: *const Protocol);
pub fn protocol_getName(proto: *const Protocol) -> *const c_char;
pub fn protocol_isEqual(proto: *const Protocol, other: *const Protocol) -> BOOL;
pub fn protocol_copyProtocolList(proto: *const Protocol, outCount: *mut c_uint) -> *mut *const Protocol;
pub fn protocol_conformsToProtocol(proto: *const Protocol, other: *const Protocol) -> BOOL;

pub fn ivar_getName(ivar: *const Ivar) -> *const c_char;
pub fn ivar_getOffset(ivar: *const Ivar) -> isize;
Expand Down Expand Up @@ -307,6 +328,20 @@ impl Class {

}

/// Checks whether this class conforms to the specified protocol.
pub fn conforms_to(&self, proto: &Protocol) -> bool {
unsafe { class_conformsToProtocol(self, proto) == YES }
}

/// Get a list of the protocols to which this class conforms.
pub fn adopted_protocols(&self) -> MallocBuffer<&Protocol> {
unsafe {
let mut count: c_uint = 0;
let protos = class_copyProtocolList(self, &mut count);
MallocBuffer::new(protos as *mut _, count as usize).unwrap()
}
}

/// Describes the instance variables declared by self.
pub fn instance_variables(&self) -> MallocBuffer<&Ivar> {
unsafe {
Expand All @@ -333,6 +368,63 @@ impl fmt::Debug for Class {
}
}

impl Protocol {
/// Returns the protocol definition of a specified protocol, or `None` if the
/// protocol is not registered with the Objective-C runtime.
pub fn get(name: &str) -> Option<&'static Protocol> {
let name = CString::new(name).unwrap();
unsafe {
let proto = objc_getProtocol(name.as_ptr());
if proto.is_null() { None } else { Some(&*proto) }
}
}

/// Obtains the list of registered protocol definitions.
pub fn protocols() -> MallocBuffer<&'static Protocol> {
unsafe {
let mut count: c_uint = 0;
let protocols = objc_copyProtocolList(&mut count);
MallocBuffer::new(protocols as *mut _, count as usize).unwrap()
}
}

/// Get a list of the protocols to which this protocol conforms.
pub fn adopted_protocols(&self) -> MallocBuffer<&Protocol> {
unsafe {
let mut count: c_uint = 0;
let protocols = protocol_copyProtocolList(self, &mut count);
MallocBuffer::new(protocols as *mut _, count as usize).unwrap()
}
}

/// Checks whether this protocol conforms to the specified protocol.
pub fn conforms_to(&self, proto: &Protocol) -> bool {
unsafe { protocol_conformsToProtocol(self, proto) == YES }
}

/// Returns the name of self.
pub fn name(&self) -> &str {
let name = unsafe {
CStr::from_ptr(protocol_getName(self))
};
str::from_utf8(name.to_bytes()).unwrap()
}
}

impl PartialEq for Protocol {
fn eq(&self, other: &Protocol) -> bool {
unsafe { protocol_isEqual(self, other) == YES }
}
}

impl Eq for Protocol { }

impl fmt::Debug for Protocol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name())
}
}

impl Object {
/// Returns the class of self.
pub fn class(&self) -> &Class {
Expand Down Expand Up @@ -406,7 +498,7 @@ impl fmt::Debug for Object {
mod tests {
use test_utils;
use Encode;
use super::{Class, Sel};
use super::{Class, Protocol, Sel};

#[test]
fn test_ivar() {
Expand Down Expand Up @@ -456,6 +548,43 @@ mod tests {
assert!(classes.len() > 0);
}

#[test]
fn test_protocol() {
let proto = test_utils::custom_protocol();
assert!(proto.name() == "CustomProtocol");
let class = test_utils::custom_class();
assert!(class.conforms_to(proto));
let class_protocols = class.adopted_protocols();
assert!(class_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));
let adopted_protocols = sub_proto.adopted_protocols();
assert_eq!(adopted_protocols[0], super_proto);
}

#[test]
fn test_protocols() {
// Ensure that a protocol has been registered on linux
let _ = test_utils::custom_protocol();

let protocols = Protocol::protocols();
assert!(protocols.len() > 0);
}

#[test]
fn test_object() {
let mut obj = test_utils::custom_object();
Expand Down
52 changes: 50 additions & 2 deletions src/test_utils.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::ops::{Deref, DerefMut};
use std::os::raw::c_char;
use std::sync::{Once, ONCE_INIT};

use declare::ClassDecl;
use runtime::{Class, Object, Sel, self};
use declare::{ClassDecl, ProtocolDecl};
use runtime::{Class, Object, Protocol, Sel, self};
use {Encode, Encoding};

pub struct CustomObject {
Expand Down Expand Up @@ -69,7 +70,9 @@ pub fn custom_class() -> &'static Class {
extern fn custom_obj_class_initialize(_this: &Class, _cmd: Sel) { }

let mut decl = ClassDecl::root("CustomObject", custom_obj_class_initialize).unwrap();
let proto = custom_protocol();

decl.add_protocol(proto);
decl.add_ivar::<u32>("_foo");

extern fn custom_obj_set_foo(this: &mut Object, _cmd: Sel, foo: u32) {
Expand All @@ -88,6 +91,14 @@ pub fn custom_class() -> &'static Class {
7
}

extern fn custom_obj_set_bar(this: &mut Object, _cmd: Sel, bar: u32) {
unsafe { this.set_ivar::<u32>("_foo", bar) ;}
}

extern fn custom_obj_add_number_to_number(_this: &Class, _cmd: Sel, fst: i32, snd: i32) -> i32 {
fst + snd
}

unsafe {
let set_foo: extern fn(&mut Object, Sel, u32) = custom_obj_set_foo;
decl.add_method(sel!(setFoo:), set_foo);
Expand All @@ -97,6 +108,11 @@ pub fn custom_class() -> &'static Class {
decl.add_method(sel!(customStruct), get_struct);
let class_method: extern fn(&Class, Sel) -> u32 = custom_obj_class_method;
decl.add_class_method(sel!(classFoo), class_method);

let protocol_instance_method: extern fn(&mut Object, Sel, u32) = custom_obj_set_bar;
decl.add_method(sel!(setBar:), protocol_instance_method);
let protocol_class_method: extern fn(&Class, Sel, i32, i32) -> i32 = custom_obj_add_number_to_number;
decl.add_class_method(sel!(addNumber:toNumber:), protocol_class_method);
}

decl.register();
Expand All @@ -105,6 +121,38 @@ pub fn custom_class() -> &'static Class {
Class::get("CustomObject").unwrap()
}

pub fn custom_protocol() -> &'static Protocol {
static REGISTER_CUSTOM_PROTOCOL: Once = ONCE_INIT;

REGISTER_CUSTOM_PROTOCOL.call_once(|| {
let mut decl = ProtocolDecl::new("CustomProtocol").unwrap();

decl.add_method_description::<(i32,), ()>(sel!(setBar:), true);
decl.add_method_description::<(), *const c_char>(sel!(getName), false);
decl.add_class_method_description::<(i32, i32), i32>(sel!(addNumber:toNumber:), true);

decl.register();
});

Protocol::get("CustomProtocol").unwrap()
}

pub fn custom_subprotocol() -> &'static Protocol {
static REGISTER_CUSTOM_SUBPROTOCOL: Once = ONCE_INIT;

REGISTER_CUSTOM_SUBPROTOCOL.call_once(|| {
let super_proto = custom_protocol();
let mut decl = ProtocolDecl::new("CustomSubProtocol").unwrap();

decl.add_protocol(super_proto);
decl.add_method_description::<(u32,), u32>(sel!(calculateFoo:), true);

decl.register();
});

Protocol::get("CustomSubProtocol").unwrap()
}

pub fn custom_object() -> CustomObject {
CustomObject::new(custom_class())
}
Expand Down

0 comments on commit e0a0547

Please sign in to comment.