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")
}
Related
Long time IOS developer/tinkerer here. I mostly taught myself programming, OBJ-C back in the day and now Swift. So apologies in advance if things I ask are too basic, its partly because I may not be well versed on some fundamentals.
I am currently working on an app. Alongside it I have been reading a fair bit on writing testable code and testing in general. I am not talking about purely TDD but I would like the libraries that I am creating for the app to have a good testset available. Partly because its good practice and partly because its soemthing I want to learn to do better.
So here goes, in my app class(es) I have a number of functions that take in parameters and give an output (as you do!). However, a number of these functions also make changes to class properties as data in these properties will be used in other class functions. For example:
class SomeClass() {
var someArrayProperty: [String] = []
var someInputParameter: String
init(input: String) {
//some initialisation code
self.someInputParameter = input
//Call function to build object
let object = self.buildObject(inputParameter: self.someInputParameter)
}
func buildObject(inputParameter: String) -> SomeObject {
let objectToReturn = SomeObject(withInputParameter: inputParameter)
let stringToAddToArray = "Object 1 created"
self.someArrayProperty.append(stringToAddToArray)
return objectToReturn
}
}
From what I have read about the testing, the code should ideally be such that it should do one job and not change something outside of the code as it becomes untestable for complex code. Here, the issue I have is that I am directly changing the someArrayProperty from within the method, i.e. changing something outside of the method.
Although its not an issue for the code and everything works fine, I would like to understand what do you guys feel about things like this from a testing point of view in your own code? And what pattern/changes you generally follow in your own code to avoid it?
Again, apologies if its too basic a question but I think it will help me fill in gaps in my knowledge to be able to write more beautiful code rather than something that just works and breaks next time a minor update is done somwhere. :)
Thanks
So if your function is called buildObject, it should do job inside it and have no return value. But if you call it buildedObject, it should return constructed object. You can read more about in Apple Naming Methods documentation.
And so your code should look like this:
class SomeClass() {
var someArrayProperty: [String] = []
var someInputParameter: String
init(input: String) {
//some initialisation code
self.someInputParameter = input
//Call function to build object
let object = self.buildedObject(inputParameter: self.someInputParameter)
// Other code which modifies data
let stringToAddToArray = "Object 1 created"
self.someArrayProperty.append(stringToAddToArray)
}
func buildedObject(inputParameter: String) -> SomeObject {
let objectToReturn = SomeObject(withInputParameter: inputParameter)
return objectToReturn
}
}
I am trying to make a dictionary with the properties of a class of mine.
class SomeClass() {
var someString = "Hello, stackoverflow"
var someInt = 42 // The answer to life, the universe and everything
var someBool = true
func objectToDict() -> [String: String] {
var dict = [String: String]()
let reflection = Mirror(reflecting: self)
for child in reflection.children {
if let key = child.label {
dict[key] = child.value as? AnyObject
}
return dict
}
}
but objectToDict() is very slow. Is there a way to speed this up, or may be another approach to add the property values to a Dictionary?
I do not agree with most other users. Using reflection results less code, which means less time to develop, maintain, and test your product. With a well written library like EVReflection you don't need to worry about things behind the scene too much.
However, if performance is going to be a concern, do NOT use reflection based approaches at all. I'd say it's never really a problem in front-end development for me, especially in iOS, but it cannot be ignored in back-end development.
To see how slow it can be, I ran some test in Xcode. I'll write a blog about it, but generally speaking, getting Mirror is not the worst part (plus it may be possible to catch property list in memory), so replacing it with objc runtime wouldn't change the situation too much. In the other hand, setValue(_, forKey) is surprisingly slow. Considering that in real life you also need to perform tasks like checking dynamicType and so on, using the dynamic approach surely will make it 100+ times slower, which won't be acceptable for server development.
- Looping 1,000,000 times for a class with 1 `Int` property
- Getting mirror: 1.52
- Looping throw mirror and set `child.value`: 3.3
- Looping throw mirror and set `42`: 3.27
- Setting value `42`: 0.05
Again, for iOS I'll keep using it to save my time. Hopefully end customers won't care about whether it's 0.005 seconds or 0.0005 seconds.
Not only is that slow, it's also not a good idea: mirroring is for debug introspection only. You should instead construct the dictionary yourself. This ensures that you have the flexibility to store all the data in exactly the right way, and also decouples your Swift property names from the keys of the dictionary you're generating.
class SomeClass {
var someString = "Hello, stackoverflow"
var someInt = 42 // The answer to life, the universe and everything
var someBool = true
func objectToDict() -> [String: AnyObject] {
return ["someString": someString, "someInt": someInt, "someBool": someBool]
}
}
I'm a bit new to Swift (so sorry if my question is a bit nooby) but...
So far my program takes strings in an array and displays them on the screen (Xcode)
I would like to implement a mechanism which makes it so that the user cannot get the same string twice in a row (when he/she presses the button).
My idea was to see if the random generated string is equal to the string already displayed on the label and (if thats true), delete the generated string from the array, run the function to display a random string from the array and then add that same string back after displaying a random fact.
Array of facts (in FactModel.swift):
struct FactModel {
var facts = [
"Ants stretch when they wake up in the morning.",
"Ostriches can run faster than horses.",
"Olympic gold medals are actually made mostly of silver.",
"etc..."
]
Function that gets returns a fact and the array index of that fact (In FactModel.swift):
func getRandomFact() -> (String,Int) {
let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(facts.count)
let checkFact = facts[randomNumber]
return (checkFact,randomNumber)
}
My ViewController code so far:
var mutableFactModel = FactModel()
#IBOutlet weak var FunFactButton: UIButton!
#IBOutlet weak var FunFact: UILabel!
#IBAction func ShowFunFact(sender: AnyObject) {
let localRandomFact = mutableFactModel.getRandomFact().0
if localRandomFact == FunFact.text {
mutableFactModel.facts.removeAtIndex(mutableFactModel.getRandomFact().1)
FunFact.text = mutableFactModel.getRandomFact().0
mutableFactModel.facts.append(mutableFactModel.getRandomFact().0)
} else {
FunFact.text = mutableFactModel.getRandomFact().0
}
}
It doesn't quite work the way I want it to, any ideas on making it work or a whole new way of going about it (not getting the same string twice in a row)?
Shuffle your facts array. Each time the user press the button, take the first element then remove it. Shuffling an array is akin to sort it in random order:
var facts = [
"Ants stretch when they wake up in the morning.",
"Ostriches can run faster than horses.",
"Olympic gold medals are actually made mostly of silver.",
"etc..."
].sort { f in arc4random_uniform(1000) % 2 == 0 }
Keeping this question up to shame myself, but no help is necessary. I just didn't understand what was going on. I thought the simulator was crashing, but it turns out I'd somehow accidentally set a breakpoint, and that was what was causing it to return to Xcode.
Feel free to ignore, but maybe leaving this up will help someone else new to Xcode.
I'm in the process of learning Swift, and taking an introductory course from lynda.com. I have run into an error that I'm sure is due to the changes in Swift since the course was produced (the instructor is on Xcode 6.0.1 while I am using 6.4). I would love some help figuring it out so I can proceed.
The example project is building a simple calculator. The step I'm on is creating the code to take a button click (any of the numbers on the calculator's UI), and display it in the UILabel that is the calculator's display.
This is the function that is apparently causing the issue:
var valueString:String! = ""
#IBAction func tappedNumber(sender: UIButton) {
var str:String! = sender.titleLabel!.text
valueString = valueString.stringByAppendingString(str)
label.text = valueString
}
It doesn't show up as an error in Xcode, but when I run the simulator, when I click on one of the number buttons, the simulator crashes back to Xcode, and the line var str:String! = sender.titleLabel!.text is highlighted.
From the little bit I've already picked up, I'm guessing the error has to do with the way Swift changed how it deals with optionals. But I just don't know enough to be sure, or how to fix it.
Any help would be much appreciated!
Try this:
var str:String! = sender.titleLabel?.text
valueString and str do not need to be optionals if they are never nil.
Try:
var valueString: String = ""
#IBAction func tappedNumber(sender: UIButton) {
if let label = sender.titleLabel {
let str = label.text
valueString = valueString.stringByAppendingString(str)
}
label.text = valueString
}
I am making calculator in Swift. Stuck in backspace button. If user press wrong digit then backspace button would help to delete digit off the display.
Though I wrote dropLast function and works. It return appropriate result. How to use count method, don't understand the return type of count method.
#IBOutlet weak var display: UILabel!
#IBAction func backspace() {
//how to use count method to check collection of elements
//dropLast drop the last digit and display result
let dropedDigit = dropLast(display.text!)
display.text = dropedDigit
}
How about something like this:
private func dropLast(text: String) -> String {
let endIndex = advance(text.endIndex, -1)
return text.substringToIndex(endIndex)
}
It calculates the index where you want to make the cut (endIndex of text - 1) and then returns the substring to this index. This function should drop the last character.
I am not using count method here, but for you reference Swift 1.2 introduces count(<#x: T#>) method that calculates length of sets including Strings.
I know this thread is outdated, but I just went through the process of making this work, myself, in Swift 2.2, and figured I could help answer it.
#IBAction func delButton(sender: AnyObject) {
if display.text != nil {
var tempString = Array(display.text!.characters)
tempString.removeLast(1)
display.text = ""
for num in 0..<tempString.count {
display.text = display.text! + String(tempString[num])
}
}
}
Basically, we're checking to see that the display label has stuff in it, so we don't throw an error, and if so, making a variable in the scope of the function to hold the label's characters individually in a string. After that, we remove the last character from the array, clear out the label to ensure we aren't adding what's already there to our new values, then iterating through the updated array of characters and adding it to the label.
It's important to note that we are casting the values contained in the array as String, because they've been put into the array as character values, which operate differently than the string value the label is expecting.
Like I said, I know the thread is a little out of date, but I've been going through courses in Swift, and have discovered that while there is a plethora of information out there for Objective-C, there is perilously little information out there for how to do a lot of those things in Swift. Since the language is being updated repeatedly, I've noticed a growing divide between the two languages.