-
-
Save preble/ab98fabda985b054126e to your computer and use it in GitHub Desktop.
import Cocoa | |
@objc | |
class SomeTextStorage: NSTextStorage { | |
private var storage: NSMutableAttributedString | |
override init() { | |
storage = NSMutableAttributedString(string: "", attributes: nil) | |
super.init() | |
} | |
required init?(coder aDecoder: NSCoder) { | |
fatalError("\(__FUNCTION__) is not supported") | |
} | |
required init?(pasteboardPropertyList propertyList: AnyObject, ofType type: String) { | |
fatalError("\(__FUNCTION__) is not supported") | |
} | |
// MARK: NSTextStorage Primitive Methods | |
// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextStorageLayer/Tasks/Subclassing.html | |
override var string: String { | |
return storage.string | |
} | |
override func attributesAtIndex(location: Int, effectiveRange range: NSRangePointer) -> [String : AnyObject] { | |
return storage.attributesAtIndex(location, effectiveRange: range) | |
} | |
override func replaceCharactersInRange(range: NSRange, withString str: String) { | |
beginEditing() | |
storage.replaceCharactersInRange(range, withString: str) | |
edited(.EditedCharacters, range: range, changeInLength: (str as NSString).length - range.length) | |
endEditing() | |
} | |
override func setAttributes(attrs: [String : AnyObject]?, range: NSRange) { | |
beginEditing() | |
storage.setAttributes(attrs, range: range) | |
edited(.EditedAttributes, range: range, changeInLength: 0) | |
endEditing() | |
} | |
} | |
Great, thanks! Wrapping the mutating methods in beginEditing/endEditing even though it's not strictly necessary solves a lot of delegate callback problems.
But I also notice that this minimal replacement type is enough to let memory consumption spike when editing if you also replace the layout manager, like with textView.textContainer?.replaceLayoutManager(NSLayoutManager())
:
- put 14k words in text view
- scroll down a bit
- hit enter a couple of times
According to instruments, I can get 8 GB of allocations in 8 seconds, mostly due to the string
read-only property.
The memory issue is gone when you delegate to a NSTextStorage
instead of a NSAttributedString
, though:
https://stackoverflow.com/questions/37952726/sub-classing-nstextstorage-causes-significant-memory-issues
class SomeTextStorage: NSTextStorage {
private var storage = NSTextStorage()
// MARK: NSTextStorage Primitive Methods
// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextStorageLayer/Tasks/Subclassing.html
override var string: String {
return storage.string
}
override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [String : Any] {
return storage.attributes(at: location, effectiveRange: range)
}
override func replaceCharacters(in range: NSRange, with str: String) {
beginEditing()
storage.replaceCharacters(in: range, with: str)
edited(.editedCharacters, range: range, changeInLength: (str as NSString).length - range.length)
endEditing()
}
override func setAttributes(_ attrs: [String : Any]?, range: NSRange) {
beginEditing()
storage.setAttributes(attrs, range: range)
edited(.editedAttributes, range: range, changeInLength: 0)
endEditing()
}
}
Since beginEditing
/endEditing
is not needed here but works nicely, I do not call these methods on storage
. Note that I am not 100% certain if the behavior will change for the better or worse in some circumstances if you do.
Swift 3 version: