Skip to content

Commit cff1f3e

Browse files
authored
Refactor data bridging and move to swift-foundation (#1578)
* Refactor data bridging and move to swift-foundation * Update CMake file
1 parent 3c602cd commit cff1f3e

File tree

3 files changed

+123
-4
lines changed

3 files changed

+123
-4
lines changed

Sources/FoundationEssentials/Data/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ target_sources(FoundationEssentials PRIVATE
2323
ContiguousBytes.swift
2424
Data.swift
2525
Data+Base64.swift
26+
Data+Bridging.swift
2627
Data+Deprecated.swift
2728
Data+Error.swift
2829
Data+Iterator.swift
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2022-2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#if FOUNDATION_FRAMEWORK
13+
14+
internal import _ForSwiftFoundation
15+
16+
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
17+
extension __DataStorage {
18+
@inline(never) // This is not @inlinable to avoid emission of the private `__NSSwiftData` class name into clients.
19+
@usableFromInline
20+
func bridgedReference(_ range: Range<Int>) -> AnyObject {
21+
if range.isEmpty {
22+
return NSData() // zero length data can be optimized as a singleton
23+
}
24+
25+
return __NSSwiftData(backing: self, range: range)
26+
}
27+
}
28+
29+
// NOTE: older overlays called this _NSSwiftData. The two must
30+
// coexist, so it was renamed. The old name must not be used in the new
31+
// runtime.
32+
internal final class __NSSwiftData : NSData {
33+
var _backing: __DataStorage!
34+
var _range: Range<Data.Index>!
35+
36+
convenience init(backing: __DataStorage, range: Range<Data.Index>) {
37+
self.init()
38+
_backing = backing
39+
_range = range
40+
}
41+
@objc override var length: Int {
42+
return _range.upperBound - _range.lowerBound
43+
}
44+
45+
@objc override var bytes: UnsafeRawPointer {
46+
// NSData's byte pointer methods are not annotated for nullability correctly
47+
// (but assume non-null by the wrapping macro guards). This placeholder value
48+
// is to work-around this bug. Any indirection to the underlying bytes of an NSData
49+
// with a length of zero would have been a programmer error anyhow so the actual
50+
// return value here is not needed to be an allocated value. This is specifically
51+
// needed to live like this to be source compatible with Swift3. Beyond that point
52+
// this API may be subject to correction.
53+
guard let bytes = _backing.bytes else {
54+
return UnsafeRawPointer(bitPattern: 0xBAD0)!
55+
}
56+
57+
return bytes.advanced(by: _range.lowerBound)
58+
}
59+
60+
@objc override func copy(with zone: NSZone? = nil) -> Any {
61+
if _backing._copyWillRetain {
62+
return self
63+
} else {
64+
return NSData(bytes: bytes, length: length)
65+
}
66+
67+
}
68+
69+
@objc override func mutableCopy(with zone: NSZone? = nil) -> Any {
70+
return NSMutableData(bytes: bytes, length: length)
71+
}
72+
73+
@objc override
74+
func _isCompact() -> Bool {
75+
return true
76+
}
77+
78+
@objc override
79+
func _bridgingCopy(_ bytes: UnsafeMutablePointer<UnsafeRawPointer?>, length: UnsafeMutablePointer<Int>) -> Data? {
80+
fatalError("Unexpected call to __NSSwiftData._bridgingCopy(_:length:)")
81+
}
82+
}
83+
84+
extension Data {
85+
internal func _bridgeToObjectiveCImpl() -> AnyObject {
86+
switch _representation {
87+
case .empty: return NSData()
88+
case .inline(let inline):
89+
return inline.withUnsafeBytes {
90+
return NSData(bytes: $0.baseAddress, length: $0.count)
91+
}
92+
case .slice(let slice):
93+
return slice.storage.bridgedReference(slice.range)
94+
case .large(let slice):
95+
return slice.storage.bridgedReference(slice.range)
96+
}
97+
}
98+
99+
internal static func _bridgeFromObjectiveCAdoptingNativeStorageOf(_ source: AnyObject) -> Data? {
100+
guard object_getClass(source) == __NSSwiftData.self else { return nil }
101+
102+
let swiftData = unsafeDowncast(source, to: __NSSwiftData.self)
103+
let range = swiftData._range!
104+
let originalBacking = swiftData._backing!
105+
106+
// (rdar://162776451) Some clients assume that the double-bridged Data's start index is 0 due to historical behavior. We need to make sure the created Data's indices begin at 0 rather than preserving the original offset/slice range here. This requires creating a new __DataStorage instead of using the existing one.
107+
// (rdar://121865256) We also need to make sure that we don't create a new __DataStorage that holds on to the original via the deallocator. If a value is double bridged repeatedly (as is the case in some clients), unwinding in the dealloc can cause a stack overflow. This requires either using the existing __DataStorage, or creating a new one with a copy of the bytes to avoid a deallocator chain.
108+
// Based on the two constraints above, we perform a copy here. Ideally in the future if we remove the first constraint we could re-use the existing originalBacking to avoid the copy.
109+
let newBacking = __DataStorage(bytes: originalBacking.mutableBytes?.advanced(by: range.lowerBound), length: range.count)
110+
111+
if InlineSlice.canStore(count: newBacking.length) {
112+
return Data(representation: .slice(InlineSlice(newBacking, count: newBacking.length)))
113+
} else {
114+
return Data(representation: .large(LargeSlice(newBacking, count: newBacking.length)))
115+
}
116+
}
117+
}
118+
119+
#endif

Sources/FoundationEssentials/Data/Data+Searching.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@ extension Data {
2929
} else {
3030
nsRange = NSRange(location: 0, length: count)
3131
}
32-
let result = _representation.withInteriorPointerReference {
33-
let opts = NSData.SearchOptions(rawValue: options.rawValue)
34-
return $0.range(of: dataToFind, options: opts, in: nsRange)
35-
}
32+
let nsData = self as NSData
33+
let opts = NSData.SearchOptions(rawValue: options.rawValue)
34+
let result = nsData.range(of: dataToFind, options: opts, in: nsRange)
3635
if result.location == NSNotFound {
3736
return nil
3837
}

0 commit comments

Comments
 (0)