简体   繁体   中英

Swift - iterating through characters in string causes memory leak

this is my very first post! I'm relatively new to Swift, and don't have a computer science background, so I'm still prone to a lot of newbie problems - but I've been able to solve them all so far, partly from browsing the excellent answers on StackOverflow. However, I've come across something that's really stumped me, and I've spend a whole week trying to solve it with no luck.

What I'm trying to do is take text in Chinese from a UITextView and then convert this to an array of individual Chinese characters, which is then used for various processing and analysis. However, this causes a leak.

In this greatly simplified example, which reproduces the same leak, there is a TextView and a Button; when the user presses the button, the function makeArray is called, which converts the text to an array of characters (actually Strings of single characters, because I need it to be strings for some of the stuff I will do with it). The class TextProcessing that contains this function is used as a singleton (yeah, I know that apparently singletons are supposed to be bad, for reasons I don't fully understand, but for various reasons involving other parts of the code it works best when there is a single instance of this class), and the text from the UITextView is passed into it, where it's then converted to the array, as you can see below:

class ViewController: UIViewController {
    @IBOutlet weak var textBox: UITextView!
    @IBOutlet weak var doneButton: UIButton!

    @IBAction func pressDoneButton(_ sender: Any) {
        let textToAnalyze = textBox.text!
        TextProcessing.textProcessing.makeArray(textToAnalyze)
    }
}

class TextProcessing {
    static let textProcessing = TextProcessing()

    private let language = "Chinese"
    private var sourceTextArray: [String]!

    func makeArray (_ sourceText: String) {
        if language == "Chinese" {
            sourceTextArray = sourceText.characters.map { String($0) }
        } else if language == "English" {
            sourceTextArray = sourceText.components(separatedBy: " ")
        }
        // then do some stuff with this array
    }
}

When I run this on the Leaks Instruments I get leaks of "Malloc 16 Bytes" and "CFString", with the number of instances of each being roughly the same as the number of array elements (thus the number of characters in the string). When I look at the Call Tree and drill down, the problem line is "sourceTextArray = sourceText.characters.map { String($0) }".

By the way, this happens with relatively long texts - with short ones, either there's no problem or Instruments doesn't detect it.

However, if I make an array by separating the string into words according to spaces, like I would want in a language like English, there's no leak - so if I change the language variable to "English" in the example code, it works fine (but of course doesn't give me the array that I want). I thought that maybe the problem was in the "map" method, since it uses a closure and it's easy to have leaks with closures, but when I try other ways of putting it into an array of characters, such as using a for loop and iterating over each character that way, it still has the same problem.

If, instead of getting the text from the UITextView, I do this instead:

class ViewController: UIViewController {
    @IBOutlet weak var textBox: UITextView!
    @IBOutlet weak var doneButton: UIButton!

    @IBAction func pressDoneButton(_ sender: Any) {
        let textToAnalyze = "blah blah put a long string of Chinese text here"
        TextProcessing.textProcessing.makeArray(textToAnalyze)
    }
}

there's no problem. Likewise, if in the makeArray function, if I ignore sourceText and instead do this:

func makeArray (_ sourceText: String) {
    if language == "Chinese" {
        let anotherText = "blah blah some text here"
        sourceTextArray = anotherText.characters.map { String($0) }
    }
    // then do some stuff with this array
}

there's also no leak. So, something about the combination of getting the string from the text box, passing it into the function, and then putting it into an array of characters is causing the leak.

I've spent countless hours scouring the internet and reading everything about ARC in Swift, and I've tried all sorts of stuff with weak/unowned etc. and nothing seems to work. What's going on here and how could this be solved?

Edit:

So it appears that this might just be a problem with Simulator and/or Instruments. When I run it on the device, and just monitor memory usage in xcode debug, there's no increase even when doing it 100+ times, so I guess it's OK...it still seems weird that it would show a leak in Instruments though.

It's instruments bug(there is a lot of issues). Your code is OK.

I just filed a bug report (FB7684067).

The following simple macOS command line application will grow to over 1GB in only a few minutes:

import Foundation
let line = "124;5678;90123"
while true {
    let fields = line.components(separatedBy: ";")
    assert(fields[1] == "5678")
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM