Skip to content

Commit

Permalink
Add an rc module with reference counting primitives
Browse files Browse the repository at this point in the history
This fixes #24.
  • Loading branch information
SSheldon committed Jul 22, 2018
1 parent f4f95a4 commit 1171ef8
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 35 deletions.
7 changes: 3 additions & 4 deletions src/exception.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use objc_exception;

use id::StrongPtr;
use rc::StrongPtr;
use runtime::Object;

pub unsafe fn try<F, R>(closure: F) -> Result<R, Option<StrongPtr>>
pub unsafe fn try<F, R>(closure: F) -> Result<R, StrongPtr>
where F: FnOnce() -> R {
objc_exception::try(closure).map_err(|exception| {
if exception.is_null() { None }
else { Some(StrongPtr::new(exception as *mut Object)) }
StrongPtr::new(exception as *mut Object)
})
}
25 changes: 0 additions & 25 deletions src/id.rs

This file was deleted.

3 changes: 1 addition & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,10 @@ mod macros;

pub mod runtime;
pub mod declare;
pub mod rc;
mod encode;
#[cfg(feature = "exception")]
mod exception;
#[cfg(feature = "exception")]
mod id;
mod message;

#[cfg(test)]
Expand Down
11 changes: 7 additions & 4 deletions src/message/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ use {Encode, EncodeArguments};
#[cfg(feature = "exception")]
macro_rules! objc_try {
($b:block) => (
$crate::exception::try(|| $b).map_err(|exception| match exception {
Some(exception) => MessageError(format!("Uncaught exception {:?}", &*exception)),
None => MessageError("Uncaught exception nil".to_owned()),
})
$crate::exception::try(|| $b).map_err(|exception|
if exception.is_null() {
MessageError("Uncaught exception nil".to_owned())
} else {
MessageError(format!("Uncaught exception {:?}", &**exception))
}
)
)
}

Expand Down
69 changes: 69 additions & 0 deletions src/rc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*!
Utilities for reference-counting Objective-C objects.
These utilities provide ARC-like semantics in Rust. They are not intended to
provide a fully safe interface, but can be useful when writing higher-level
Rust wrappers for Objective-C code.
For more information on Objective-C's reference counting, see Apple's documentation:
<https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html>
*/

mod strong;
mod weak;

pub use self::strong::StrongPtr;
pub use self::weak::WeakPtr;

// These tests use NSObject, which isn't present for GNUstep
#[cfg(all(test, any(target_os = "macos", target_os = "ios")))]
mod tests {
use runtime::Object;
use super::StrongPtr;

#[test]
fn test_strong_clone() {
fn retain_count(obj: *mut Object) -> usize {
unsafe { msg_send![obj, retainCount] }
}

let obj = unsafe {
StrongPtr::new(msg_send![class!(NSObject), new])
};
assert!(retain_count(*obj) == 1);

let cloned = obj.clone();
assert!(retain_count(*cloned) == 2);
assert!(retain_count(*obj) == 2);

drop(obj);
assert!(retain_count(*cloned) == 1);
}

#[test]
fn test_weak() {
let obj = unsafe {
StrongPtr::new(msg_send![class!(NSObject), new])
};
let weak = obj.weak();

let strong = weak.load();
assert!(*strong == *obj);
drop(strong);

drop(obj);
assert!(weak.load().is_null());
}

#[test]
fn test_weak_copy() {
let obj = unsafe {
StrongPtr::new(msg_send![class!(NSObject), new])
};
let weak = obj.weak();

let weak2 = weak.clone();
let strong = weak2.load();
assert!(*strong == *obj);
}
}
73 changes: 73 additions & 0 deletions src/rc/strong.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use std::fmt;
use std::mem;
use std::ops::Deref;

use runtime::{Object, self};
use super::WeakPtr;

/// A pointer that strongly references an object, ensuring it won't be deallocated.
pub struct StrongPtr(*mut Object);

