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