[swift-users] So how do you implement a NSTextStorage subclass in Swift?
Michel Fortin
michel.fortin at michelf.ca
Fri Feb 10 07:52:34 CST 2017
In case this is useful to someone, this is the workaround I'll be using:
```
class CustomTextStorage: NSTextStorage {
private let backingStore: NSMutableAttributedString
// This method should never get called from Objective-C as it doesn't respect
// the API contract because of the wrapping in `String`.
//
// This could get called directly from Swift code when dispatching without
// passing by the Objective-C runtime. So it must still produce the right
// thing for Swift.
override var string: String {
return backingStore.string
}
// Objective-C method implementation for `string` is remapped to this method to
// avoid wrapping the result in `String`. With the correct method signature
// we can return the backing store string object.
var backingNSString: NSString {
return backingStore.mutableString
}
// call once at program initialization:
static func fixupStringMethod() {
let theClass = CustomTextStorage.self
let badStringMeth = class_getInstanceMethod(theClass, #selector(getter: string))
let goodStringMeth = class_getInstanceMethod(theClass, #selector(getter: backingNSString))
let goodImp = method_getImplementation(goodStringMeth)
method_setImplementation(badStringMeth, goodImp)
}
// ... rest of the class goes here ...
}
```
> Le 9 févr. 2017 à 18:12, Michel Fortin via swift-users <swift-users at swift.org> a écrit :
>
> The `string` property of `NSTextStorage` is of type `String`, but the contract it must implement is that it should return the backing store of the attributed string (the underlying `NSMutableString` used as the backing store). It seems to me that this makes it impossible to implement correctly a subclass of `NSTextStorage` in Swift, because Swift automatically wraps the `NSString` into a `String` and the underlying mutable storage is not passed around.
>
> Here's the documentation for that method:
> https://developer.apple.com/reference/foundation/nsattributedstring/1412616-string
>
> In case the contract isn't clear from the documentation (it wasn't for me), I got a confirmation as a response in radar 30314719:
>> It’s returning a copy of the backing store, but the contract is actually returning the backing store string. The copy storage declaration in the property spec is only used for setter implementation.
>
> So looks like this is impossible to model correctly in Swift due to the automatic bridging to `String`. Some APIs in AppKit expect the `NSString` they receive to mutate when mutating the text storage (touch bar suggestions in `NSTextView`) and will do bad things this isn't the case.
>
> Obviously, I can work around this by writing some Objective-C code, but it'd be better if I could avoid splitting the class implementation between two languages. There's another way using swizzling to map the Objective-C method to a Swift implementation of the same method that has the correct signature, and that's probably what I'll end up doing unless a better solution can be pointed out to me.
--
Michel Fortin
https://michelf.ca
More information about the swift-users
mailing list