impl StrongPtr {
/// Constructs a `StrongPtr` to a newly created object that already has a
/// +1 retain count. This will not retain the object.
/// When dropped, the object will be released.
/// Unsafe because the caller must ensure the given object pointer is valid.
pub unsafe fn new(ptr: *mut Object) -> Self {
StrongPtr(ptr)
}

/// Retains the given object and constructs a `StrongPtr` to it.
/// When dropped, the object will be released.
/// Unsafe because the caller must ensure the given object pointer is valid.
pub unsafe fn retain(ptr: *mut Object) -> Self {
StrongPtr(runtime::objc_retain(ptr))
}

/// Autoreleases self, meaning that the object is not immediately released,
/// but will be when the autorelease pool is drained. A pointer to the
/// object is returned, but its validity is no longer ensured.
pub fn autorelease(self) -> *mut Object {
let ptr = self.0;
mem::forget(self);
unsafe {
runtime::objc_autorelease(ptr);
}
ptr
}

/// Returns a `WeakPtr` to self.
pub fn weak(&self) -> WeakPtr {
unsafe { WeakPtr::new(self.0) }
}
}

impl Drop for StrongPtr {
fn drop(&mut self) {
unsafe {
runtime::objc_release(self.0);
}
}
}

impl Clone for StrongPtr {
fn clone(&self) -> StrongPtr {
unsafe {
StrongPtr::retain(self.0)
}
}
}

impl Deref for StrongPtr {
type Target = *mut Object;

fn deref(&self) -> &*mut Object {
&self.0
}
}

impl fmt::Pointer for StrongPtr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Pointer::fmt(&self.0, f)
}
}
50 changes: 50 additions & 0 deletions src/rc/weak.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use std::cell::UnsafeCell;
use std::ptr;

use runtime::{Object, self};
use super::StrongPtr;

// Our pointer must have the same address even if we are moved, so Box it.
// Although loading the WeakPtr may modify the pointer, it is thread safe,
// so we must use an UnsafeCell to get a *mut without self being mutable.

/// A pointer that weakly references an object, allowing to safely check
/// whether it has been deallocated.
pub struct WeakPtr(Box<UnsafeCell<*mut Object>>);

impl WeakPtr {
/// Constructs a `WeakPtr` to the given object.
/// Unsafe because the caller must ensure the given object pointer is valid.
pub unsafe fn new(obj: *mut Object) -> Self {
let ptr = Box::new(UnsafeCell::new(ptr::null_mut()));
runtime::objc_initWeak(ptr.get(), obj);
WeakPtr(ptr)
}

/// Loads the object self points to, returning a `StrongPtr`.
/// If the object has been deallocated, the returned pointer will be null.
pub fn load(&self) -> StrongPtr {
unsafe {
let ptr = runtime::objc_loadWeakRetained(self.0.get());
StrongPtr::new(ptr)
}
}
}

impl Drop for WeakPtr {
fn drop(&mut self) {
unsafe {
runtime::objc_destroyWeak(self.0.get());
}
}
}

impl Clone for WeakPtr {
fn clone(&self) -> Self {
let ptr = Box::new(UnsafeCell::new(ptr::null_mut()));
unsafe {
runtime::objc_copyWeak(ptr.get(), self.0.get());
}
WeakPtr(ptr)
}
}
9 changes: 9 additions & 0 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@ extern {
pub fn method_getNumberOfArguments(method: *const Method) -> c_uint;
pub fn method_setImplementation(method: *mut Method, imp: Imp) -> Imp;
pub fn method_exchangeImplementations(m1: *mut Method, m2: *mut Method);

pub fn objc_retain(obj: *mut Object) -> *mut Object;
pub fn objc_release(obj: *mut Object);
pub fn objc_autorelease(obj: *mut Object);

pub fn objc_loadWeakRetained(location: *mut *mut Object) -> *mut Object;
pub fn objc_initWeak(location: *mut *mut Object, obj: *mut Object) -> *mut Object;
pub fn objc_destroyWeak(location: *mut *mut Object);
pub fn objc_copyWeak(to: *mut *mut Object, from: *mut *mut Object);
}

impl Sel {
Expand Down
1 change: 1 addition & 0 deletions tests-ios/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ extern crate objc;

pub use objc::*;
use objc::runtime::*;
use objc::rc::*;

#[path = "../src/test_utils.rs"]
mod test_utils;

0 comments on commit 1171ef8

Please sign in to comment.