diff --git a/src/foundation.rs b/src/foundation.rs index 262bc6d..7f1a357 100644 --- a/src/foundation.rs +++ b/src/foundation.rs @@ -7,6 +7,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use std::ptr; use base::{id, class, BOOL, nil}; use libc; use objc; @@ -256,3 +257,76 @@ pub trait NSDate { impl NSDate for id { } + +#[repr(C)] +struct NSFastEnumerationState { + pub state: libc::c_ulong, + pub items_ptr: *mut id, + pub mutations_ptr: *mut libc::c_ulong, + pub extra: [libc::c_ulong; 5] +} + +const NS_FAST_ENUM_BUF_SIZE: usize = 16; + +pub struct NSFastIterator { + state: NSFastEnumerationState, + buffer: [id; NS_FAST_ENUM_BUF_SIZE], + mut_val: Option, + len: usize, + idx: usize, + object: id +} + +impl Iterator for NSFastIterator { + type Item = id; + + fn next(&mut self) -> Option { + if self.idx >= self.len { + self.len = unsafe { + msg_send![self.object, countByEnumeratingWithState:&mut self.state objects:self.buffer.as_mut_ptr() count:NS_FAST_ENUM_BUF_SIZE] + }; + self.idx = 0; + } + + let new_mut = unsafe { + *self.state.mutations_ptr + }; + + if let Some(old_mut) = self.mut_val { + assert!(old_mut == new_mut, "The collection was mutated while being enumerated"); + } + + if self.idx < self.len { + let object = unsafe { + *self.state.items_ptr.offset(self.idx as isize) + }; + self.mut_val = Some(new_mut); + self.idx += 1; + Some(object) + } else { + None + } + } +} + +pub trait NSFastEnumeration { + unsafe fn iter(self) -> NSFastIterator; +} + +impl NSFastEnumeration for id { + unsafe fn iter(self) -> NSFastIterator { + NSFastIterator { + state: NSFastEnumerationState { + state: 0, + items_ptr: ptr::null_mut(), + mutations_ptr: ptr::null_mut(), + extra: [0; 5] + }, + buffer: [nil; NS_FAST_ENUM_BUF_SIZE], + mut_val: None, + len: 0, + idx: 0, + object: self + } + } +} diff --git a/tests/foundation.rs b/tests/foundation.rs index c4f9130..af6fbc5 100644 --- a/tests/foundation.rs +++ b/tests/foundation.rs @@ -1,3 +1,5 @@ +#[macro_use] +extern crate objc; extern crate cocoa; #[cfg(test)] @@ -56,4 +58,44 @@ mod foundation { } } } + + mod nsfastenumeration { + use std::str; + use std::slice; + use cocoa::foundation::{NSString, NSFastEnumeration}; + use cocoa::base::{id, nil}; + + #[test] + fn test_iter() { + unsafe { + let string = NSString::alloc(nil).init_str("this is a test string"); + let separator = NSString::alloc(nil).init_str(" "); + let components: id = msg_send![string, componentsSeparatedByString:separator]; + + let combined = components.iter() + .map(|s| { + let bytes = s.UTF8String() as *const u8; + str::from_utf8(slice::from_raw_parts(bytes, s.len())).unwrap() + }) + .fold(String::new(), |mut acc, s| { acc.push_str(s); acc }); + + assert_eq!(combined, "thisisateststring"); + } + } + + #[test] + #[should_panic] + fn test_mutation() { + unsafe { + let string = NSString::alloc(nil).init_str("this is a test string"); + let separator = NSString::alloc(nil).init_str(" "); + let components: id = msg_send![string, componentsSeparatedByString:separator]; + let mut_components: id = msg_send![components, mutableCopy]; + let mut iter = mut_components.iter(); + iter.next(); + msg_send![mut_components, removeObjectAtIndex:1]; + iter.next(); + } + } + } }