#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::ffi::c_void;
use core::{borrow::Borrow, mem};
use crate::{kCFTypeArrayCallBacks, CFArray, CFIndex, CFMutableArray, CFRetained, Type};
#[inline]
fn get_len<T>(objects: &[T]) -> CFIndex {
let len = objects.len();
debug_assert!(len < CFIndex::MAX as usize);
len as CFIndex
}
#[cold]
fn failed_creating_array(len: CFIndex) -> ! {
#[cfg(feature = "alloc")]
{
use alloc::alloc::{handle_alloc_error, Layout};
use core::mem::align_of;
let layout = Layout::array::<*const ()>(len as usize).unwrap_or_else(|_| unsafe {
Layout::from_size_align_unchecked(0, align_of::<*const ()>())
});
handle_alloc_error(layout)
}
#[cfg(not(feature = "alloc"))]
{
panic!("failed allocating CFArray holding {len} elements")
}
}
impl<T: ?Sized> CFArray<T> {
#[inline]
#[doc(alias = "CFArray::new")]
pub fn empty() -> CFRetained<Self>
where
T: Type,
{
Self::from_objects(&[])
}
#[inline]
#[doc(alias = "CFArray::new")]
pub fn from_objects(objects: &[&T]) -> CFRetained<Self>
where
T: Type,
{
let len = get_len(objects);
let ptr = objects.as_ptr().cast::<*const c_void>().cast_mut();
let array = unsafe { CFArray::new(None, ptr, len, &kCFTypeArrayCallBacks) }
.unwrap_or_else(|| failed_creating_array(len));
unsafe { CFRetained::cast_unchecked::<Self>(array) }
}
#[inline]
#[allow(non_snake_case)]
#[deprecated = "renamed to CFArray::from_objects"]
pub fn from_CFTypes(objects: &[&T]) -> CFRetained<Self>
where
T: Type,
{
Self::from_objects(objects)
}
#[inline]
#[doc(alias = "CFArray::new")]
pub fn from_retained_objects(objects: &[CFRetained<T>]) -> CFRetained<Self>
where
T: Type,
{
let len = get_len(objects);
let ptr = objects.as_ptr().cast::<*const c_void>().cast_mut();
let array = unsafe { CFArray::new(None, ptr, len, &kCFTypeArrayCallBacks) }
.unwrap_or_else(|| failed_creating_array(len));
unsafe { CFRetained::cast_unchecked::<Self>(array) }
}
}
impl<T: ?Sized> CFMutableArray<T> {
#[inline]
#[doc(alias = "CFMutableArray::new")]
pub fn empty() -> CFRetained<Self>
where
T: Type,
{
Self::with_capacity(0)
}
#[inline]
#[doc(alias = "CFMutableArray::new")]
pub fn with_capacity(capacity: usize) -> CFRetained<Self>
where
T: Type,
{
let capacity = capacity.try_into().expect("capacity too high");
let array = unsafe { CFMutableArray::new(None, capacity, &kCFTypeArrayCallBacks) }
.unwrap_or_else(|| failed_creating_array(capacity));
unsafe { CFRetained::cast_unchecked::<Self>(array) }
}
}
impl<T: ?Sized> CFArray<T> {
#[inline]
#[doc(alias = "CFArrayGetValueAtIndex")]
pub unsafe fn get_unchecked(&self, index: CFIndex) -> &T
where
T: Type + Sized,
{
let ptr = unsafe { self.as_opaque().value_at_index(index) };
unsafe { &*ptr.cast::<T>() }
}
#[cfg(feature = "alloc")]
#[doc(alias = "CFArrayGetValues")]
pub unsafe fn to_vec_unchecked(&self) -> Vec<&T>
where
T: Type,
{
let len = self.len();
let range = crate::CFRange {
location: 0,
length: len as CFIndex,
};
let mut vec = Vec::<&T>::with_capacity(len);
let ptr = vec.as_mut_ptr().cast::<*const c_void>();
unsafe { self.as_opaque().values(range, ptr) };
unsafe { vec.set_len(len) };
vec
}
#[inline]
pub unsafe fn iter_unchecked(&self) -> CFArrayIterUnchecked<'_, T>
where
T: Type,
{
CFArrayIterUnchecked {
array: self,
index: 0,
len: self.len() as CFIndex,
}
}
}
impl<T: ?Sized> CFArray<T> {
pub fn as_opaque(&self) -> &CFArray {
unsafe { mem::transmute::<&CFArray<T>, &CFArray>(self) }
}
#[inline]
#[doc(alias = "CFArrayGetCount")]
pub fn len(&self) -> usize {
self.as_opaque().count() as _
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[doc(alias = "CFArrayGetValueAtIndex")]
pub fn get(&self, index: usize) -> Option<CFRetained<T>>
where
T: Type + Sized,
{
if index < self.len() {
let index = index as CFIndex;
Some(unsafe { self.get_unchecked(index) }.retain())
} else {
None
}
}
#[cfg(feature = "alloc")]
#[doc(alias = "CFArrayGetValues")]
pub fn to_vec(&self) -> Vec<CFRetained<T>>
where
T: Type + Sized,
{
let vec = unsafe { self.to_vec_unchecked() };
vec.into_iter().map(T::retain).collect()
}
#[inline]
pub fn iter(&self) -> CFArrayIter<'_, T> {
CFArrayIter {
array: self,
index: 0,
}
}
}
impl<T: ?Sized> CFMutableArray<T> {
pub fn as_opaque(&self) -> &CFMutableArray {
unsafe { mem::transmute::<&CFMutableArray<T>, &CFMutableArray>(self) }
}
}
impl<T> CFMutableArray<T> {
#[inline]
#[doc(alias = "CFArrayAppendValue")]
pub fn append(&self, obj: &T) {
let ptr: *const T = obj;
let ptr: *const c_void = ptr.cast();
unsafe { CFMutableArray::append_value(Some(self.as_opaque()), ptr) }
}
#[doc(alias = "CFArrayInsertValueAtIndex")]
pub fn insert(&self, index: usize, obj: &T) {
let len = self.len();
if index <= len {
let ptr: *const T = obj;
let ptr: *const c_void = ptr.cast();
unsafe {
CFMutableArray::insert_value_at_index(Some(self.as_opaque()), index as CFIndex, ptr)
}
} else {
panic!(
"insertion index (is {}) should be <= len (is {})",
index, len
);
}
}
}
#[derive(Debug)]
pub struct CFArrayIter<'a, T: ?Sized + 'a> {
array: &'a CFArray<T>,
index: usize,
}
impl<T: Type> Iterator for CFArrayIter<'_, T> {
type Item = CFRetained<T>;
fn next(&mut self) -> Option<CFRetained<T>> {
let value = self.array.get(self.index)?;
self.index += 1;
Some(value)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.array.len().saturating_sub(self.index);
(len, Some(len))
}
}
impl<T: Type> ExactSizeIterator for CFArrayIter<'_, T> {}
#[derive(Debug)]
pub struct CFArrayIntoIter<T: ?Sized> {
array: CFRetained<CFArray<T>>,
index: usize,
}
impl<T: Type> Iterator for CFArrayIntoIter<T> {
type Item = CFRetained<T>;
fn next(&mut self) -> Option<CFRetained<T>> {
let value = self.array.get(self.index)?;
self.index += 1;
Some(value)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.array.len().saturating_sub(self.index);
(len, Some(len))
}
}
impl<T: Type> ExactSizeIterator for CFArrayIntoIter<T> {}
impl<'a, T: Type> IntoIterator for &'a CFArray<T> {
type Item = CFRetained<T>;
type IntoIter = CFArrayIter<'a, T>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, T: Type> IntoIterator for &'a CFMutableArray<T> {
type Item = CFRetained<T>;
type IntoIter = CFArrayIter<'a, T>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<T: Type> IntoIterator for CFRetained<CFArray<T>> {
type Item = CFRetained<T>;
type IntoIter = CFArrayIntoIter<T>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
CFArrayIntoIter {
array: self,
index: 0,
}
}
}
impl<T: Type> IntoIterator for CFRetained<CFMutableArray<T>> {
type Item = CFRetained<T>;
type IntoIter = CFArrayIntoIter<T>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
let array = unsafe { CFRetained::cast_unchecked::<CFArray<T>>(self) };
CFArrayIntoIter { array, index: 0 }
}
}
#[derive(Debug)]
pub struct CFArrayIterUnchecked<'a, T: ?Sized + 'a> {
array: &'a CFArray<T>,
index: CFIndex,
len: CFIndex,
}
impl<'a, T: Type> Iterator for CFArrayIterUnchecked<'a, T> {
type Item = &'a T;
#[inline]
fn next(&mut self) -> Option<&'a T> {
debug_assert_eq!(
self.array.len(),
self.len as usize,
"array was mutated while iterating"
);
if self.index < self.len {
let value = unsafe { self.array.get_unchecked(self.index) };
self.index += 1;
Some(value)
} else {
None
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = (self.len - self.index) as usize;
(len, Some(len))
}
}
impl<T: Type> ExactSizeIterator for CFArrayIterUnchecked<'_, T> {}
impl<T: ?Sized + Type> AsRef<CFArray> for CFArray<T> {
fn as_ref(&self) -> &CFArray {
self.as_opaque()
}
}
impl<T: ?Sized + Type> AsRef<CFMutableArray> for CFMutableArray<T> {
fn as_ref(&self) -> &CFMutableArray {
self.as_opaque()
}
}
impl<T: ?Sized + Type> Borrow<CFArray> for CFArray<T> {
fn borrow(&self) -> &CFArray {
self.as_opaque()
}
}
impl<T: ?Sized + Type> Borrow<CFMutableArray> for CFMutableArray<T> {
fn borrow(&self) -> &CFMutableArray {
self.as_opaque()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "CFString")]
use crate::CFString;
use core::ptr::null;
#[test]
fn array_with_invalid_pointers() {
let ptr = [0 as _, 1 as _, 2 as _, 3 as _, usize::MAX as _].as_mut_ptr();
let array = unsafe { CFArray::new(None, ptr, 1, null()) }.unwrap();
let value = unsafe { array.value_at_index(0) };
assert!(value.is_null());
}
#[test]
#[should_panic]
#[ignore = "aborts (as expected)"]
fn object_array_cannot_contain_null() {
let ptr = [null()].as_mut_ptr();
let _array = unsafe { CFArray::new(None, ptr, 1, &kCFTypeArrayCallBacks) };
}
#[test]
#[cfg(feature = "CFString")]
fn correct_retain_count() {
let objects = [
CFString::from_str("some long string that doesn't get small-string optimized"),
CFString::from_str("another long string that doesn't get small-string optimized"),
];
let array = CFArray::from_retained_objects(&objects);
assert_eq!(array.retain_count(), 1);
assert_eq!(unsafe { array.get_unchecked(0) }.retain_count(), 2);
assert_eq!(unsafe { array.get_unchecked(1) }.retain_count(), 2);
drop(objects);
assert_eq!(unsafe { array.get_unchecked(0) }.retain_count(), 1);
assert_eq!(unsafe { array.get_unchecked(1) }.retain_count(), 1);
let _array2 = array.retain();
assert_eq!(unsafe { array.get_unchecked(0) }.retain_count(), 1);
assert_eq!(unsafe { array.get_unchecked(1) }.retain_count(), 1);
assert_eq!(array.get(0).unwrap().retain_count(), 2);
}
#[test]
#[cfg(feature = "CFString")]
fn iter() {
use alloc::vec::Vec;
let s1 = CFString::from_str("a");
let s2 = CFString::from_str("b");
let array = CFArray::from_objects(&[&*s1, &*s2, &*s1]);
assert_eq!(
array.iter().collect::<Vec<_>>(),
[s1.clone(), s2.clone(), s1.clone()]
);
assert_eq!(
unsafe { array.iter_unchecked() }.collect::<Vec<_>>(),
[&*s1, &*s2, &*s1]
);
}
#[test]
#[cfg(feature = "CFString")]
fn iter_fused() {
let s1 = CFString::from_str("a");
let s2 = CFString::from_str("b");
let array = CFArray::from_objects(&[&*s1, &*s2]);
let mut iter = array.iter();
assert_eq!(iter.next(), Some(s1.clone()));
assert_eq!(iter.next(), Some(s2.clone()));
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
let mut iter = unsafe { array.iter_unchecked() };
assert_eq!(iter.next(), Some(&*s1));
assert_eq!(iter.next(), Some(&*s2));
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
}
#[test]
#[cfg(feature = "CFString")]
fn mutate() {
let array = CFMutableArray::<CFString>::with_capacity(10);
array.insert(0, &CFString::from_str("a"));
array.append(&CFString::from_str("c"));
array.insert(1, &CFString::from_str("b"));
assert_eq!(
array.to_vec(),
[
CFString::from_str("a"),
CFString::from_str("b"),
CFString::from_str("c"),
]
);
}
#[test]
#[cfg(feature = "CFString")]
#[cfg_attr(
not(debug_assertions),
ignore = "not detected when debug assertions are off"
)]
#[should_panic = "array was mutated while iterating"]
fn mutate_while_iter_unchecked() {
let array = CFMutableArray::<CFString>::with_capacity(10);
assert_eq!(array.len(), 0);
let mut iter = unsafe { array.iter_unchecked() };
array.append(&CFString::from_str("a"));
let _ = iter.next();
}
}