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