Skip to content

Commit 31e2ead

Browse files
authored
[objective_c] Collections support for NS[Mutable]Dictionary and NS[Mutable]Set (#2234)
1 parent 11966f1 commit 31e2ead

File tree

7 files changed

+760
-16
lines changed

7 files changed

+760
-16
lines changed

pkgs/objective_c/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
- Use ffigen 18.2.0
44
- `NSArray` is now a Dart `Iterable` and `NSMutableArray` is now a Dart `List`.
5+
- `NSDictionary` and `NSMutableDictionary` are now Dart `Map`s.
6+
- `NSSet` and `NSMutableSet` are now Dart `Set`s.
57
- Add `.toNSNumber()` extension method to `int`, `double`, and `num`.
68

79
## 7.1.0

pkgs/objective_c/lib/src/objective_c_bindings_generated.dart

Lines changed: 166 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,7 +2022,39 @@ class NSDate extends NSObject implements NSCopying, NSSecureCoding {
20222022

20232023
/// NSDictionary
20242024
class NSDictionary extends NSObject
2025+
with MapBase<NSCopying, objc.ObjCObjectBase>
20252026
implements NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration {
2027+
/// Creates a [NSDictionary] from [other].
2028+
static NSDictionary of(Map<NSCopying, objc.ObjCObjectBase> other) =>
2029+
NSMutableDictionary.of(other);
2030+
2031+
@override
2032+
int get length => count;
2033+
2034+
@override
2035+
objc.ObjCObjectBase? operator [](Object? key) =>
2036+
key is NSCopying ? objectForKey_(key) : null;
2037+
2038+
@override
2039+
Iterable<NSCopying> get keys => _NSDictionaryKeyIterable(this);
2040+
2041+
@override
2042+
Iterable<objc.ObjCObjectBase> get values => _NSDictionaryValueIterable(this);
2043+
2044+
@override
2045+
bool containsKey(Object? key) => this[key] != null;
2046+
2047+
@override
2048+
void operator []=(NSCopying key, objc.ObjCObjectBase value) =>
2049+
throw UnsupportedError("Cannot modify NSDictionary");
2050+
2051+
@override
2052+
void clear() => throw UnsupportedError("Cannot modify NSDictionary");
2053+
2054+
@override
2055+
objc.ObjCObjectBase? remove(Object? key) =>
2056+
throw UnsupportedError("Cannot modify NSDictionary");
2057+
20262058
NSDictionary._(ffi.Pointer<objc.ObjCObject> pointer,
20272059
{bool retain = false, bool release = false})
20282060
: super.castFromPointer(pointer, retain: retain, release: release);
@@ -2228,7 +2260,20 @@ enum NSEnumerationOptions {
22282260
}
22292261

22302262
/// NSEnumerator
2231-
class NSEnumerator extends NSObject implements NSFastEnumeration {
2263+
class NSEnumerator extends NSObject
2264+
implements NSFastEnumeration, Iterator<objc.ObjCObjectBase> {
2265+
objc.ObjCObjectBase? _current;
2266+
2267+
@override
2268+
objc.ObjCObjectBase get current => _current!;
2269+
2270+
@override
2271+
@pragma('vm:prefer-inline')
2272+
bool moveNext() {
2273+
_current = nextObject();
2274+
return _current != null;
2275+
}
2276+
22322277
NSEnumerator._(ffi.Pointer<objc.ObjCObject> pointer,
22332278
{bool retain = false, bool release = false})
22342279
: super.castFromPointer(pointer, retain: retain, release: release);
@@ -4694,7 +4739,7 @@ class NSMethodSignature extends NSObject {
46944739
}
46954740

46964741
/// NSMutableArray
4697-
class NSMutableArray extends NSArray with ListMixin<objc.ObjCObjectBase> {
4742+
class NSMutableArray extends NSArray with ListBase<objc.ObjCObjectBase> {
46984743
/// Creates a [NSMutableArray] of the given length with [fill] at each
46994744
/// position.
47004745
///
@@ -4716,18 +4761,19 @@ class NSMutableArray extends NSArray with ListMixin<objc.ObjCObjectBase> {
47164761
for (; len > newLength; --len) removeLastObject();
47174762
}
47184763

4764+
@override
4765+
Iterator<objc.ObjCObjectBase> get iterator => _NSArrayIterator(this);
4766+
4767+
@override
4768+
objc.ObjCObjectBase operator [](int index) => objectAtIndex_(index);
4769+
47194770
@override
47204771
void operator []=(int index, objc.ObjCObjectBase value) =>
47214772
replaceObjectAtIndex_withObject_(index, value);
47224773

47234774
@override
47244775
void add(objc.ObjCObjectBase value) => addObject_(value);
47254776

4726-
@override
4727-
void addAll(Iterable<objc.ObjCObjectBase> iterable) {
4728-
for (final value in iterable) add(value);
4729-
}
4730-
47314777
NSMutableArray._(ffi.Pointer<objc.ObjCObject> pointer,
47324778
{bool retain = false, bool release = false})
47334779
: super.castFromPointer(pointer, retain: retain, release: release);
@@ -5339,6 +5385,25 @@ class NSMutableData extends NSData {
53395385

53405386
/// NSMutableDictionary
53415387
class NSMutableDictionary extends NSDictionary {
5388+
/// Creates a [NSMutableDictionary] from [other].
5389+
static NSDictionary of(Map<NSCopying, objc.ObjCObjectBase> other) =>
5390+
NSMutableDictionary.dictionaryWithCapacity_(other.length)..addAll(other);
5391+
5392+
@override
5393+
void clear() => removeAllObjects();
5394+
5395+
@override
5396+
objc.ObjCObjectBase? remove(Object? key) {
5397+
if (key is! NSCopying) return null;
5398+
final old = this[key];
5399+
removeObjectForKey_(key);
5400+
return old;
5401+
}
5402+
5403+
@override
5404+
void operator []=(NSCopying key, objc.ObjCObjectBase value) =>
5405+
setObject_forKey_(value, NSCopying.castFrom(key));
5406+
53425407
NSMutableDictionary._(ffi.Pointer<objc.ObjCObject> pointer,
53435408
{bool retain = false, bool release = false})
53445409
: super.castFromPointer(pointer, retain: retain, release: release);
@@ -6083,6 +6148,28 @@ class NSMutableOrderedSet extends NSOrderedSet {
60836148

60846149
/// NSMutableSet
60856150
class NSMutableSet extends NSSet {
6151+
/// Creates a [NSMutableSet] from [elements].
6152+
static NSMutableSet of(Iterable<objc.ObjCObjectBase> elements) =>
6153+
setWithCapacity_(elements.length)..addAll(elements);
6154+
6155+
@override
6156+
bool add(objc.ObjCObjectBase value) {
6157+
final alreadyContains = contains(value);
6158+
addObject_(value);
6159+
return !alreadyContains;
6160+
}
6161+
6162+
@override
6163+
bool remove(Object? value) {
6164+
if (value is! objc.ObjCObjectBase) return false;
6165+
final alreadyContains = contains(value);
6166+
removeObject_(value);
6167+
return alreadyContains;
6168+
}
6169+
6170+
@override
6171+
void clear() => removeAllObjects();
6172+
60866173
NSMutableSet._(ffi.Pointer<objc.ObjCObject> pointer,
60876174
{bool retain = false, bool release = false})
60886175
: super.castFromPointer(pointer, retain: retain, release: release);
@@ -9379,7 +9466,39 @@ interface class NSSecureCoding extends objc.ObjCProtocolBase
93799466

93809467
/// NSSet
93819468
class NSSet extends NSObject
9469+
with SetBase<objc.ObjCObjectBase>
93829470
implements NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration {
9471+
/// Creates a [NSSet] from [elements].
9472+
static NSSet of(Iterable<objc.ObjCObjectBase> elements) =>
9473+
NSMutableSet.of(elements);
9474+
9475+
@override
9476+
int get length => count;
9477+
9478+
@override
9479+
bool contains(Object? element) =>
9480+
element is objc.ObjCObjectBase ? containsObject_(element) : false;
9481+
9482+
@override
9483+
objc.ObjCObjectBase? lookup(Object? element) =>
9484+
element is objc.ObjCObjectBase ? member_(element) : null;
9485+
9486+
@override
9487+
Iterator<objc.ObjCObjectBase> get iterator => objectEnumerator();
9488+
9489+
@override
9490+
Set<objc.ObjCObjectBase> toSet() => {...this};
9491+
9492+
@override
9493+
bool add(objc.ObjCObjectBase value) =>
9494+
throw UnsupportedError("Cannot modify NSSet");
9495+
9496+
@override
9497+
bool remove(Object? value) => throw UnsupportedError("Cannot modify NSSet");
9498+
9499+
@override
9500+
void clear() => throw UnsupportedError("Cannot modify NSSet");
9501+
93839502
NSSet._(ffi.Pointer<objc.ObjCObject> pointer,
93849503
{bool retain = false, bool release = false})
93859504
: super.castFromPointer(pointer, retain: retain, release: release);
@@ -18498,3 +18617,43 @@ class _NSArrayIterator implements Iterator<objc.ObjCObjectBase> {
1849818617
return true;
1849918618
}
1850018619
}
18620+
18621+
class _NSDictionaryKeyIterable with Iterable<NSCopying> {
18622+
NSDictionary _dictionary;
18623+
18624+
_NSDictionaryKeyIterable(this._dictionary);
18625+
18626+
@override
18627+
int get length => _dictionary.length;
18628+
18629+
@override
18630+
Iterator<NSCopying> get iterator =>
18631+
_NSDictionaryKeyIterator(_dictionary.keyEnumerator());
18632+
18633+
@override
18634+
bool contains(Object? key) => _dictionary.containsKey(key);
18635+
}
18636+
18637+
class _NSDictionaryValueIterable with Iterable<objc.ObjCObjectBase> {
18638+
NSDictionary _dictionary;
18639+
18640+
_NSDictionaryValueIterable(this._dictionary);
18641+
18642+
@override
18643+
int get length => _dictionary.length;
18644+
18645+
@override
18646+
Iterator<objc.ObjCObjectBase> get iterator => _dictionary.objectEnumerator();
18647+
}
18648+
18649+
class _NSDictionaryKeyIterator implements Iterator<NSCopying> {
18650+
final Iterator<objc.ObjCObjectBase> _iterator;
18651+
_NSDictionaryKeyIterator(this._iterator);
18652+
18653+
@override
18654+
NSCopying get current => NSCopying.castFrom(_iterator.current);
18655+
18656+
@override
18657+
@pragma('vm:prefer-inline')
18658+
bool moveNext() => _iterator.moveNext();
18659+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Objective C support is only available on mac.
6+
@TestOn('mac-os')
7+
library;
8+
9+
import 'dart:ffi';
10+
11+
import 'package:objective_c/objective_c.dart';
12+
import 'package:test/test.dart';
13+
14+
void main() {
15+
group('NSDictionary', () {
16+
setUpAll(() {
17+
// TODO(https://github.com/dart-lang/native/issues/1068): Remove this.
18+
DynamicLibrary.open('test/objective_c.dylib');
19+
});
20+
21+
test('of', () {
22+
final obj1 = 'obj1'.toNSString();
23+
final obj2 = 'obj2'.toNSString();
24+
final obj3 = 'obj3'.toNSString();
25+
final obj4 = 'obj4'.toNSString();
26+
final obj5 = 'obj5'.toNSString();
27+
final obj6 = 'obj6'.toNSString();
28+
29+
final dict = NSDictionary.of({
30+
obj1: obj2,
31+
obj3: obj4,
32+
obj5: obj6,
33+
});
34+
35+
expect(dict.length, 3);
36+
expect(dict[obj1], obj2);
37+
expect(dict[obj3], obj4);
38+
expect(dict[obj5], obj6);
39+
40+
// Keys are copied, so compare the string values.
41+
final actualKeys = <String>[];
42+
for (final key in dict.keys) {
43+
actualKeys.add(NSString.castFrom(key).toDartString());
44+
}
45+
expect(actualKeys, unorderedEquals(['obj1', 'obj3', 'obj5']));
46+
47+
// Values are stored by reference, so we can compare the actual objects.
48+
final actualValues = <ObjCObjectBase>[];
49+
for (final value in dict.values) {
50+
actualValues.add(value);
51+
}
52+
expect(actualValues, unorderedEquals([obj2, obj4, obj6]));
53+
});
54+
55+
test('immutable', () {
56+
final obj1 = 'obj1'.toNSString();
57+
final obj2 = 'obj2'.toNSString();
58+
final obj3 = 'obj3'.toNSString();
59+
final obj4 = 'obj4'.toNSString();
60+
final obj5 = 'obj5'.toNSString();
61+
final obj6 = 'obj6'.toNSString();
62+
63+
// NSDictionary.of actually returns a NSMutableDictionary, so our
64+
// immutability tests wouldn't actually work. So convert it to a real
65+
// NSDictionary using an ObjC constructor.
66+
final dict = NSDictionary.dictionaryWithDictionary_(NSDictionary.of({
67+
obj1: obj2,
68+
obj3: obj4,
69+
obj5: obj6,
70+
}));
71+
72+
expect(() => dict[obj3] = obj1, throwsUnsupportedError);
73+
expect(dict.clear, throwsUnsupportedError);
74+
expect(() => dict.remove(obj1), throwsUnsupportedError);
75+
});
76+
77+
test('MapBase mixin', () {
78+
final obj1 = 'obj1'.toNSString();
79+
final obj2 = 'obj2'.toNSString();
80+
final obj3 = 'obj3'.toNSString();
81+
final obj4 = 'obj4'.toNSString();
82+
final obj5 = 'obj5'.toNSString();
83+
final obj6 = 'obj6'.toNSString();
84+
85+
final dict = NSDictionary.of({
86+
obj1: obj2,
87+
obj3: obj4,
88+
obj5: obj6,
89+
});
90+
91+
expect(dict.isNotEmpty, isTrue);
92+
expect(dict.containsKey(obj1), isTrue);
93+
expect(dict.containsKey(obj4), isFalse);
94+
expect(dict.containsValue(obj2), isTrue);
95+
expect(dict.containsValue(obj3), isFalse);
96+
97+
expect(
98+
dict.map((key, value) =>
99+
MapEntry<ObjCObjectBase, ObjCObjectBase>(value, key)),
100+
{
101+
obj2: obj1,
102+
obj4: obj3,
103+
obj6: obj5,
104+
});
105+
expect(
106+
dict.keys
107+
.map((key) => NSString.castFrom(key).toDartString())
108+
.toList(),
109+
unorderedEquals(['obj1', 'obj3', 'obj5']));
110+
expect(dict.values.toList(), unorderedEquals([obj2, obj4, obj6]));
111+
});
112+
});
113+
}

0 commit comments

Comments
 (0)