Skip to content

Commit 1cce12f

Browse files
committed
Add an Array-based NSMutableArray subclass
1 parent 698bcad commit 1cce12f

File tree

6 files changed

+224
-9
lines changed

6 files changed

+224
-9
lines changed

stdlib/public/core/BridgeObjectiveC.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,7 @@ public protocol _ObjectiveCBridgeable {
8585

8686
#if _runtime(_ObjC)
8787

88-
@available(macOS, introduced: 9999, deprecated)
89-
@available(iOS, introduced: 9999, deprecated)
90-
@available(watchOS, introduced: 9999, deprecated)
91-
@available(tvOS, introduced: 9999, deprecated)
88+
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
9289
@available(*, deprecated)
9390
@_cdecl("_SwiftCreateBridgedArray")
9491
@usableFromInline
@@ -101,6 +98,19 @@ internal func _SwiftCreateBridgedArray(
10198
return Unmanaged<AnyObject>.passRetained(bridged)
10299
}
103100

101+
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
102+
@available(*, deprecated)
103+
@_cdecl("_SwiftCreateBridgedMutableArray")
104+
@usableFromInline
105+
internal func _SwiftCreateBridgedMutableArray(
106+
values: UnsafePointer<AnyObject>,
107+
numValues: Int
108+
) -> Unmanaged<AnyObject> {
109+
let bufPtr = UnsafeBufferPointer(start: values, count: numValues)
110+
let bridged = _SwiftNSMutableArray(Array(bufPtr))
111+
return Unmanaged<AnyObject>.passRetained(bridged)
112+
}
113+
104114
@_silgen_name("swift_stdlib_connectNSBaseClasses")
105115
internal func _connectNSBaseClasses() -> Bool
106116

stdlib/public/core/Runtime.swift.gyb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,18 @@ internal class __SwiftNativeNSArray {
276276
deinit {}
277277
}
278278

279+
@_fixed_layout
280+
@usableFromInline
281+
@objc @_swift_native_objc_runtime_base(__SwiftNativeNSMutableArrayBase)
282+
internal class _SwiftNativeNSMutableArray {
283+
@inlinable
284+
@nonobjc
285+
internal init() {}
286+
// @objc public init(coder: AnyObject) {}
287+
@inlinable
288+
deinit {}
289+
}
290+
279291
@_fixed_layout
280292
@usableFromInline
281293
@objc @_swift_native_objc_runtime_base(__SwiftNativeNSDictionaryBase)

stdlib/public/core/SwiftNativeNSArray.swift

Lines changed: 193 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,16 @@ internal class __SwiftNativeNSArrayWithContiguousStorage
6161
}
6262
}
6363

