-
-
Notifications
You must be signed in to change notification settings - Fork 51
Implement JSString to reduce bridging overhead #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import _CJavaScriptKit | ||
|
||
/// `JSString` represents a string in JavaScript and supports bridging string between JavaScript and Swift. | ||
/// | ||
/// Conversion between `Swift.String` and `JSString` can be: | ||
/// | ||
/// ```swift | ||
/// // Convert `Swift.String` to `JSString` | ||
/// let jsString: JSString = ... | ||
/// let swiftString: String = String(jsString) | ||
/// | ||
/// // Convert `JSString` to `Swift.String` | ||
/// let swiftString: String = ... | ||
/// let jsString: JSString = JSString(swiftString) | ||
/// ``` | ||
/// | ||
public struct JSString: LosslessStringConvertible, Equatable { | ||
/// The internal representation of JS compatible string | ||
/// The initializers of this type must initialize `jsRef` or `buffer`. | ||
/// And the uninitialized one will be lazily initialized | ||
class Guts { | ||
var shouldDealocateRef: Bool = false | ||
lazy var jsRef: JavaScriptObjectRef = { | ||
self.shouldDealocateRef = true | ||
return buffer.withUTF8 { bufferPtr in | ||
return _decode_string(bufferPtr.baseAddress!, Int32(bufferPtr.count)) | ||
} | ||
}() | ||
|
||
lazy var buffer: String = { | ||
var bytesRef: JavaScriptObjectRef = 0 | ||
let bytesLength = Int(_encode_string(jsRef, &bytesRef)) | ||
// +1 for null terminator | ||
let buffer = malloc(Int(bytesLength + 1))!.assumingMemoryBound(to: UInt8.self) | ||
defer { | ||
free(buffer) | ||
_release(bytesRef) | ||
} | ||
_load_string(bytesRef, buffer) | ||
buffer[bytesLength] = 0 | ||
return String(decodingCString: UnsafePointer(buffer), as: UTF8.self) | ||
}() | ||
|
||
init(from stringValue: String) { | ||
self.buffer = stringValue | ||
} | ||
|
||
init(from jsRef: JavaScriptObjectRef) { | ||
self.jsRef = jsRef | ||
self.shouldDealocateRef = true | ||
} | ||
|
||
deinit { | ||
guard shouldDealocateRef else { return } | ||
_release(jsRef) | ||
} | ||
} | ||
|
||
let guts: Guts | ||
|
||
internal init(jsRef: JavaScriptObjectRef) { | ||
self.guts = Guts(from: jsRef) | ||
} | ||
|
||
/// Instantiate a new `JSString` with given Swift.String. | ||
public init(_ stringValue: String) { | ||
self.guts = Guts(from: stringValue) | ||
} | ||
|
||
/// A Swift representation of this `JSString`. | ||
/// Note that this accessor may copy the JS string value into Swift side memory. | ||
public var description: String { guts.buffer } | ||
|
||
/// Returns a Boolean value indicating whether two strings are equal values. | ||
/// | ||
/// - Parameters: | ||
/// - lhs: A string to compare. | ||
/// - rhs: Another string to compare. | ||
public static func == (lhs: JSString, rhs: JSString) -> Bool { | ||
return lhs.guts.buffer == rhs.guts.buffer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could there be a way to run this comparison on the JS side, without potentially triggering copies? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a trade-off point. If we compare them in JS side, it may trigger copies and re-encode Swift string to JS There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it make sense to add a flag to Guts that reports whether the value is cached in Swift? That way it would be possible to do the check in JS if neither string is cached in Swift. |
||
} | ||
} | ||
|
||
extension JSString: ExpressibleByStringLiteral { | ||
public init(stringLiteral value: String) { | ||
self.init(value) | ||
} | ||
} | ||
|
||
|
||
// MARK: - Internal Helpers | ||
extension JSString { | ||
|
||
func asInternalJSRef() -> JavaScriptObjectRef { | ||
guts.jsRef | ||
} | ||
|
||
func withRawJSValue<T>(_ body: (RawJSValue) -> T) -> T { | ||
let rawValue = RawJSValue( | ||
kind: .string, payload1: guts.jsRef, payload2: 0, payload3: 0 | ||
) | ||
return body(rawValue) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.