Подкласс NSTextStorage вызывает серьезные проблемы с памятью

У меня есть пользовательский UITextView, который использует преимущества TextKit от Apple, определяя пользовательский класс NSTextStorage, однако, когда я использую свой подкласс для настраиваемого текстового представления, текстового хранилища (как реализовано ниже) и пытаюсь открыть любой файл размером более 20,0 КБ, приложение вылетает из-за утечки памяти: "Message from debugger: Terminated due to memory issue".

Как ни странно, если я заменю свой собственный BMTextStorage только стандартным, NSTextStorage, текст загружается мгновенно без какой-либо утечки памяти и использует ‹ 30 МБ ОЗУ. Чем это вызвано?

TextView.swift

class TextView : UITextView {

    required init(frame: CGRect) {

        // If I replace the following line with simply 
        // "let textStorage = NSTextStorage()"
        // I can open any file of any size and not have a memory leak
        // issue, using only about 20-30MB of RAM. If I ran this code
        // as is, it can open most files less than 20KB but will 
        // crash otherwise.
        let textStorage = BMTextStorage() 

        let layoutManager = NSLayoutManager()

        layoutManager.allowsNonContiguousLayout = true

        let textContainer = NSTextContainer(size: CGSizeMake(.max, .max))

        textContainer.widthTracksTextView = true
        textContainer.heightTracksTextView = true
        textContainer.exclusionPaths = [UIBezierPath(rect: CGRectMake(0.0, 0.0, 40.0, .max))]

        layoutManager.addTextContainer(textContainer)
        textStorage.addLayoutManager(layoutManager)

        super.init(frame: frame, textContainer: textContainer)

        textStorage.delegate = self
        layoutManager.delegate = self

    }

}

BMTextStorage.swift

typealias PropertyList = [String : AnyObject]

class BMTextStorage : NSTextStorage {

    override var string: String {
        return storage.string
    }

    private var storage = NSMutableAttributedString()

    override func attributesAtIndex(location: Int, effectiveRange range: NSRangePointer) -> PropertyList {
        return storage.attributesAtIndex(location, effectiveRange: range)
    }

    override func replaceCharactersInRange(range: NSRange, withString str: String) {
        storage.replaceCharactersInRange(range, withString: str)
        edited([.EditedAttributes, .EditedCharacters], range: range, changeInLength: str.length - range.length)
    }

    override func setAttributes(attrs: PropertyList?, range: NSRange) {
        storage.setAttributes(attrs, range: range)
        edited([.EditedAttributes], range: range, changeInLength: 0)
    }

    override func processEditing() {
        super.processEditing()
    }

 }

person NoodleOfDeath    schedule 21.06.2016    source источник
comment
Можете ли вы сузить его до одного из четырех методов, которые вы переопределяете? (А processEditing() можно вообще удалить, так как он ничего не делает.)   -  person NRitH    schedule 21.06.2016
comment
@NRitH Я не могу сузить его, так как эти методы требуются абстрактному классу для переопределения. И да, проблема не изменится, если я пропущу processEditing() Я пробовал точки останова, я полагаю, но он просто зацикливается до сбоя, особенно для больших файлов   -  person NoodleOfDeath    schedule 21.06.2016


Ответы (1)


Вау.... странно, это было исправлено, когда я изменил тип storage на NSTextStorage....

typealias PropertyList = [String : AnyObject]

class BMTextStorage : NSTextStorage {

    overrride var string: String {
        return storage.string
    }

    private var storage = NSTextStorage()

    override func attributesAtIndex(location: Int, effectiveRange range: NSRangePointer) -> PropertyList {
        return storage.attributesAtIndex(location, effectiveRange: range)
    }

    override func replaceCharactersInRange(range: NSRange, withString str: String) {
        storage.replaceCharactersInRange(range, withString: str)
        edited([.EditedAttributes, .EditedCharacters], range: range, changeInLength: str.length - range.length)
    }

    override func setAttributes(attrs: PropertyList?, range: NSRange) {
        storage.setAttributes(attrs, range: range)
        edited([.EditedAttributes], range: range, changeInLength: 0)
    }

    override func processEditing() {
        super.processEditing()
    }

 }
person NoodleOfDeath    schedule 21.06.2016
comment
Гениальный ход! Интересно, в каких необычных случаях это сломается. И теперь вам нужно звонить beginEditing/endEditing как для self, так и для storage? ???? А вот проблем с памятью пока нет. - person ctietze; 13.07.2017
comment
@ctietze как ни странно, я делаю только beginEditing и endEditing вызовы для self, и все работает нормально. Я считаю, что эти методы используются в первую очередь, чтобы помочь приложению управлять состоянием документа. - person NoodleOfDeath; 17.07.2017
comment
Я не могу не чувствовать, что это огромный запах кода от Apple API... Кроме того, почему /heck/ NSTextStorage является абстрактным классом, когда это не имеет смысла в Objective-C, это еще один отличный показатель того, что есть некоторый черный магия, стоящая за этим могущественным классом. Но спасибо за подсказку! +1 - person Bruno Philipe; 27.08.2017
comment
Потрясающая находка! вроде 4 года спустя - person Ayoub Khayati; 24.11.2020
comment
Спасибо за находку. Я не могу сделать их действительно гладкими. Это все еще немного шелушащийся. Я использовал NSTextStorage() и удалил все beginEditing/endEditing из моей перезаписанной реализации. Есть идеи, как получить потрясающий опыт прокрутки? - person Peter Shaw; 03.05.2021
comment
Понятно. Вызывается setApperance() в методе рисования. :facepalm: Спасибо за находку, даже в 2021 году! - person Peter Shaw; 03.05.2021