64+
private let NSNotFound: Int = .max
65+
6466
// Implement the APIs required by NSArray
6567
extension __SwiftNativeNSArrayWithContiguousStorage: _NSArrayCore {
6668
@objc internal var count: Int {
6769
return withUnsafeBufferOfObjects { $0.count }
6870
}
6971

70-
@objc(objectAtIndex:)
71-
internal func objectAt(_ index: Int) -> AnyObject {
72+
@inline(__always)
73+
@nonobjc private func _objectAt(_ index: Int) -> AnyObject {
7274
return withUnsafeBufferOfObjects {
7375
objects in
7476
_precondition(
@@ -77,6 +79,16 @@ extension __SwiftNativeNSArrayWithContiguousStorage: _NSArrayCore {
7779
return objects[index]
7880
}
7981
}
82+
83+
@objc(objectAtIndexedSubscript:)
84+
dynamic internal func objectAtSubscript(_ index: Int) -> AnyObject {
85+
return _objectAt(index)
86+
}
87+
88+
@objc(objectAtIndex:)
89+
dynamic internal func objectAt(_ index: Int) -> AnyObject {
90+
return _objectAt(index)
91+
}
8092

8193
@objc internal func getObjects(
8294
_ aBuffer: UnsafeMutablePointer<AnyObject>, range: _SwiftNSRange
@@ -131,6 +143,172 @@ extension __SwiftNativeNSArrayWithContiguousStorage: _NSArrayCore {
131143
}
132144
}
133145

146+
@_fixed_layout
147+
@usableFromInline
148+
@objc internal final class _SwiftNSMutableArray :
149+
_SwiftNativeNSMutableArray, _NSArrayCore
150+
{
151+
internal var contents: [AnyObject]
152+
153+
internal init(_ array: [AnyObject]) {
154+
contents = array
155+
super.init()
156+
}
157+
158+
@objc internal var count: Int {
159+
return contents.count
160+
}
161+
162+
@objc(objectAtIndexedSubscript:)
163+
dynamic internal func objectAtSubscript(_ index: Int) -> AnyObject {
164+
//TODO: exception instead of precondition, once that's possible
165+
return contents[index]
166+
}
167+
168+
@objc(objectAtIndex:)
169+
dynamic internal func objectAt(_ index: Int) -> AnyObject {
170+
//TODO: exception instead of precondition, once that's possible
171+
return contents[index]
172+
}
173+
174+
@objc internal func getObjects(
175+
_ aBuffer: UnsafeMutablePointer<AnyObject>, range: _SwiftNSRange
176+
) {
177+
return contents.withContiguousStorageIfAvailable { objects in
178+
//TODO: exceptions instead of preconditions, once that's possible
179+
180+
_precondition(
181+
_isValidArrayIndex(range.location, count: objects.count),
182+
"Array index out of range")
183+
184+
_precondition(
185+
_isValidArrayIndex(
186+
range.location + range.length, count: objects.count),
187+
"Array index out of range")
188+
189+
if objects.isEmpty { return }
190+
191+
// These objects are "returned" at +0, so treat them as pointer values to
192+
// avoid retains. Copy bytes via a raw pointer to circumvent reference
193+
// counting while correctly aliasing with all other pointer types.
194+
UnsafeMutableRawPointer(aBuffer).copyMemory(
195+
from: objects.baseAddress! + range.location,
196+
byteCount: range.length * MemoryLayout<AnyObject>.stride)
197+
}!
198+
}
199+
200+
@objc(countByEnumeratingWithState:objects:count:)
201+
internal func countByEnumerating(
202+
with state: UnsafeMutablePointer<_SwiftNSFastEnumerationState>,
203+
objects: UnsafeMutablePointer<AnyObject>?, count: Int
204+
) -> Int {
205+
var enumerationState = state.pointee
206+
207+
if enumerationState.state != 0 {
208+
return 0
209+
}
210+
211+
return contents.withContiguousStorageIfAvailable {
212+
objects in
213+
enumerationState.mutationsPtr = _fastEnumerationStorageMutationsPtr
214+
enumerationState.itemsPtr =
215+
AutoreleasingUnsafeMutablePointer(objects.baseAddress)
216+
enumerationState.state = 1
217+
state.pointee = enumerationState
218+
return objects.count
219+
}!
220+
}
221+
222+
@objc(copyWithZone:)
223+
dynamic internal func copy(with _: _SwiftNSZone?) -> AnyObject {
224+
return contents._bridgeToObjectiveCImpl()
225+
}
226+
227+
@objc(insertObject:atIndex:)
228+
dynamic internal func insert(_ anObject: AnyObject, at index: Int) {
229+
contents.insert(anObject, at: index)
230+
}
231+
232+
@objc(removeObjectAtIndex:)
233+
dynamic internal func removeObject(at index: Int) {
234+
contents.remove(at: index)
235+
}
236+
237+
@objc(addObject:)
238+
dynamic internal func add(_ anObject: AnyObject) {
239+
contents.append(anObject)
240+
}
241+
242+
@objc(removeLastObject)
243+
dynamic internal func removeLastObject() {
244+
contents.removeLast()
245+
}
246+
247+
@objc(replaceObjectAtIndex:withObject:)
248+
dynamic internal func replaceObject(at index: Int, with anObject: AnyObject) {
249+
//enforces bounds, unlike set equivalent, which can append
250+
contents[index] = anObject
251+
}
252+
253+
//Non-core methods overridden for performance
254+
255+
@objc(exchangeObjectAtIndex:withObjectAtIndex:)
256+
dynamic internal func exchange(at index: Int, with index2: Int) {
257+
swap(&contents[index], &contents[index2])
258+
}
259+
260+
@objc(replaceObjectsInRange:withObjects:count:)
261+
dynamic internal func replaceObjects(in range: _SwiftNSRange,
262+
with objects: UnsafePointer<AnyObject>,
263+
count: Int) {
264+
let range = range.location ..< range.location + range.length
265+
let buf = UnsafeBufferPointer(start: objects, count: count)
266+
contents.replaceSubrange(range, with: buf)
267+
}
268+
269+
@objc(insertObjects:count:atIndex:)
270+
dynamic internal func insertObjects(_ objects: UnsafePointer<AnyObject>,
271+
count: Int,
272+
at index: Int) {
273+
let buf = UnsafeBufferPointer(start: objects, count: count)
274+
contents.insert(contentsOf: buf, at: index)
275+
}
276+
277+
@objc(indexOfObjectIdenticalTo:)
278+
dynamic internal func index(ofObjectIdenticalTo object: AnyObject) -> Int {
279+
return contents.firstIndex { $0 === object } ?? NSNotFound
280+
}
281+
282+
@objc(removeObjectsInRange:)
283+
dynamic internal func removeObjects(in range: _SwiftNSRange) {
284+
let range = range.location ..< range.location + range.length
285+
contents.replaceSubrange(range, with: [])
286+
}
287+
288+
@objc(removeAllObjects)
289+
dynamic internal func removeAllObjects() {
290+
contents = []
291+
}
292+
293+
@objc(setObject:atIndex:)
294+
dynamic internal func setObject(_ anObject: AnyObject, at index: Int) {
295+
if index == contents.count {
296+
contents.append(anObject)
297+
} else {
298+
contents[index] = anObject
299+
}
300+
}
301+
302+
@objc(setObject:atIndexedSubscript:) dynamic
303+
internal func setObjectSubscript(_ anObject: AnyObject, at index: Int) {
304+
if index == contents.count {
305+
contents.append(anObject)
306+
} else {
307+
contents[index] = anObject
308+
}
309+
}
310+
}
311+
134312
/// An `NSArray` whose contiguous storage is created and filled, upon
135313
/// first access, by bridging the elements of a Swift `Array`.
136314
///
@@ -292,6 +470,18 @@ internal class __ContiguousArrayStorageBase
292470
_internalInvariantFailure(
293471
"Concrete subclasses must implement _getNonVerbatimBridgingBuffer")
294472
}
473+
474+
@objc(mutableCopyWithZone:)
475+
dynamic internal func mutableCopy(with _: _SwiftNSZone?) -> AnyObject {
476+
let arr = Array<AnyObject>(_ContiguousArrayBuffer(self))
477+
return _SwiftNSMutableArray(arr)
478+
}
479+
480+
@objc(indexOfObjectIdenticalTo:)
481+
dynamic internal func index(ofObjectIdenticalTo object: AnyObject) -> Int {
482+
let arr = Array<AnyObject>(_ContiguousArrayBuffer(self))
483+
return arr.firstIndex { $0 === object } ?? NSNotFound
484+
}
295485
#endif
296486

297487
@inlinable
@@ -306,7 +496,7 @@ internal class __ContiguousArrayStorageBase
306496
_internalInvariantFailure(
307497
"Concrete subclasses must implement staticElementType")
308498
}
309-
499+
310500
@inlinable
311501
deinit {
312502
_internalInvariant(

stdlib/public/stubs/SwiftNativeNSXXXBase.mm.gyb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ using namespace swift;
5050
// NOTE: older runtimes called these _SwiftNativeNSXXXBase. The two must
5151
// coexist, so these were renamed. The old names must not be used in the new
5252
// runtime.
53-
% for Class in ('Array', 'Dictionary', 'Set', 'String', 'Enumerator'):
53+
% for Class in ('Array', 'MutableArray', 'Dictionary', 'Set', 'String', 'Enumerator'):
5454
SWIFT_RUNTIME_STDLIB_API
5555
@interface __SwiftNativeNS${Class}Base : NSObject
5656
{
@@ -120,7 +120,7 @@ swift_stdlib_NSObject_isEqual(id lhs,
120120
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_SPI
121121
bool
122122
swift_stdlib_connectNSBaseClasses() {
123-
% for Class in ('Array', 'Dictionary', 'Set', 'String', 'Enumerator'):
123+
% for Class in ('Array', 'MutableArray', 'Dictionary', 'Set', 'String', 'Enumerator'):
124124
Class NS${Class}Super = objc_lookUpClass("NS${Class}");
125125
if (!NS${Class}Super) return false;
126126
Class NS${Class}OurClass = objc_lookUpClass("__SwiftNativeNS${Class}Base");

test/api-digester/Outputs/stability-stdlib-abi.asserts.additional.swift.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
Class _SwiftNSMutableArray is a new API without @available attribute
2+
Class _SwiftNativeNSMutableArray is a new API without @available attribute
13
Func _collectReferencesInsideObject(_:) is a new API without @available attribute
24
Func _loadDestroyTLSCounter() is a new API without @available attribute
35
Protocol _RuntimeFunctionCountersStats is a new API without @available attribute

test/stdlib/Inputs/SwiftNativeNSBase/SwiftNativeNSBase.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ BOOL TestSwiftNativeNSBase_UnwantedCdtors()
6565
NSMutableSet *expectedClasses =
6666
[NSMutableSet setWithObjects:
6767
@"__SwiftNativeNSArrayBase",
68+
@"__SwiftNativeNSMutableArrayBase",
6869
@"__SwiftNativeNSDictionaryBase",
6970
@"__SwiftNativeNSSetBase",
7071
@"__SwiftNativeNSStringBase",

0 commit comments

Comments
 (0)