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 =
213            unsafe { ffi::cf_set_create(raw_values.as_ptr(), raw_values.len(), callbacks as i32) };
214        Self::from_raw(ptr).expect("CFSetCreate returned NULL")
215    }
216
217    /// Create an immutable retained copy of this set.
218    #[must_use]
219    pub fn copy(&self) -> Self {
220        let ptr = unsafe { ffi::cf_set_create_copy(self.as_ptr()) };
221        Self::from_raw(ptr).expect("CFSetCreateCopy returned NULL")
222    }
223
224    /// Create a mutable retained copy of this set.
225    #[must_use]
226    pub fn mutable_copy(&self, capacity: usize) -> CFMutableSet {
227        let ptr = unsafe { ffi::cf_set_create_mutable_copy(self.as_ptr(), capacity) };
228        CFMutableSet::from_raw(ptr).expect("CFSetCreateMutableCopy returned NULL")
229    }
230
231    /// Number of values in the set.
232    #[must_use]
233    pub fn len(&self) -> usize {
234        unsafe { ffi::cf_set_get_count(self.as_ptr()) }
235    }
236
237    /// Whether the set is empty.
238    #[must_use]
239    pub fn is_empty(&self) -> bool {
240        self.len() == 0
241    }
242
243    /// Whether the set contains `candidate`.
244    #[must_use]
245    pub fn contains(&self, candidate: &dyn AsCFType) -> bool {
246        unsafe { ffi::cf_set_contains_value(self.as_ptr(), candidate.as_ptr()) }
247    }
248
249    /// Number of times `candidate` appears in the set (0 or 1).
250    #[must_use]
251    pub fn count_of_value(&self, candidate: &dyn AsCFType) -> usize {
252        unsafe { ffi::cf_set_get_count_of_value(self.as_ptr(), candidate.as_ptr()) }
253    }
254
255    /// Copy a matching value using `CFSetGetValue` semantics.
256    #[must_use]
257    pub fn get(&self, candidate: &dyn AsCFType) -> Option<CFType> {
258        let ptr = unsafe { ffi::cf_set_get_value(self.as_ptr(), candidate.as_ptr()) };
259        CFType::from_raw(ptr)
260    }
261
262    /// Copy a matching value using `CFSetGetValueIfPresent` semantics.
263    #[must_use]
264    pub fn get_if_present(&self, candidate: &dyn AsCFType) -> Option<CFType> {
265        let mut ptr = std::ptr::null_mut();
266        let present = unsafe {
267            ffi::cf_set_get_value_if_present(self.as_ptr(), candidate.as_ptr(), &mut ptr)
268        };
269        present.then(|| CFType::from_raw(ptr)).flatten()
270    }
271
272    /// Copy all values into a Rust vector.
273    #[must_use]
274    pub fn values(&self) -> Vec<CFType> {
275        let len = self.len();
276        let mut raw_values = vec![std::ptr::null_mut(); len];
277        if !raw_values.is_empty() {
278            unsafe { ffi::cf_set_get_values(self.as_ptr(), raw_values.as_mut_ptr()) };
279        }
280        raw_values
281            .into_iter()
282            .filter_map(CFType::from_raw)
283            .collect()
284    }
285
286    /// Call `callback` once for each value in the set.
287    pub fn for_each<F>(&self, callback: F)
288    where
289        F: FnMut(CFType),
290    {
291        let mut callback: CFSetApplyTask<'_> = Box::new(callback);
292        unsafe {
293            ffi::cf_set_apply_function(
294                self.as_ptr(),
295                std::ptr::addr_of_mut!(callback).cast::<c_void>(),
296                cf_set_apply_trampoline,
297            );
298        }
299    }
300}
301
302impl CFMutableSet {
303    /// Create an empty mutable set using Core Foundation type callbacks.
304    #[must_use]
305    pub fn new() -> Self {
306        Self::with_callbacks(0, CFSetCallbacks::Type)
307    }
308
309    /// Create an empty mutable set with explicit callback semantics.
310    #[must_use]
311    pub fn with_callbacks(capacity: usize, callbacks: CFSetCallbacks) -> Self {
312        let ptr = unsafe { ffi::cf_set_create_mutable(capacity, callbacks as i32) };
313        Self::from_raw(ptr).expect("CFSetCreateMutable returned NULL")
314    }
315
316    /// Create a mutable set from borrowed Core Foundation values using type callbacks.
317    #[must_use]
318    pub fn from_values(values: &[&dyn AsCFType]) -> Self {
319        Self::from_values_with_callbacks(values, CFSetCallbacks::Type)
320    }
321
322    /// Create a mutable set from borrowed Core Foundation values with explicit callback semantics.
323    #[must_use]
324    pub fn from_values_with_callbacks(values: &[&dyn AsCFType], callbacks: CFSetCallbacks) -> Self {
325        let set = Self::with_callbacks(values.len(), callbacks);
326        for value in values {
327            set.add(*value);
328        }
329        set
330    }
331
332    /// Create an immutable retained copy of this set.
333    #[must_use]
334    pub fn copy(&self) -> CFSet {
335        let ptr = unsafe { ffi::cf_set_create_copy(self.as_ptr()) };
336        CFSet::from_raw(ptr).expect("CFSetCreateCopy returned NULL")
337    }
338
339    /// Create a mutable retained copy of this set.
340    #[must_use]
341    pub fn mutable_copy(&self, capacity: usize) -> Self {
342        let ptr = unsafe { ffi::cf_set_create_mutable_copy(self.as_ptr(), capacity) };
343        Self::from_raw(ptr).expect("CFSetCreateMutableCopy returned NULL")
344    }
345
346    /// Number of values in the set.
347    #[must_use]
348    pub fn len(&self) -> usize {
349        unsafe { ffi::cf_set_get_count(self.as_ptr()) }
350    }
351
352    /// Whether the set is empty.
353    #[must_use]
354    pub fn is_empty(&self) -> bool {
355        self.len() == 0
356    }
357
358    /// Whether the set contains `candidate`.
359    #[must_use]
360    pub fn contains(&self, candidate: &dyn AsCFType) -> bool {
361        unsafe { ffi::cf_set_contains_value(self.as_ptr(), candidate.as_ptr()) }
362    }
363
364    /// Number of times `candidate` appears in the set (0 or 1).
365    #[must_use]
366    pub fn count_of_value(&self, candidate: &dyn AsCFType) -> usize {
367        unsafe { ffi::cf_set_get_count_of_value(self.as_ptr(), candidate.as_ptr()) }
368    }
369
370    /// Copy all values into a Rust vector.
371    #[must_use]
372    pub fn values(&self) -> Vec<CFType> {
373        let len = self.len();
374        let mut raw_values = vec![std::ptr::null_mut(); len];
375        if !raw_values.is_empty() {
376            unsafe { ffi::cf_set_get_values(self.as_ptr(), raw_values.as_mut_ptr()) };
377        }
378        raw_values
379            .into_iter()
380            .filter_map(CFType::from_raw)
381            .collect()
382    }
383
384    /// Add `candidate` if it is not already present.
385    pub fn add(&self, candidate: &dyn AsCFType) {
386        unsafe { ffi::cf_set_add_value(self.as_ptr(), candidate.as_ptr()) };
387    }
388
389    /// Replace the matching value if it is already present.
390    pub fn replace(&self, candidate: &dyn AsCFType) {
391        unsafe { ffi::cf_set_replace_value(self.as_ptr(), candidate.as_ptr()) };
392    }
393
394    /// Insert or replace `candidate`.
395    pub fn set(&self, candidate: &dyn AsCFType) {
396        unsafe { ffi::cf_set_set_value(self.as_ptr(), candidate.as_ptr()) };
397    }
398
399    /// Remove `candidate` if it exists.
400    pub fn remove(&self, candidate: &dyn AsCFType) {
401        unsafe { ffi::cf_set_remove_value(self.as_ptr(), candidate.as_ptr()) };
402    }
403
404    /// Remove every value from the set.
405    pub fn clear(&self) {
406        unsafe { ffi::cf_set_remove_all_values(self.as_ptr()) };
407    }
408
409    /// Call `callback` once for each value in the set.
410    pub fn for_each<F>(&self, callback: F)
411    where
412        F: FnMut(CFType),
413    {
414        let mut callback: CFSetApplyTask<'_> = Box::new(callback);
415        unsafe {
416            ffi::cf_set_apply_function(
417                self.as_ptr(),
418                std::ptr::addr_of_mut!(callback).cast::<c_void>(),
419                cf_set_apply_trampoline,
420            );
421        }
422    }
423}
424
425impl Default for CFMutableSet {
426    fn default() -> Self {
427        Self::new()
428    }
429}
430
431impl CFAttributedString {
432    /// Create an attributed string with no attributes.
433    #[must_use]
434    pub fn new(string: &CFString) -> Self {
435        let ptr = unsafe { ffi::cf_attributed_string_create(string.as_ptr()) };
436        Self::from_raw(ptr).expect("CFAttributedStringCreate returned NULL")
437    }
438
439    /// Underlying plain string.
440    #[must_use]
441    pub fn string(&self) -> CFString {
442        let ptr = unsafe { ffi::cf_attributed_string_get_string(self.as_ptr()) };
443        CFString::from_raw(ptr).expect("CFAttributedStringGetString returned NULL")
444    }
445
446    /// Character length.
447    #[must_use]
448    pub fn len(&self) -> usize {
449        unsafe { ffi::cf_attributed_string_get_length(self.as_ptr()) }
450    }
451
452    /// Whether the string is empty.
453    #[must_use]
454    pub fn is_empty(&self) -> bool {
455        self.len() == 0
456    }
457}
458
459/// Safe wrapper around a Swift-backed `CFTree` helper.
460#[derive(Clone, PartialEq, Eq, Hash)]
461pub struct CFTree(SwiftObject);
462
463impl CFTree {
464    /// Create a tree node with an optional Core Foundation payload.
465    #[must_use]
466    pub fn new(value: Option<&dyn AsCFType>) -> Self {
467        let ptr =
468            unsafe { ffi::cf_tree_create(value.map_or(std::ptr::null_mut(), AsCFType::as_ptr)) };
469        Self(SwiftObject::from_raw(ptr).expect("tree bridge returned NULL"))
470    }
471
472    #[must_use]
473    pub(crate) fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
474        SwiftObject::from_raw(ptr).map(Self)
475    }
476
477    /// Borrow the raw tree handle.
478    #[must_use]
479    pub(crate) const fn as_ptr(&self) -> *mut std::ffi::c_void {
480        self.0.as_ptr()
481    }
482
483    /// Append `child` to this node.
484    pub fn append_child(&self, child: &Self) {
485        unsafe { ffi::cf_tree_append_child(self.as_ptr(), child.as_ptr()) };
486    }
487
488    /// Number of direct children.
489    #[must_use]
490    pub fn child_count(&self) -> usize {
491        unsafe { ffi::cf_tree_get_child_count(self.as_ptr()) }
492    }
493
494    /// Copy the child at `index`.
495    #[must_use]
496    pub fn child_at(&self, index: usize) -> Option<Self> {
497        let ptr = unsafe { ffi::cf_tree_get_child_at_index(self.as_ptr(), index) };
498        Self::from_raw(ptr)
499    }
500
501    /// Copy the payload value if present.
502    #[must_use]
503    pub fn value(&self) -> Option<CFType> {
504        let ptr = unsafe { ffi::cf_tree_copy_value(self.as_ptr()) };
505        CFType::from_raw(ptr)
506    }
507}
508
509impl fmt::Debug for CFTree {
510    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
511        f.debug_struct("CFTree")
512            .field("ptr", &self.as_ptr())
513            .field("child_count", &self.child_count())
514            .finish()
515    }
516}