objc2_foundation/dictionary.rs
1//! Utilities for the `NSDictionary` and `NSMutableDictionary` classes.
2#[cfg(feature = "alloc")]
3use alloc::vec::Vec;
4use core::fmt;
5use core::mem;
6use core::ptr::NonNull;
7use objc2::msg_send;
8
9use objc2::rc::Retained;
10#[cfg(feature = "NSObject")]
11use objc2::runtime::ProtocolObject;
12#[cfg(feature = "NSObject")]
13use objc2::AnyThread;
14use objc2::Message;
15
16#[cfg(feature = "NSEnumerator")]
17use crate::iter;
18#[cfg(feature = "NSObject")]
19use crate::{util, CopyingHelper, NSCopying};
20use crate::{NSDictionary, NSMutableDictionary};
21
22#[cfg(feature = "NSObject")]
23fn keys_to_ptr<CopiedKey>(keys: &[&CopiedKey]) -> *mut NonNull<ProtocolObject<dyn NSCopying>>
24where
25 CopiedKey: Message + NSCopying,
26{
27 let keys: *mut NonNull<CopiedKey> = util::ref_ptr_cast_const(keys.as_ptr());
28 // SAFETY: `CopiedKey` is `Message + NSCopying`, and is therefore safe to cast to
29 // `ProtocolObject<dyn NSCopying>`.
30 let keys: *mut NonNull<ProtocolObject<dyn NSCopying>> = keys.cast();
31 keys
32}
33
34/// Convenience creation methods.
35impl<KeyType: Message, ObjectType: Message> NSDictionary<KeyType, ObjectType> {
36 /// Create a new dictionary from a slice of keys, and a slice of objects.
37 ///
38 /// This is a safe interface to `initWithObjects:forKeys:count:`.
39 ///
40 /// # Panics
41 ///
42 /// Panics if the slices have different lengths, as this is likely a
43 /// programmer error.
44 ///
45 /// # Example
46 ///
47 /// ```
48 /// use objc2_foundation::{NSDictionary, ns_string};
49 ///
50 /// let dict = NSDictionary::from_slices(
51 /// &[ns_string!("key1"), ns_string!("key2"), ns_string!("key3")],
52 /// &[ns_string!("value1"), ns_string!("value2"), ns_string!("value3")],
53 /// );
54 ///
55 /// assert_eq!(&*dict.objectForKey(ns_string!("key2")).unwrap(), ns_string!("value2"));
56 /// ```
57 #[cfg(feature = "NSObject")]
58 pub fn from_slices<CopiedKey>(keys: &[&CopiedKey], objects: &[&ObjectType]) -> Retained<Self>
59 where
60 // The dictionary copies its keys, which is why we require `NSCopying`
61 // and use `CopyingHelper` on all input data - we want to ensure that
62 // the type-system knows that it's not actually e.g. `NSMutableString`
63 // that is being stored, but instead `NSString`.
64 CopiedKey: Message + NSCopying + CopyingHelper<Result = KeyType>,
65 {
66 // Ensure that we don't read too far into one of the buffers.
67 assert_eq!(
68 keys.len(),
69 objects.len(),
70 "key slice and object slice should have the same length",
71 );
72 let count = keys.len();
73
74 let keys = keys_to_ptr(keys);
75 let objects = util::ref_ptr_cast_const(objects.as_ptr());
76
77 // SAFETY:
78 // - All types that are `Message` use interior mutability, and the
79 // dictionary extends the lifetime of them internally by retaining
80 // them.
81 //
82 // - The pointers are valid until the method has finished executing,
83 // at which point the dictionary will have created its own internal
84 // storage for holding the keys and objects.
85 //
86 // - The length is lower than or equal to the length of the two
87 // pointers.
88 //
89 // - While recommended against in the below link, the key _can_ be
90 // mutated, it'll "just" corrupt the collection's invariants (but
91 // won't cause undefined behaviour).
92 //
93 // This is tested via fuzzing in `collection_interior_mut.rs`.
94 //
95 // <https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/ObjectMutability/ObjectMutability.html#//apple_ref/doc/uid/TP40010810-CH5-SW69>
96 unsafe { Self::initWithObjects_forKeys_count(Self::alloc(), objects, keys, count) }
97 }
98
99 #[cfg(feature = "NSObject")]
100 pub fn from_retained_objects<CopiedKey>(
101 keys: &[&CopiedKey],
102 objects: &[Retained<ObjectType>],
103 ) -> Retained<Self>
104 where
105 CopiedKey: Message + NSCopying + CopyingHelper<Result = KeyType>,
106 {
107 // Ensure that we don't read too far into one of the buffers.
108 assert_eq!(
109 keys.len(),
110 objects.len(),
111 "key slice and object slice should have the same length",
112 );
113 let count = keys.len();
114
115 let keys = keys_to_ptr(keys);
116 let objects = util::retained_ptr_cast_const(objects.as_ptr());
117
118 // SAFETY: Same as `from_slices`.
119 unsafe { Self::initWithObjects_forKeys_count(Self::alloc(), objects, keys, count) }
120 }
121}
122
123/// Convenience creation methods.
124impl<KeyType: Message, ObjectType: Message> NSMutableDictionary<KeyType, ObjectType> {
125 #[cfg(feature = "NSObject")]
126 pub fn from_slices<CopiedKey>(keys: &[&CopiedKey], objects: &[&ObjectType]) -> Retained<Self>
127 where
128 CopiedKey: Message + NSCopying + CopyingHelper<Result = KeyType>,
129 {
130 // Ensure that we don't read too far into one of the buffers.
131 assert_eq!(
132 keys.len(),
133 objects.len(),
134 "key slice and object slice should have the same length",
135 );
136 let count = keys.len();
137
138 let keys = keys_to_ptr(keys);
139 let objects = util::ref_ptr_cast_const(objects.as_ptr());
140
141 // SAFETY: Same as `NSDictionary::from_slices`.
142 unsafe { Self::initWithObjects_forKeys_count(Self::alloc(), objects, keys, count) }
143 }
144
145 #[cfg(feature = "NSObject")]
146 pub fn from_retained_objects<CopiedKey>(
147 keys: &[&CopiedKey],
148 objects: &[Retained<ObjectType>],
149 ) -> Retained<Self>
150 where
151 CopiedKey: Message + NSCopying + CopyingHelper<Result = KeyType>,
152 {
153 // Ensure that we don't read too far into one of the buffers.
154 assert_eq!(
155 keys.len(),
156 objects.len(),
157 "key slice and object slice should have the same length",
158 );
159 let count = keys.len();
160
161 let keys = keys_to_ptr(keys);
162 let objects = util::retained_ptr_cast_const(objects.as_ptr());
163
164 // SAFETY: Same as `NSDictionary::from_retained_objects`.
165 unsafe { Self::initWithObjects_forKeys_count(Self::alloc(), objects, keys, count) }
166 }
167}
168
169// Note: We'd like to make getter methods take `KeyType: Borrow<KeyType>`
170// like `std::collections::HashMap`, so that e.g. `NSDictionary<NSString, V>`
171// could take a `&NSObject` as input, and still make that work since
172// `NSString` borrows to `NSObject`.
173//
174// But we can't really, at least not with extra `unsafe` / an extra trait,
175// since we don't control how the comparisons happen.
176//
177// The most useful alternative would probably be to take `impl AsRef<KeyType>`, but
178// objc2 classes deref to their superclass anyhow, so let's just use a simple,
179// normal reference.
180
181/// Direct, unsafe object accessors.
182///
183/// Foundation's collection types store their items in such a way that they
184/// can give out references to their data without having to autorelease it
185/// first, see [the docs][collections-own].
186///
187/// This means that we can more efficiently access the dictionary's keys and
188/// objects, but _only_ if the dictionary isn't mutated via e.g.
189/// `NSMutableDictionary` methods while doing so - otherwise, we might end up
190/// accessing a deallocated object.
191///
192/// [collections-own]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW12
193impl<KeyType: Message, ObjectType: Message> NSDictionary<KeyType, ObjectType> {
194 /// Get a direct reference to the object corresponding to the key.
195 ///
196 /// Consider using the [`objectForKey`](Self::objectForKey) method
197 /// instead, unless you're seeing performance issues from the retaining.
198 ///
199 /// # Safety
200 ///
201 /// The dictionary must not be mutated while the reference is live.
202 #[doc(alias = "objectForKey:")]
203 #[inline]
204 pub unsafe fn objectForKey_unchecked(&self, key: &KeyType) -> Option<&ObjectType> {
205 unsafe { msg_send![self, objectForKey: key] }
206 }
207
208 /// Two vectors containing direct references to respectively the
209 /// dictionary's keys and objects.
210 ///
211 /// Consider using the [`to_vecs`](Self::to_vecs) method instead, unless
212 /// you're seeing performance issues from the retaining.
213 ///
214 /// # Safety
215 ///
216 /// The dictionary must not be mutated while the returned references are
217 /// alive.
218 #[doc(alias = "getObjects:andKeys:")]
219 #[cfg(feature = "alloc")]
220 pub unsafe fn to_vecs_unchecked(&self) -> (Vec<&KeyType>, Vec<&ObjectType>) {
221 let len = self.len();
222 let mut keys = Vec::with_capacity(len);
223 let mut objs = Vec::with_capacity(len);
224
225 // SAFETY: The pointers are valid.
226 unsafe {
227 // No reason to use `getObjects:andKeys:count:`, the dictionary is
228 // not thread safe, so we know it won't change within this scope.
229 #[allow(deprecated)]
230 self.getObjects_andKeys(objs.as_mut_ptr(), keys.as_mut_ptr());
231 }
232
233 // SAFETY: The vecs were just initialized by `getObjects:andKeys:`.
234 unsafe {
235 keys.set_len(len);
236 objs.set_len(len);
237 }
238
239 // SAFETY: `NonNull<T>` and `&T` have the same memory layout, and the
240 // lifetime is upheld by the caller.
241 unsafe {
242 (
243 mem::transmute::<Vec<NonNull<KeyType>>, Vec<&KeyType>>(keys),
244 mem::transmute::<Vec<NonNull<ObjectType>>, Vec<&ObjectType>>(objs),
245 )
246 }
247 }
248
249 /// Iterate over the dictionary's keys without retaining them.
250 ///
251 /// Consider using the [`keys`](Self::keys) method instead, unless you're
252 /// seeing performance issues from the retaining.
253 ///
254 /// # Safety
255 ///
256 /// The dictionary must not be mutated for the lifetime of the iterator,
257 /// or the elements it returns.
258 #[cfg(feature = "NSEnumerator")]
259 #[doc(alias = "keyEnumerator")]
260 #[inline]
261 pub unsafe fn keys_unchecked(&self) -> KeysUnchecked<'_, KeyType, ObjectType> {
262 KeysUnchecked(iter::IterUnchecked::new(self))
263 }
264
265 /// Iterate over the dictionary's objects / values without retaining them.
266 ///
267 /// Consider using the [`objects`](Self::objects) method instead, unless
268 /// you're seeing performance issues from the retaining.
269 ///
270 /// # Safety
271 ///
272 /// The dictionary must not be mutated for the lifetime of the iterator,
273 /// or the elements it returns.
274 #[cfg(feature = "NSEnumerator")]
275 #[doc(alias = "objectEnumerator")]
276 #[inline]
277 pub unsafe fn objects_unchecked(&self) -> ObjectsUnchecked<'_, KeyType, ObjectType> {
278 // SAFETY: Avoiding mutation is upheld by caller.
279 let enumerator = unsafe { self.objectEnumerator() };
280 // SAFETY: The enumerator came from the dictionary.
281 ObjectsUnchecked(unsafe { iter::IterUncheckedWithBackingEnum::new(self, enumerator) })
282 }
283}
284
285/// Various accessor methods.
286impl<KeyType: Message, ObjectType: Message> NSDictionary<KeyType, ObjectType> {
287 /// The amount of elements in the dictionary.
288 #[doc(alias = "count")]
289 #[inline]
290 pub fn len(&self) -> usize {
291 self.count()
292 }
293
294 /// Whether the dictionary is empty or not.
295 #[inline]
296 pub fn is_empty(&self) -> bool {
297 self.len() == 0
298 }
299
300 /// Two vectors containing respectively the dictionary's keys and objects.
301 ///
302 /// # Example
303 ///
304 /// Iterate over the keys and values of the dictionary.
305 ///
306 /// ```
307 /// use objc2_foundation::{NSDictionary, ns_string};
308 ///
309 /// let dict = NSDictionary::from_slices(
310 /// &[ns_string!("a"), ns_string!("b")],
311 /// &[ns_string!("a"), ns_string!("b")],
312 /// );
313 /// let (keys, objects) = dict.to_vecs();
314 /// for (key, obj) in keys.into_iter().zip(objects) {
315 /// assert_eq!(key, obj);
316 /// }
317 /// ```
318 #[doc(alias = "getObjects:")]
319 #[cfg(feature = "alloc")]
320 pub fn to_vecs(&self) -> (Vec<Retained<KeyType>>, Vec<Retained<ObjectType>>) {
321 // SAFETY: We retain the elements below, so that we know that the
322 // dictionary isn't mutated while they are alive.
323 let (keys, objects) = unsafe { self.to_vecs_unchecked() };
324 (
325 keys.into_iter().map(KeyType::retain).collect(),
326 objects.into_iter().map(ObjectType::retain).collect(),
327 )
328 }
329
330 /// Iterate over the dictionary's keys.
331 #[cfg(feature = "NSEnumerator")]
332 #[doc(alias = "keyEnumerator")]
333 #[inline]
334 pub fn keys(&self) -> Keys<'_, KeyType, ObjectType> {
335 Keys(iter::Iter::new(self))
336 }
337
338 /// Iterate over the dictionary's objects / values.
339 ///
340 /// # Examples
341 ///
342 #[cfg_attr(feature = "NSString", doc = "```")]
343 #[cfg_attr(not(feature = "NSString"), doc = "```ignore")]
344 /// use objc2_foundation::{ns_string, NSMutableDictionary, NSString};
345 ///
346 /// let dict = NSMutableDictionary::new();
347 /// dict.insert(ns_string!("key1"), ns_string!("value1"));
348 /// dict.insert(ns_string!("key2"), ns_string!("value2"));
349 /// for obj in dict.objects() {
350 /// assert!(obj.hasPrefix(ns_string!("value")));
351 /// }
352 /// ```
353 #[cfg(feature = "NSEnumerator")]
354 #[doc(alias = "objectEnumerator")]
355 #[inline]
356 pub fn objects(&self) -> Objects<'_, KeyType, ObjectType> {
357 // SAFETY: The iterator checks for mutation while enumerating.
358 let enumerator = unsafe { self.objectEnumerator() };
359 // SAFETY: The enumerator came from the dictionary.
360 Objects(unsafe { iter::IterWithBackingEnum::new(self, enumerator) })
361 }
362}
363
364/// Convenience mutation methods.
365impl<KeyType: Message, ObjectType: Message> NSMutableDictionary<KeyType, ObjectType> {
366 /// Inserts a key-value pair into the dictionary.
367 ///
368 /// If the dictionary did not have this key present, the value is
369 /// inserted. If the dictionary already had this key present, the value
370 /// and the key is updated.
371 ///
372 /// # Examples
373 ///
374 /// ```
375 /// use objc2_foundation::{ns_string, NSMutableDictionary, NSObject};
376 ///
377 /// let dict = NSMutableDictionary::new();
378 /// dict.insert(ns_string!("key"), &*NSObject::new());
379 /// ```
380 #[cfg(feature = "NSObject")]
381 #[doc(alias = "setObject:forKey:")]
382 #[inline]
383 pub fn insert<CopiedKey>(&self, key: &CopiedKey, object: &ObjectType)
384 where
385 CopiedKey: Message + NSCopying + CopyingHelper<Result = KeyType>,
386 {
387 let key = ProtocolObject::from_ref(key);
388 // SAFETY: The key is copied, and then has the correct type `KeyType`.
389 unsafe { self.setObject_forKey(object, key) };
390 }
391}
392
393#[cfg(feature = "NSEnumerator")]
394unsafe impl<KeyType: Message, ObjectType: Message> iter::FastEnumerationHelper
395 for NSDictionary<KeyType, ObjectType>
396{
397 // Fast enumeration for dictionaries returns the keys.
398 type Item = KeyType;
399
400 #[inline]
401 fn maybe_len(&self) -> Option<usize> {
402 Some(self.len())
403 }
404}
405
406#[cfg(feature = "NSEnumerator")]
407unsafe impl<KeyType: Message, ObjectType: Message> iter::FastEnumerationHelper
408 for NSMutableDictionary<KeyType, ObjectType>
409{
410 // The same goes for mutable dictionaries.
411 type Item = KeyType;
412
413 #[inline]
414 fn maybe_len(&self) -> Option<usize> {
415 Some(self.len())
416 }
417}
418
419/// An iterator over the keys of a dictionary.
420#[derive(Debug)]
421#[cfg(feature = "NSEnumerator")]
422pub struct Keys<'a, KeyType: Message, ObjectType: Message>(
423 iter::Iter<'a, NSDictionary<KeyType, ObjectType>>,
424);
425
426#[cfg(feature = "NSEnumerator")]
427__impl_iter! {
428 impl<'a, KeyType: Message, ObjectType: Message> Iterator<Item = Retained<KeyType>> for Keys<'a, KeyType, ObjectType> { ... }
429}
430
431/// An iterator over unretained keys of a dictionary.
432///
433/// # Safety
434///
435/// The dictionary must not be mutated while this is alive.
436#[derive(Debug)]
437#[cfg(feature = "NSEnumerator")]
438pub struct KeysUnchecked<'a, KeyType: Message, ObjectType: Message>(
439 iter::IterUnchecked<'a, NSDictionary<KeyType, ObjectType>>,
440);
441
442#[cfg(feature = "NSEnumerator")]
443__impl_iter! {
444 impl<'a, KeyType: Message, ObjectType: Message> Iterator<Item = &'a KeyType> for KeysUnchecked<'a, KeyType, ObjectType> { ... }
445}
446
447/// An iterator over the objects / values in a dictionary.
448#[derive(Debug)]
449#[cfg(feature = "NSEnumerator")]
450pub struct Objects<'a, KeyType: Message, ObjectType: Message>(
451 iter::IterWithBackingEnum<
452 'a,
453 NSDictionary<KeyType, ObjectType>,
454 crate::NSEnumerator<ObjectType>,
455 >,
456);
457
458#[cfg(feature = "NSEnumerator")]
459__impl_iter! {
460 impl<'a, KeyType: Message, ObjectType: Message> Iterator<Item = Retained<ObjectType>> for Objects<'a, KeyType, ObjectType> { ... }
461}
462
463/// An iterator over unretained objects / values of a dictionary.
464///
465/// # Safety
466///
467/// The dictionary must not be mutated while this is alive.
468#[derive(Debug)]
469#[cfg(feature = "NSEnumerator")]
470pub struct ObjectsUnchecked<'a, KeyType: Message, ObjectType: Message + 'a>(
471 iter::IterUncheckedWithBackingEnum<
472 'a,
473 NSDictionary<KeyType, ObjectType>,
474 crate::NSEnumerator<ObjectType>,
475 >,
476);
477
478#[cfg(feature = "NSEnumerator")]
479__impl_iter! {
480 impl<'a, KeyType: Message, ObjectType: Message> Iterator<Item = &'a ObjectType> for ObjectsUnchecked<'a, KeyType, ObjectType> { ... }
481}
482
483impl<KeyType: fmt::Debug + Message, ObjectType: fmt::Debug + Message> fmt::Debug
484 for NSDictionary<KeyType, ObjectType>
485{
486 #[inline]
487 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
488 // SAFETY: Unsound, use `to_vecs` instead when that doesn't have extra bounds
489 let (keys, objects) = unsafe { self.to_vecs_unchecked() };
490 let iter = keys.into_iter().zip(objects);
491 f.debug_map().entries(iter).finish()
492 }
493}
494
495impl<KeyType: fmt::Debug + Message, ObjectType: fmt::Debug + Message> fmt::Debug
496 for NSMutableDictionary<KeyType, ObjectType>
497{
498 #[inline]
499 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
500 fmt::Debug::fmt(&**self, f)
501 }
502}