swift 里正确的实现一个 NSTextStorage 子类

Nov 11, 2019 • 预计阅读时间 1 分钟

即使你按照官方的文档来操作依然有可能会掉进坑里,因为 swift 的实现和 objc 稍有不同。

NSTextStorage 是基于 NSMutableAttributedString 的一个虚基类。
这意味着当你写一个 NSTextStorage 的子类时,必须正确实现以下 4 个接口

override var string: String {
  return innerStorage.string
}

override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key : Any] {
  return innerStorage.attributes(at: location, effectiveRange: range)
}

下面这个方法需要注意,不能使用 str.count - range.length ,必须要转为 NSString 用 length 的长度,否则在处理 emoji 表情字符的时候会引发死循环。

override func replaceCharacters(in range: NSRange, with str: String) {
  innerStorage.replaceCharacters(in: range, with: str)

  edited(.editedCharacters, range: range, changeInLength: (str as NSString).length - range.length)
}

override func setAttributes(_ attrs: [NSAttributedString.Key : Any]?, range: NSRange) {
  innerStorage.setAttributes(attrs, range: range)

  edited(.editedAttributes, range: range, changeInLength: 0)
}

以上代码里的 innerStorage 是私有变量,用来存储字符串和对应的属性。
它的定义如下:

private var innerStorage = NSTextStorage()

需要注意的是,不要使用 NSMutableAttributedString,因为 swift 目前有 bug, NSMutableAttributedString 会导致内存暴涨。
正确的用法是使用一个 NSTextStorage 实例作为内置存储容器。

最后,完整的子类代码如下:

import UIKit

class NDTextStorage: NSTextStorage {
  private var innerStorage = NSTextStorage()

  override var string: String {
    return innerStorage.string
  }

  override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key : Any] {
    return innerStorage.attributes(at: location, effectiveRange: range)
  }

  override func replaceCharacters(in range: NSRange, with str: String) {
    innerStorage.replaceCharacters(in: range, with: str)

    edited(.editedCharacters, range: range, changeInLength: (str as NSString).length - range.length)
  }

  override func setAttributes(_ attrs: [NSAttributedString.Key : Any]?, range: NSRange) {
    innerStorage.setAttributes(attrs, range: range)

    edited(.editedAttributes, range: range, changeInLength: 0)
  }
}
iOS

给 Xcode 添加最新的 iOS DeviceSupport

免费的国内 git 代码托管服务

comments powered by Disqus