Last active
January 21, 2021 17:00
-
-
Save leonbreedt/f242f9423af8d65a84cefadcf6a70945 to your computer and use it in GitHub Desktop.
Revisions
-
leonbreedt revised this gist
Jun 11, 2016 . 1 changed file with 29 additions and 29 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -62,35 +62,6 @@ class ExtensionObject: NSObject, JSValueRepresentable { return JSValue(JSValueRef: object, inContext: context) } // MARK: - Private private static func defineProxyClass() -> JSClassRef { @@ -118,6 +89,35 @@ class ExtensionObject: NSObject, JSValueRepresentable { return JSClassCreate(&definition) } private func getProperty(named name: String) -> JSValue { if let customProperty = properties.filter({$0.name == name}).first { return customProperty.value } else { guard let targetProperty = Mirror(reflecting: target!).children.filter({ $0.label == name }).first where target != nil else { return JSValue(nullInContext: context) } return anyAsJSValue(targetProperty.value, inContext: context) } } private func setProperty(named name: String, toValue value: JSValue) -> Bool { if let customProperty = properties.filter({$0.name == name}).first { customProperty.value = value return true } else { if let target = target as? NSObject { let startIndex = name.startIndex let firstCharacter = name.substringWithRange(startIndex...startIndex).uppercaseString let remainderOfName = name.substringWithRange(startIndex.advancedBy(1)...name.endIndex.predecessor()) if target.respondsToSelector(Selector("set\(firstCharacter)\(remainderOfName):")) { target.setValue(value.toObject(), forKey: name) } return true } } return false } } /// Returns a `JSValue` for a particular value. Supports integral, boolean, floating point -
leonbreedt revised this gist
Jun 11, 2016 . 1 changed file with 42 additions and 39 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -50,12 +50,49 @@ class ExtensionObject: NSObject, JSValueRepresentable { super.init() } deinit { JSClassRelease(proxyClass) } // MARK: - API var jsValue: JSValue { /// We want to keep `self` alive until the JSValue gets GC'd. let object = JSObjectMake(context.JSGlobalContextRef, proxyClass, retainedPointerFor(self)) return JSValue(JSValueRef: object, inContext: context) } func getProperty(named name: String) -> JSValue { if let customProperty = properties.filter({$0.name == name}).first { return customProperty.value } else { guard let targetProperty = Mirror(reflecting: target!).children.filter({ $0.label == name }).first where target != nil else { return JSValue(nullInContext: context) } return anyAsJSValue(targetProperty.value, inContext: context) } } func setProperty(named name: String, toValue value: JSValue) -> Bool { if let customProperty = properties.filter({$0.name == name}).first { customProperty.value = value return true } else { if let target = target as? NSObject { let startIndex = name.startIndex let firstCharacter = name.substringWithRange(startIndex...startIndex).uppercaseString let remainderOfName = name.substringWithRange(startIndex.advancedBy(1)...name.endIndex.predecessor()) if target.respondsToSelector(Selector("set\(firstCharacter)\(remainderOfName):")) { target.setValue(value.toObject(), forKey: name) } return true } } return false } // MARK: - Private private static func defineProxyClass() -> JSClassRef { var definition = kJSClassDefinitionEmpty @@ -81,40 +118,6 @@ class ExtensionObject: NSObject, JSValueRepresentable { return JSClassCreate(&definition) } } /// Returns a `JSValue` for a particular value. Supports integral, boolean, floating point @@ -129,15 +132,15 @@ func anyAsJSValue(any: Any, inContext context: JSContext) -> JSValue { case let int as Int: jsValue = JSValue(int32: Int32(int), inContext: context) case let uint as UInt: jsValue = JSValue(UInt32: UInt32(uint), inContext: context) case let uint8 as UInt8: jsValue = JSValue(UInt32: UInt32(uint8), inContext: context) case let uint16 as UInt16: jsValue = JSValue(UInt32: UInt32(uint16), inContext: context) case let uint32 as UInt32: jsValue = JSValue(UInt32: UInt32(uint32), inContext: context) case let uint64 as UInt64: jsValue = JSValue(UInt32: UInt32(uint64), inContext: context) case let bool as Bool: jsValue = JSValue(bool: bool, inContext: context) case let float as Float: -
leonbreedt created this gist
Jun 11, 2016 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,190 @@ import Cocoa import JavaScriptCore /// Something that can be represented as a `JSValue`. protocol JSValueRepresentable { /// The `JSValue` for this thing. var jsValue: JSValue { get } } /// Represents a mutable property, with a `JSValue` that is not copied every time /// the property is accessed, unlike the default `JSExport` behavior. class ExtensionProperty { /// The property name. let name: String /// The `JSValue` for the property. var value: JSValue init(name: String, value: JSValue) { self.name = name self.value = value } } /// Represents an object that will be passed into JavaScript extensions. /// Intentionally not using `JSExport` protocol, because its default behavior is /// to always create a copy of a property on access, which does not yield a great /// API for extensions, especially not for things like header mutation. class ExtensionObject: NSObject, JSValueRepresentable { let context: JSContext let properties: [ExtensionProperty] let proxyClass: JSClassRef var target: AnyObject? /// Creates a new `ExtensionObject` with a list of mutable properties. Any attempts /// to access properties not found in the list will be forwarded on to a target object. /// /// - parameter context: The `JSContext` in which this object should live. /// - parameter properties: The list of mutable properties. /// - parameter target: The object to forward any non-mutable property requests to. init(context: JSContext, properties: [ExtensionProperty], target: AnyObject?) { self.context = context self.properties = properties self.target = target self.proxyClass = ExtensionObject.defineProxyClass() super.init() } var jsValue: JSValue { /// We want to keep `self` alive until the JSValue gets GC'd. let object = JSObjectMake(context.JSGlobalContextRef, proxyClass, retainedPointerFor(self)) return JSValue(JSValueRef: object, inContext: context) } private static func defineProxyClass() -> JSClassRef { var definition = kJSClassDefinitionEmpty definition.getProperty = { context, object, name, exception in guard let proxy = JSObjectGetPrivate(object).dataValueOf(type: ExtensionObject.self) else { return JSValueMakeNull(context) } let name = (JSStringCopyCFString(kCFAllocatorDefault, name) as NSString) as String return proxy.getProperty(named: name).JSValueRef } definition.setProperty = { context, object, name, value, exception in guard let proxy = JSObjectGetPrivate(object).dataValueOf(type: ExtensionObject.self) else { return false } let name = (JSStringCopyCFString(kCFAllocatorDefault, name) as NSString) as String return proxy.setProperty(named: name, toValue: JSValue(JSValueRef: value, inContext: proxy.context)) } definition.finalize = { object in JSObjectGetPrivate(object).releasePointer() } return JSClassCreate(&definition) } func getProperty(named name: String) -> JSValue { if let customProperty = properties.filter({$0.name == name}).first { return customProperty.value } else { guard let targetProperty = Mirror(reflecting: target!).children.filter({ $0.label == name }).first where target != nil else { return JSValue(nullInContext: context) } return anyAsJSValue(targetProperty.value, inContext: context) } } func setProperty(named name: String, toValue value: JSValue) -> Bool { if let customProperty = properties.filter({$0.name == name}).first { customProperty.value = value return true } else { if let obj = target as? NSObject { let first = name.substringWithRange(name.startIndex...name.startIndex).uppercaseString let remainder = name.substringWithRange(name.startIndex.advancedBy(1)...name.endIndex.predecessor()) let selector = Selector("set\(first)\(remainder):") if obj.respondsToSelector(selector) { obj.setValue(value.toObject(), forKey: name) } return true } } return false } deinit { log.debug("Deinitializing \(self)") JSClassRelease(proxyClass) } } /// Returns a `JSValue` for a particular value. Supports integral, boolean, floating point /// and object values. /// /// - parameter any: A value for which to produce `JSValue`. /// - parameter context: The `JSContext` in which the value should live. /// - returns: A `JSValue` for the suppplied value. func anyAsJSValue(any: Any, inContext context: JSContext) -> JSValue { var jsValue = JSValue(nullInContext: context) switch any { case let int as Int: jsValue = JSValue(int32: Int32(int), inContext: context) case let uint as UInt: jsValue = JSValue(int32: Int32(uint), inContext: context) case let uint8 as UInt8: jsValue = JSValue(int32: Int32(uint8), inContext: context) case let uint16 as UInt16: jsValue = JSValue(int32: Int32(uint16), inContext: context) case let uint32 as UInt32: jsValue = JSValue(int32: Int32(uint32), inContext: context) case let uint64 as UInt64: jsValue = JSValue(int32: Int32(uint64), inContext: context) case let bool as Bool: jsValue = JSValue(bool: bool, inContext: context) case let float as Float: jsValue = JSValue(double: Double(float), inContext: context) case let double as Double: jsValue = JSValue(double: double, inContext: context) case let point as CGPoint: jsValue = JSValue(point: point, inContext: context) case let range as NSRange: jsValue = JSValue(range: range, inContext: context) case let rect as CGRect: jsValue = JSValue(rect: rect, inContext: context) case let size as CGSize: jsValue = JSValue(size: size, inContext: context) case let object as AnyObject: jsValue = JSValue(object: object, inContext: context) default: break } return jsValue } /// Increases the retain count of an object, and returns an `UnsafeMutablePointer<Void>` /// that can be passed into native code. /// /// - parameter value: The object to retain. ///.- returns: An `UnsafeMutablePointer<Void>` that can be passed into native code. private func retainedPointerFor(value: AnyObject) -> UnsafeMutablePointer<Void> { return UnsafeMutablePointer(Unmanaged.passRetained(value).toOpaque()) } private extension UnsafeMutablePointer { /// Returns the object associated with a pointer previously returned by `retainedPointerFor()`. Does /// not affect retain count. /// /// - parameter type: The type of the object to return. /// - returns: The object, or `nil` if this pointer is `nil`. func dataValueOf<T: AnyObject>(type type: T.Type) -> T? { guard self != nil else { return nil } return Unmanaged<T>.fromOpaque(COpaquePointer(self)).takeUnretainedValue() } /// Decreases the retain count of an object associated with a pointer previously returned by `retainedPointerFor()`. func releasePointer() { guard self != nil else { return } let _ = Unmanaged<AnyObject>.fromOpaque(COpaquePointer(self)).takeRetainedValue() } }