[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