Skip to content

Commit af0aa94

Browse files
committed
[stdlib] lazy-eager bridging for non-UTF8 NSString instances
1 parent a061425 commit af0aa94

File tree

1 file changed

+82
-1
lines changed

1 file changed

+82
-1
lines changed

stdlib/public/core/StringGuts.swift

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -417,6 +417,87 @@ extension _StringGuts {
417417
}
418418
}
419419

420+
#if _runtime(_ObjC)
421+
extension _StringGuts {
422+
423+
private static var associationKey: UnsafeRawPointer {
424+
// We never dereference this, we just use this address as a unique key
425+
// TODO: perhaps use the address of a metatype instead
426+
unsafe UnsafeRawPointer(Builtin.addressof(&_swiftEmptyArrayStorage))
427+
}
428+
429+
internal func getAssociatedStorage() -> __StringStorage? {
430+
let getter = unsafe unsafeBitCast(
431+
getGetAssociatedObjectPtr(),
432+
to: (@convention(c)(
433+
AnyObject,
434+
UnsafeRawPointer
435+
) -> UnsafeRawPointer?).self
436+
)
437+
_precondition(_object.hasObjCBridgeableObject)
438+
// print("has ObjC Bridgeable Object")
439+
if let assocPtr = unsafe getter(
440+
_object.objCBridgeableObject,
441+
Self.associationKey
442+
) {
443+
let storage: __StringStorage
444+
storage = unsafe Unmanaged.fromOpaque(assocPtr).takeUnretainedValue()
445+
return storage
446+
}
447+
return nil
448+
}
449+
450+
internal func setAssociatedStorage(_ storage: __StringStorage) {
451+
let setter = unsafe unsafeBitCast(
452+
getSetAssociatedObjectPtr(),
453+
to: (@convention(c)(
454+
AnyObject,
455+
UnsafeRawPointer,
456+
AnyObject?,
457+
UInt
458+
) -> Void).self
459+
)
460+
461+
unsafe setter(
462+
_object.objCBridgeableObject,
463+
Self.associationKey,
464+
storage,
465+
1 //OBJC_ASSOCIATION_RETAIN_NONATOMIC
466+
)
467+
}
468+
469+
internal func getOrAllocateAssociatedStorage() -> __StringStorage {
470+
_precondition(_object.hasObjCBridgeableObject)
471+
let unwrapped: __StringStorage
472+
// libobjc already provides the necessary memory barriers for
473+
// double checked locking to be safe, per comments on
474+
// https://github.com/swiftlang/swift/pull/75148
475+
if let storage = getAssociatedStorage() {
476+
unwrapped = storage
477+
} else {
478+
let lock = _object.objCBridgeableObject
479+
objc_sync_enter(lock)
480+
if let storage = getAssociatedStorage() {
481+
unwrapped = storage
482+
} else {
483+
var contents = String.UnicodeScalarView()
484+
// always reserve a size larger than a small string
485+
contents.reserveCapacity(Swift.max(31, 1 + count + count >> 1))
486+
for c in String.UnicodeScalarView(self) {
487+
contents.append(c)
488+
}
489+
_precondition(contents._guts._object.hasNativeStorage)
490+
unwrapped = (consume contents)._guts._object.nativeStorage
491+
setAssociatedStorage(unwrapped)
492+
}
493+
defer { _fixLifetime(unwrapped) }
494+
objc_sync_exit(lock)
495+
}
496+
return unwrapped
497+
}
498+
}
499+
#endif
500+
420501
// Old SPI(corelibs-foundation)
421502
extension _StringGuts {
422503
public // SPI(corelibs-foundation)

0 commit comments

Comments
 (0)