Skip to main content

apple_cf/cf/
collections.rs

1//! Core Foundation collection wrappers.
2//!
3#![allow(clippy::missing_panics_doc)]
4
5//! ```rust
6//! use apple_cf::cf::{
7//!     CFArray, CFAttributedString, CFBag, CFDictionary, CFMutableSet, CFSet,
8//!     CFSetCallbacks, CFString, CFTree,
9//! };
10//!
11//! let first = CFString::new("first");
12//! let second = CFString::new("second");
13//! let array = CFArray::from_values(&[&first, &second]);
14//! assert_eq!(array.len(), 2);
15//!
16//! let dict = CFDictionary::from_pairs(&[(&first, &second)]);
17//! assert!(dict.contains_key(&first));
18//!
19//! let bag = CFBag::from_values(&[&first, &first, &second]);
20//! assert_eq!(bag.count_of_value(&first), 2);
21//!
22//! let set = CFSet::from_values(&[&first, &second]);
23//! assert!(set.contains(&first));
24//!
25//! let mutable_set = CFMutableSet::with_callbacks(0, CFSetCallbacks::Type);
26//! mutable_set.add(&first);
27//! mutable_set.set(&second);
28//! assert_eq!(mutable_set.len(), 2);
29//!
30//! let attributed = CFAttributedString::new(&first);
31//! assert_eq!(attributed.string().to_string(), "first");
32//!
33//! let root = CFTree::new(Some(&first));
34//! let child = CFTree::new(Some(&second));
35//! root.append_child(&child);
36//! assert_eq!(root.child_count(), 1);
37//! ```
38
39use super::base::{impl_cf_type_wrapper, AsCFType, CFType, SwiftObject};
40use super::CFString;
41use crate::{ffi, utils::panic_safe};
42use std::ffi::c_void;
43use std::fmt;
44
45impl_cf_type_wrapper!(CFArray, cf_array_get_type_id);
46impl_cf_type_wrapper!(CFDictionary, cf_dictionary_get_type_id);
47/// Alias for `CFDictionary`.
48pub type CFDict = CFDictionary;
49impl_cf_type_wrapper!(CFBag, cf_bag_get_type_id);
50impl_cf_type_wrapper!(CFSet, cf_set_get_type_id);
51impl_cf_type_wrapper!(CFMutableSet, cf_set_get_type_id);
52impl_cf_type_wrapper!(CFAttributedString, cf_attributed_string_get_type_id);
53
54/// Safe representation of the `CFSetCallBacks` / `kCFTypeSetCallBacks` /
55/// `kCFCopyStringSetCallBacks` configuration used when creating sets.
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
57#[repr(i32)]
58pub enum CFSetCallbacks {
59    /// Use Core Foundation retain/release/hash/equality callbacks for arbitrary `CFType`s.
60    #[default]
61    Type = 0,
62    /// Copy inserted `CFString` values using `kCFCopyStringSetCallBacks`.
63    CopyString = 1,
64}
65
66type CFSetApplyTask<'a> = Box<dyn FnMut(CFType) + 'a>;
67
68extern "C" fn cf_set_apply_trampoline(value: *mut c_void, context: *mut c_void) {
69    if context.is_null() {
70        return;
71    }
72    let callback = unsafe { &mut *context.cast::<CFSetApplyTask<'_>>() };
73    if let Some(value) = unsafe { CFType::from_raw(value) } {
74        panic_safe::catch_user_panic("CFSet::for_each", || callback(value));
75    }
76}
77
78fn copy_set_values(set: *mut c_void, expected_len: usize) -> Vec<CFType> {
79    let mut raw_values: Vec<*mut c_void> = vec![std::ptr::null_mut(); expected_len];
80    loop {
81        let capacity = raw_values.len();
82        let buffer = if capacity == 0 {
83            std::ptr::null_mut()
84        } else {
85            raw_values.as_mut_ptr()
86        };
87        let len = unsafe { ffi::acf_cf_set_copy_values(set, buffer, capacity) };
88        if len <= capacity {
89            raw_values.truncate(len);
90            return raw_values
91                .into_iter()
92                .filter_map(|ptr| unsafe { CFType::from_raw(ptr) })
93                .collect();
94        }
95        raw_values.resize(len, std::ptr::null_mut());
96    }
97}
98
99impl CFArray {
100    /// Create an array from borrowed Core Foundation values.
101    #[must_use]
102    pub fn from_values(values: &[&dyn AsCFType]) -> Self {
103        let raw_values: Vec<*mut std::ffi::c_void> =
104            values.iter().map(|value| value.as_ptr()).collect();
105        let ptr = unsafe { ffi::cf_array_create(raw_values.as_ptr(), raw_values.len()) };
106        unsafe { Self::from_raw(ptr) }.expect("CFArrayCreate returned NULL")
107    }
108
109    /// Number of elements in the array.
110    #[must_use]
111    pub fn len(&self) -> usize {
112        unsafe { ffi::cf_array_get_count(self.as_ptr()) }
113    }
114
115    /// Whether the array is empty.
116    #[must_use]
117    pub fn is_empty(&self) -> bool {
118        self.len() == 0
119    }
120
121    /// Copy the value at `index`.
122    #[must_use]
123    pub fn get(&self, index: usize) -> Option<CFType> {
124        let ptr = unsafe { ffi::cf_array_get_value_at_index(self.as_ptr(), index) };
125        unsafe { CFType::from_raw(ptr) }
126    }
127
128    /// Copy all elements into a Rust vector.
129    #[must_use]
130    pub fn values(&self) -> Vec<CFType> {
131        (0..self.len())
132            .filter_map(|index| self.get(index))
133            .collect()
134    }
135}
136
137impl CFDictionary {
138    /// Create a dictionary from borrowed key/value pairs.
139    #[must_use]
140    pub fn from_pairs(pairs: &[(&dyn AsCFType, &dyn AsCFType)]) -> Self {
141        let keys: Vec<*mut std::ffi::c_void> = pairs.iter().map(|(key, _)| key.as_ptr()).collect();
142        let values: Vec<*mut std::ffi::c_void> =
143            pairs.iter().map(|(_, value)| value.as_ptr()).collect();
144        let ptr = unsafe { ffi::cf_dictionary_create(keys.as_ptr(), values.as_ptr(), pairs.len()) };
145        unsafe { Self::from_raw(ptr) }.expect("CFDictionaryCreate returned NULL")
146    }
147
148    /// Number of key/value pairs.
149    #[must_use]
150    pub fn len(&self) -> usize {
151        unsafe { ffi::cf_dictionary_get_count(self.as_ptr()) }
152    }
153
154    /// Whether the dictionary is empty.
155    #[must_use]
156    pub fn is_empty(&self) -> bool {
157        self.len() == 0
158    }
159
160    /// Whether `key` exists in the dictionary.
161    #[must_use]
162    pub fn contains_key(&self, key: &dyn AsCFType) -> bool {
163        unsafe { ffi::cf_dictionary_contains_key(self.as_ptr(), key.as_ptr()) }
164    }
165
166    /// Copy the value associated with `key`.
167    #[must_use]
168    pub fn get(&self, key: &dyn AsCFType) -> Option<CFType> {
169        let ptr = unsafe { ffi::cf_dictionary_get_value(self.as_ptr(), key.as_ptr()) };
170        unsafe { CFType::from_raw(ptr) }
171    }
172
173    /// Copy all keys into a `CFArray`.
174    #[must_use]
175    pub fn keys(&self) -> CFArray {
176        let ptr = unsafe { ffi::cf_dictionary_copy_keys(self.as_ptr()) };
177        unsafe { CFArray::from_raw(ptr) }.expect("CFDictionary keys array should be non-null")
178    }
179
180    /// Copy all values into a `CFArray`.
181    #[must_use]
182    pub fn values(&self) -> CFArray {
183        let ptr = unsafe { ffi::cf_dictionary_copy_values(self.as_ptr()) };
184        unsafe { CFArray::from_raw(ptr) }.expect("CFDictionary values array should be non-null")
185    }
186}
187
188impl CFBag {
189    /// Create a bag from borrowed Core Foundation values.
190    #[must_use]
191    pub fn from_values(values: &[&dyn AsCFType]) -> Self {
192        let raw_values: Vec<*mut std::ffi::c_void> =
193            values.iter().map(|value| value.as_ptr()).collect();
194        let ptr = unsafe { ffi::cf_bag_create(raw_values.as_ptr(), raw_values.len()) };
195        unsafe { Self::from_raw(ptr) }.expect("CFBagCreate returned NULL")
196    }
197
198    /// Number of values in the bag.
199    #[must_use]
200    pub fn len(&self) -> usize {
201        unsafe { ffi::cf_bag_get_count(self.as_ptr()) }
202    }
203
204    /// Whether the bag is empty.
205    #[must_use]
206    pub fn is_empty(&self) -> bool {
207        self.len() == 0
208    }
209
210    /// Whether the bag contains `candidate`.
211    #[must_use]
212    pub fn contains(&self, candidate: &dyn AsCFType) -> bool {
213        unsafe { ffi::cf_bag_contains_value(self.as_ptr(), candidate.as_ptr()) }
214    }
215
216    /// Number of times `candidate` appears in the bag.
217    #[must_use]
218    pub fn count_of_value(&self, candidate: &dyn AsCFType) -> usize {
219        unsafe { ffi::cf_bag_get_count_of_value(self.as_ptr(), candidate.as_ptr()) }
220    }
221}
222
223impl CFSet {
224    /// Create an immutable set from borrowed Core Foundation values using type callbacks.
225    #[must_use]
226    pub fn from_values(values: &[&dyn AsCFType]) -> Self {
227        Self::from_values_with_callbacks(values, CFSetCallbacks::Type)
228    }
229
230    /// Create an immutable set from borrowed Core Foundation values with explicit callback semantics.
231    #[must_use]
232    pub fn from_values_with_callbacks(values: &[&dyn AsCFType], callbacks: CFSetCallbacks) -> Self {
233        let raw_values: Vec<*mut c_void> = values.iter().map(|value| value.as_ptr()).collect();
234        let ptr =
235            unsafe { ffi::cf_set_create(raw_values.as_ptr(), raw_values.len(), callbacks as i32) };
236        unsafe { Self::from_raw(ptr) }.expect("CFSetCreate returned NULL")
237    }
238
239    /// Create an immutable retained copy of this set.
240    #[must_use]
241    pub fn copy(&self) -> Self {
242        let ptr = unsafe { ffi::cf_set_create_copy(self.as_ptr()) };
243        unsafe { Self::from_raw(ptr) }.expect("CFSetCreateCopy returned NULL")
244    }
245
246    /// Create a mutable retained copy of this set.
247    #[must_use]
248    pub fn mutable_copy(&self, capacity: usize) -> CFMutableSet {
249        let ptr = unsafe { ffi::cf_set_create_mutable_copy(self.as_ptr(), capacity) };
250        unsafe { CFMutableSet::from_raw(ptr) }.expect("CFSetCreateMutableCopy returned NULL")
251    }
252
253    /// Number of values in the set.
254    #[must_use]
255    pub fn len(&self) -> usize {
256        unsafe { ffi::cf_set_get_count(self.as_ptr()) }
257    }
258
259    /// Whether the set is empty.
260    #[must_use]
261    pub fn is_empty(&self) -> bool {
262        self.len() == 0
263    }
264
265    /// Whether the set contains `candidate`.
266    #[must_use]
267    pub fn contains(&self, candidate: &dyn AsCFType) -> bool {
268        unsafe { ffi::cf_set_contains_value(self.as_ptr(), candidate.as_ptr()) }
269    }
270
271    /// Number of times `candidate` appears in the set (0 or 1).
272    #[must_use]
273    pub fn count_of_value(&self, candidate: &dyn AsCFType) -> usize {
274        unsafe { ffi::cf_set_get_count_of_value(self.as_ptr(), candidate.as_ptr()) }
275    }
276
277    /// Copy a matching value using `CFSetGetValue` semantics.
278    #[must_use]
279    pub fn get(&self, candidate: &dyn AsCFType) -> Option<CFType> {
280        let ptr = unsafe { ffi::cf_set_get_value(self.as_ptr(), candidate.as_ptr()) };
281        unsafe { CFType::from_raw(ptr) }
282    }
283
284    /// Copy a matching value using `CFSetGetValueIfPresent` semantics.
285    #[must_use]
286    pub fn get_if_present(&self, candidate: &dyn AsCFType) -> Option<CFType> {
287        let mut ptr = std::ptr::null_mut();
288        let present = unsafe {
289            ffi::cf_set_get_value_if_present(self.as_ptr(), candidate.as_ptr(), &raw mut ptr)
290        };
291        present.then(|| unsafe { CFType::from_raw(ptr) }).flatten()
292    }
293
294    /// Copy all values into a Rust vector.
295    #[must_use]
296    pub fn values(&self) -> Vec<CFType> {
297        copy_set_values(self.as_ptr(), self.len())
298    }
299
300    /// Call `callback` once for each value in the set.
301    pub fn for_each<F>(&self, callback: F)
302    where
303        F: FnMut(CFType),
304    {
305        let mut callback: CFSetApplyTask<'_> = Box::new(callback);
306        unsafe {
307            ffi::cf_set_apply_function(
308                self.as_ptr(),
309                std::ptr::addr_of_mut!(callback).cast::<c_void>(),
310                cf_set_apply_trampoline,
311            );
312        }
313    }
314}
315
316impl CFMutableSet {
317    /// Create an empty mutable set using Core Foundation type callbacks.
318    #[must_use]
319    pub fn new() -> Self {
320        Self::with_callbacks(0, CFSetCallbacks::Type)
321    }
322
323    /// Create an empty mutable set with explicit callback semantics.
324    #[must_use]
325    pub fn with_callbacks(capacity: usize, callbacks: CFSetCallbacks) -> Self {
326        let ptr = unsafe { ffi::cf_set_create_mutable(capacity, callbacks as i32) };
327        unsafe { Self::from_raw(ptr) }.expect("CFSetCreateMutable returned NULL")
328    }
329
330    /// Create a mutable set from borrowed Core Foundation values using type callbacks.
331    #[must_use]
332    pub fn from_values(values: &[&dyn AsCFType]) -> Self {
333        Self::from_values_with_callbacks(values, CFSetCallbacks::Type)
334    }
335
336    /// Create a mutable set from borrowed Core Foundation values with explicit callback semantics.
337    #[must_use]
338    pub fn from_values_with_callbacks(values: &[&dyn AsCFType], callbacks: CFSetCallbacks) -> Self {
339        let set = Self::with_callbacks(values.len(), callbacks);
340        for value in values {
341            set.add(*value);
342        }
343        set
344    }
345
346    /// Create an immutable retained copy of this set.
347    #[must_use]
348    pub fn copy(&self) -> CFSet {
349        let ptr = unsafe { ffi::cf_set_create_copy(self.as_ptr()) };
350        unsafe { CFSet::from_raw(ptr) }.expect("CFSetCreateCopy returned NULL")
351    }
352
353    /// Create a mutable retained copy of this set.
354    #[must_use]
355    pub fn mutable_copy(&self, capacity: usize) -> Self {
356        let ptr = unsafe { ffi::cf_set_create_mutable_copy(self.as_ptr(), capacity) };
357        unsafe { Self::from_raw(ptr) }.expect("CFSetCreateMutableCopy returned NULL")
358    }
359
360    /// Number of values in the set.
361    #[must_use]
362    pub fn len(&self) -> usize {
363        unsafe { ffi::cf_set_get_count(self.as_ptr()) }
364    }
365
366    /// Whether the set is empty.
367    #[must_use]
368    pub fn is_empty(&self) -> bool {
369        self.len() == 0
370    }
371
372    /// Whether the set contains `candidate`.
373    #[must_use]
374    pub fn contains(&self, candidate: &dyn AsCFType) -> bool {
375        unsafe { ffi::cf_set_contains_value(self.as_ptr(), candidate.as_ptr()) }
376    }
377
378    /// Number of times `candidate` appears in the set (0 or 1).
379    #[must_use]
380    pub fn count_of_value(&self, candidate: &dyn AsCFType) -> usize {
381        unsafe { ffi::cf_set_get_count_of_value(self.as_ptr(), candidate.as_ptr()) }
382    }
383
384    /// Copy all values into a Rust vector.
385    #[must_use]
386    pub fn values(&self) -> Vec<CFType> {
387        copy_set_values(self.as_ptr(), self.len())
388    }
389
390    /// Add `candidate` if it is not already present.
391    pub fn add(&self, candidate: &dyn AsCFType) {
392        unsafe { ffi::cf_set_add_value(self.as_ptr(), candidate.as_ptr()) };
393    }
394
395    /// Replace the matching value if it is already present.
396    pub fn replace(&self, candidate: &dyn AsCFType) {
397        unsafe { ffi::cf_set_replace_value(self.as_ptr(), candidate.as_ptr()) };
398    }
399
400    /// Insert or replace `candidate`.
401    pub fn set(&self, candidate: &dyn AsCFType) {
402        unsafe { ffi::cf_set_set_value(self.as_ptr(), candidate.as_ptr()) };
403    }
404
405    /// Remove `candidate` if it exists.
406    pub fn remove(&self, candidate: &dyn AsCFType) {
407        unsafe { ffi::cf_set_remove_value(self.as_ptr(), candidate.as_ptr()) };
408    }
409
410    /// Remove every value from the set.
411    pub fn clear(&self) {
412        unsafe { ffi::cf_set_remove_all_values(self.as_ptr()) };
413    }
414
415    /// Call `callback` once for each value in the set.
416    pub fn for_each<F>(&self, callback: F)
417    where
418        F: FnMut(CFType),
419    {
420        let mut callback: CFSetApplyTask<'_> = Box::new(callback);
421        unsafe {
422            ffi::cf_set_apply_function(
423                self.as_ptr(),
424                std::ptr::addr_of_mut!(callback).cast::<c_void>(),
425                cf_set_apply_trampoline,
426            );
427        }
428    }
429}
430
431impl Default for CFMutableSet {
432    fn default() -> Self {
433        Self::new()
434    }
435}
436
437impl CFAttributedString {
438    /// Create an attributed string with no attributes.
439    #[must_use]
440    pub fn new(string: &CFString) -> Self {
441        let ptr = unsafe { ffi::cf_attributed_string_create(string.as_ptr()) };
442        unsafe { Self::from_raw(ptr) }.expect("CFAttributedStringCreate returned NULL")
443    }
444
445    /// Underlying plain string.
446    #[must_use]
447    pub fn string(&self) -> CFString {
448        let ptr = unsafe { ffi::cf_attributed_string_get_string(self.as_ptr()) };
449        unsafe { CFString::from_raw(ptr) }.expect("CFAttributedStringGetString returned NULL")
450    }
451
452    /// Character length.
453    #[must_use]
454    pub fn len(&self) -> usize {
455        unsafe { ffi::cf_attributed_string_get_length(self.as_ptr()) }
456    }
457
458    /// Whether the string is empty.
459    #[must_use]
460    pub fn is_empty(&self) -> bool {
461        self.len() == 0
462    }
463}
464
465/// Safe wrapper around a Swift-backed `CFTree` helper.
466#[derive(Clone, PartialEq, Eq, Hash)]
467pub struct CFTree(SwiftObject);
468
469impl CFTree {
470    /// Create a tree node with an optional Core Foundation payload.
471    #[must_use]
472    pub fn new(value: Option<&dyn AsCFType>) -> Self {
473        let ptr =
474            unsafe { ffi::cf_tree_create(value.map_or(std::ptr::null_mut(), AsCFType::as_ptr)) };
475        Self(SwiftObject::from_raw_owned(ptr).expect("tree bridge returned NULL"))
476    }
477
478    /// Wraps a +1 retained tree helper pointer and returns `None` for null.
479    #[must_use]
480    pub(crate) fn from_raw_owned(ptr: *mut std::ffi::c_void) -> Option<Self> {
481        SwiftObject::from_raw_owned(ptr).map(Self)
482    }
483
484    /// Borrow the raw tree handle.
485    #[must_use]
486    pub(crate) const fn as_ptr(&self) -> *mut std::ffi::c_void {
487        self.0.as_ptr()
488    }
489
490    /// Append `child` to this node.
491    pub fn append_child(&self, child: &Self) {
492        unsafe { ffi::cf_tree_append_child(self.as_ptr(), child.as_ptr()) };
493    }
494
495    /// Number of direct children.
496    #[must_use]
497    pub fn child_count(&self) -> usize {
498        unsafe { ffi::cf_tree_get_child_count(self.as_ptr()) }
499    }
500
501    /// Copy the child at `index`.
502    #[must_use]
503    pub fn child_at(&self, index: usize) -> Option<Self> {
504        let ptr = unsafe { ffi::cf_tree_get_child_at_index(self.as_ptr(), index) };
505        Self::from_raw_owned(ptr)
506    }
507
508    /// Copy the payload value if present.
509    #[must_use]
510    pub fn value(&self) -> Option<CFType> {
511        let ptr = unsafe { ffi::cf_tree_copy_value(self.as_ptr()) };
512        unsafe { CFType::from_raw(ptr) }
513    }
514}
515
516impl fmt::Debug for CFTree {
517    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
518        f.debug_struct("CFTree")
519            .field("ptr", &self.as_ptr())
520            .field("child_count", &self.child_count())
521            .finish()
522    }
523}