Auto merge of #119 - conradev:nsfastenumeration, r=jdm

Added basic NSFastEnumeration implementation

This allows you to turn any Foundation collection into an `Iterator`. The basic implementation is derived from @mikeash's [blog post](https://www.mikeash.com/pyblog/friday-qa-2010-04-16-implementing-fast-enumeration.html) on the subject.

Let me know what you think!

I have not yet decided how best to handle a change in the mutation pointer – that still remains to be implemented.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/cocoa-rs/119)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo
2016-04-01 01:45:56 +05:30
2 changed files with 116 additions and 0 deletions
+74
View File
@@ -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<libc::c_ulong>,
len: usize,
idx: usize,
object: id
}
impl Iterator for NSFastIterator {
type Item = id;
fn next(&mut self) -> Option<id> {
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
}
}
}
+42
View File
@@ -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();
}
}
}
}