Realm Swift adding object with String property having character count > 1874 is crashing - ios

I have a csv file with 200K rows.
each row contains 9 values, all the rows String character count is less than 2048 but 3 rows have character count equal to 4420. These rows indexes are 134481, 134482, 134483 respectively. Out of the 9 values in the row last string value is big.
When row 134481 is adding to realm or creating in realm, it get crashed at BpTree::create_root_from_mem with EXC_BAD_ACCESS(code=2, address=xxxxx)
Below is the swift code [swift version 2.2, xcode: 7.3, realmSwift: 1.1.0]...for adding the csv objects to realm database.
This code is running in a background queue. I tried skipping first 130k rows and even then it crashes exactly at row 134481. Only after reducing the string character count the object is added without crash.
class PackageObject:Object {
dynamic var id = ""
dynamic var packageBatch = ""
dynamic var packageCode = ""
dynamic var packageDescription = ""
dynamic var packageName = ""
dynamic var packagelocalName = ""
dynamic var packageNumber = ""
dynamic var packageBuild = ""
dynamic var packageSummary = ""
}
let filename = NSBundle.mainBundle().pathForResource("codes", ofType: "tsv")
if let realm = try? Realm(configuration: configuration), data = try? String.init(contentsOfFile: filename!) {
let block = 5000
var lineNumber = 0
print(realm.configuration.fileURL)
data.enumerateLines({ (line, stop) in
if lineNumber % block == 0 {
print("begin: ", lineNumber)
realm.beginWrite()
}
lineNumber += 1
var strings = line.componentsSeparatedByString("\t")
if lineNumber >= 134481 && lineNumber <= 134483 {
// strings[8] count is about 3805
// after reducing the count to 1874, the object
// is successfully added to the realm, else a crash is observed.
let count = strings[8].characters.count
print(count, line.characters.count)
let index = strings[8].endIndex.advancedBy(1931 - count)
strings[8].removeRange(index..<strings[8].endIndex)
}
let packageObj = PackageObject(value: strings)
realm.add(packageObj)
//realm.create(PackageObject.self, value:strings)
if lineNumber % block == 0 {
print("commit: ", lineNumber)
_ = try? realm.commitWrite()
}
})
}
// The last commitWrite is not handled...inside the block.
// for the below condition...
if lineNumber % block != 0 {
_ = try? realm.commitWrite()
}
The realm doc says a String property can be less than 16MB, but here its not even taking 10KB.
I don't find anything wrong with the string, it is just plain English text. Is this a bug or do I have to add objects in a different way.

If the string property is indexed, upgrading to 2.0 will probably fix this.
Older versions had a flaw in how the index was implemented that lead to it hitting a stack overflow when strings have sufficiently long common prefixes, and 2.0 changed how indexes are stored to remove the recursion.

Related

Function and array of strings using swift [duplicate]

This question already has answers here:
How to sort array of strings by length in reverse/descending order in Swift?
(2 answers)
Closed last year.
Good day everyone I want to create a function that takes an array of strings and return an array, sorted from shortest to longest but I'm getting a terminated by signal 4 error. I'm using an online swift compiler on my windows laptop if that somehow matters.
here's the code I wrote:
var siliconvalley = ["Google", "Apple", "Microsoft"]
var elementamount: Int = siliconvalley.count
var newarray: [String] = [] //new array created to store the newly sorted array
var a = siliconvalley[0].count // this variable was created to count the letters of the first string in the array
var temporary: String = "" // this was created to store the largest string so that I can use it to append the new array
func longestelement () -> [String] {
repeat {
if siliconvalley[1].count > a {
print (siliconvalley[1])
temporary = siliconvalley[1]
siliconvalley.remove(at:1)
}
else if siliconvalley[2].count > a {
print (siliconvalley[2])
temporary = siliconvalley[2]
siliconvalley.remove(at:2)
}
else {
print (siliconvalley[0])
temporary = siliconvalley[0]
siliconvalley.remove(at:0)
}
newarray.append(temporary)
elementamount = elementamount - 1
} while elementamount > 0
return newarray
}
print (longestelement())
You know swift has built-in sorting? You can do:
siliconvalley.sorted(by: {$0.count < $1.count})
and then if you just want the longest use .last
here's the issue:
while elementamount > 0
Consider rechecking the code for possible illogical loop termination condition.
P.S: elementamount is always greater than 0.

Pulling a random item from an array on a timer

I have an array of strings. I would like to display 3 unique items from this array randomly. Then every 5 seconds, one of the items gets replaced with another unique item (my idea here is adding an animation with a delay).
I can display the 3 strings, however sometimes they repeat, and the timer is not updating the factLabel label.
Here's my progress:
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
func randomFact() -> String {
let arrayCount = model.cancunFacts.count
let randomIndex = Int(arc4random_uniform(UInt32(arrayCount)))
return model.cancunFacts[randomIndex]
}
// Display the facts
func updateUI() {
Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(randomFact), userInfo: nil, repeats: true)
factLabel.text = randomFact() + " " + randomFact() + " " + randomFact()
}
How do I get the text to always update randomly, without the 3 facts repeating?
Create an array of indexes. Remove a random index from the array, use it to index into your strings. When the array of indexes is empty, refill it.
Here is some sample code that will generate random, non-repeating strings:
var randomStrings = ["Traitor", "Lord Dampnut", "Cheeto-In-Chief",
"F***face Von Clownstick", "Short-Fingered Vulgarian",
"Drumpf", "Der Gropenführer", "Pumpkin in a suit"]
var indexes = [Int]()
func randomString() -> String {
if indexes.isEmpty {
indexes = Array(0...randomStrings.count-1)
}
let index = Int(arc4random_uniform(UInt32(indexes.count)))
let randomIndex = indexes.remove(at: index)
return randomStrings[randomIndex]
}
for i in 1...100 {
print (randomString())
}
(Note that it may still generate repeating strings in the case when the array of indexes is empty and it needs to be refilled. You'd need to add extra logic to prevent that case.)
Version 2:
Here is the same code, modified slightly to avoid repeats when the array of indexes is empty and needs to be refilled:
var randomStrings = ["tiny-fingered", "cheeto-faced", "ferret-wearing", "sh*tgibbon"]
var indexes = [Int]()
var lastIndex: Int?
func randomString() -> String {
if indexes.isEmpty {
indexes = Array(0...randomStrings.count-1)
}
var randomIndex: Int
repeat {
let index = Int(arc4random_uniform(UInt32(indexes.count)))
randomIndex = indexes.remove(at: index)
} while randomIndex == lastIndex
lastIndex = randomIndex
return randomStrings[randomIndex]
}
for i in 1...10000 {
print (randomString())
}
Even though it's using a repeat...while statement, the repeat condition will never fire twice in a row, because you'll never get a repeat except right after refilling the array of indexes.
With that code, if there is a repeat, the selected string will be skipped on that pass through the array. To avoid that, you'd need to adjust the code slightly to not remove a given index from the array until you verify that it is not a repeat.
Version 3:
Version 2, above, will skip an entry if it picks a repeat when it refills the array. I wrote a 3rd version of the code that refills the array, removes the last item it returned so that it can't repeat, and then adds it back to the array once it's picked a random item. This third version will always return every item in the source array before refilling it and will also never repeat an item. Thus it's truly random with no bias:
import UIKit
var randomStrings = ["Traitor", "Lord Dampnut", "Cheeto-In-Chief",
"F***face Von Clownstick", "Short-Fingered Vulgarian",
"Drumpf", "Der Gropenführer", "Pumpkin in a suit"]
var indexes = [Int]()
var lastIndex: Int?
var indexToPutBack: Int?
func randomString() -> String {
//If our array of indexes is empty, fill it.
if indexes.isEmpty {
indexes = Array(0...randomStrings.count-1)
print("") //Print a blank line each time we refill the array so you can see
//If we have returned an item previously, find and remove that index
//From the refilled array
if let lastIndex = lastIndex,
let indexToRemove = indexes.index(of: lastIndex) {
indexes.remove(at: indexToRemove)
indexToPutBack = indexToRemove //Remember the index we removed so we can put it back.
}
}
var randomIndex: Int
let index = Int(arc4random_uniform(UInt32(indexes.count)))
randomIndex = indexes.remove(at: index)
//If we refilled the array and removed an index to avoid repeats, put the removed index back in the array
if indexToPutBack != nil{
indexes.append(indexToPutBack!)
indexToPutBack = nil
}
lastIndex = randomIndex
return randomStrings[randomIndex]
}
for i in 1...30 {
print (randomString())
}
Sample output:
Short-Fingered Vulgarian
F***face Von Clownstick
Pumpkin in a suit
Drumpf
Lord Dampnut
Traitor
Der Gropenführer
Cheeto-In-Chief
Der Gropenführer
Drumpf
Lord Dampnut
Short-Fingered Vulgarian
Cheeto-In-Chief
Pumpkin in a suit
Traitor
F***face Von Clownstick
Short-Fingered Vulgarian
F***face Von Clownstick
Drumpf
Traitor
Cheeto-In-Chief
Lord Dampnut
Pumpkin in a suit
Der Gropenführer
Lord Dampnut
Short-Fingered Vulgarian
Pumpkin in a suit
Cheeto-In-Chief
Der Gropenführer
F***face Von Clownstick
Your timer is calling random fact, which simply returns a fact and doesn't do anything. You should probably have some third method called initializeTimer that does the Timer.scheduledtimer, which you should take out of your updateUI method. That timer should call updateUI. This would fix your labels updating. Also you would call initializeTimer in your viewDidLoad instead of updateUI. As far as preventing the repetition of facts, Duncan C's idea of having a separate array that you remove items from as you set new random facts then fill back up when it's empty seems like a good idea.
It may be easiest to maintain two arrays, usedStrings and unusedStrings, of random strings, like this:
var unusedStrings: [String] = ["king", "philip", "calls", "out", "for", "good", "soup"]
var usedStrings: [String] = []
func randomString() -> String {
if unusedStrings.isEmpty() {
unusedStrings = usedStrings
usedStrings = []
}
let randomIndex = Int(arc4random_uniform(unusedStrings.count))
let randomString = unusedStrings[randomIndex]
usedStrings.append(randomString)
return randomString
}

retrieving id and incrementing it swift3

I have a id whose format is 1223-3939-ABC.1 and I would like to retrieve the last value i.e. 1 and increment it so now it looks like 1223-3939-ABC.2. But its possible that "1" is not there so in that case, I would like to append ".1"
I am trying to achieve this in Swift and here is my code:
var deviceId: String = "1234-ASCD-SCSDS.1"
if (deviceId != "") {
var id: [String] = deviceId.components(separatedBy: ".")
if let incrementedId: String = id.capacity > 1 ? deviceId.components(separatedBy: ".")[1] : "" {
if (incrementedId == "") {
//should append to id
var firstEle = deviceId.components(separatedBy: ".")[0]
firstEle.append(".")
firstEle.append("1")
deviceId = firstEle
} else {
// retrieve that id, convert to int, increment id, convert back to string and replace the old id with new id
let newId: Int = Int(deviceId.components(separatedBy: ".")[1])! + 1
deviceId = deviceId.replacingOccurrences(of: ".\\d", with: ".\(newId)", options: .regularExpression)
}
}
}
Not sure what I'm doing wrong?
Your regular expression for replacing is .\\d where . is actually any symbol. Replace it with \\.\\d and it will operate as expected.
You are referencing capacity but you need to reference count to understand an amount of components.
Based on documentation:
Capacity – the total number of elements that the array can contain without
allocating new storage.
There are several problems, such as
Wrong usage of capacity (as already said by Nikita),
Your code assumes that there is only a single dot, so that id
has exactly two elements.
Your code will crash if the dot is not followed by an integer.
The main problem is that
deviceId = deviceId.replacingOccurrences(of: ".\\d", with: ".\(newId)", options: .regularExpression)
replaces all occurrences of an arbitrary character followed by
any digit with ".\(newId)". It should probably be
deviceId = id[0] + ".\(newId)"
instead.
But the entire problem can be solved much easier:
Find the last occurrence of a dot.
Check if the part of the string following the dot can be converted to an integer.
If yes, replace that part by the increased integer, otherwise append .1
Both checks can be achieved with conditional binding, so that the
if-block is only executed if the device id already has a trailing
number:
var deviceId = "1234-ASCD-SCSDS.1"
if let pos = deviceId.range(of: ".", options: .backwards),
let id = Int(deviceId.substring(from: pos.upperBound)) {
deviceId = deviceId.substring(to: pos.upperBound) + String(id + 1)
} else {
deviceId = deviceId + ".1"
}
print(deviceId) // 1234-ASCD-SCSDS.2

Swift 3 For loop compare and change

Here is my code so far
var counter = 0
for i in 0...9 {
var val = NamePicker()
// array to find duplicates
var buttonValues = ["", "", "", "", "", "", "", "", "", ""] // array for button names
buttonValues.insert(val, at: counter)
print(buttonValues[counter])
counter += 1
}
This code is putting 10 string values into my array. What I would like to do is find a way to check each value in my array. for eample if my end result array is ["a","a","a","b","b","c","c","e","f","c"] I want to see if there is a triple of the same name(single and duplicates are fine). However if there is a triple I would like to change the 3rd value to another val from my NamePicker() function.
so with my array of
["a","a","a","b","b","c","c","e","f","c"]
there are 3 "a" and 3 "c", having two of the same is ok, i would like to change the 3rd to a new values and if the new value makes another triple it will change until there are no more triples.
so that array could possible have an end result of
["a","a","f","b","b","c","c","e","f","z"]
this is where the triples where changed.
Any help on how to do this efficiently?
Both options below asume that your NamePciker() function can generate at least 5 distinct values so there exists an array that satisfies your requirement.
Your requirement is better handled by not generating so many duplicates to begin with. If all you want is an array of names when each name cannot be repeated more than twice, try this:
var buttonValues = [String]()
var dict = [String: Int]()
while buttonValues.count < 10 {
let name = NamePicker()
let count = dict[name] ?? 0
guard count < 2 else { continue }
buttonValues.append(name)
dict[name] = count + 1
}
If you already have the array and want to correct it, do this:
var buttonValues = ["a","a","a","b","b","c","c","e","f","c"]
// Scan the array to tally how many times each name appears
var totalDict = [String: Int]()
buttonValues.forEach { totalDict[$0] = (totalDict[$0] ?? 0) + 1 }
// Now scan it again to update names that appear too many times
var runningDict = [String: Int]()
for (index, value) in buttonValues.enumerated() {
let count = runningDict[value] ?? 0
if count >= 2 {
while true {
let newValue = NamePicker()
let newTotal = (totalDict[newValue] ?? 0) + 1
if newTotal < 3 {
buttonValues[index] = newValue
totalDict[newValue] = newTotal
break
}
}
} else {
runningDict[value] = count + 1
}
}
Dictionary is the best way I think. Have the key be the character and the value be the count of that character. Your runtime will be O(n) since you only have to run through each input once. Here is an example:
let chars = ["a","a","a","b","b","c","c","e","f","c"]
var dict = [String: Int]()
for char in chars {
//If already in Dictionary, increase by one
if var count = dict[char] {
count += 1
dict[char] = count
} else {//else is not in the dictionary already, init with 1
dict[char] = 1
}
}
Output:
["b": 2, "e": 1, "a": 3, "f": 1, "c": 3]
Now I'm not sure how you want to replace the value that's the same character for a third time, but this is probably the best way to group the strings to determine which are over the limit.
Instead of inserting the wrong value and then checking if the values are correct, I would suggest to automatically create the correct array.
//array for button names
var buttonValues = Array<String>()
//tracks what value has been inserted how many times
var trackerDict = [String: Int]()
for i in 0...9 {
//we initialize a new variable that tells us if we found a valid value (if the value has not been inserted 2 times already)
var foundValidValue = false
while !foundValidValue{
var val = NamePicker()
//now we check if the value exists and if it is inserted less than 2 times
if let count = trackerDict[val] {
if count < 2 {
foundValidValue = true
}
}
//if we found the value, we can add it
if foundValidValue {
trackerDict[val] = (trackerDict[val] ?? 0) + 1
buttonValues.append(val)
}
//if we did not find it, we just run through the loop again
}
}
I added a dictionary because it is faster to keep track of the count in a dictionary than counting the number of occurrences in the array every time.

NSNetService dictionaryFromTXTRecord fails an assertion on invalid input

The input to dictionary(fromTXTRecord:) comes from the network, potentially from outside the app, or even the device. However, Apple's docs say:
... Fails an assertion if txtData cannot be represented as an NSDictionary object.
Failing an assertion leaves the programmer (me) with no way of handling the error, which seems illogic for a method that processes external data.
If I run this in Terminal on a Mac:
dns-sd -R 'My Service Name' _myservice._tcp local 4567 asdf asdf
my app, running in an iPhone, crashes.
dictionary(fromTXTRecord:) expects the TXT record data (asdf asdf) to be in key=val form. If, like above, a word doesn't contain any = the method won't be able to parse it and fail the assertion.
I see no way of solving this problem other than not using that method at all and implementing my own parsing, which feels wrong.
Am I missing something?
Here's a solution in Swift 4.2, assuming the TXT record has only strings:
/// Decode the TXT record as a string dictionary, or [:] if the data is malformed
public func dictionary(fromTXTRecord txtData: Data) -> [String: String] {
var result = [String: String]()
var data = txtData
while !data.isEmpty {
// The first byte of each record is its length, so prefix that much data
let recordLength = Int(data.removeFirst())
guard data.count >= recordLength else { return [:] }
let recordData = data[..<(data.startIndex + recordLength)]
data = data.dropFirst(recordLength)
guard let record = String(bytes: recordData, encoding: .utf8) else { return [:] }
// The format of the entry is "key=value"
// (According to the reference implementation, = is optional if there is no value,
// and any equals signs after the first are part of the value.)
// `ommittingEmptySubsequences` is necessary otherwise an empty string will crash the next line
let keyValue = record.split(separator: "=", maxSplits: 1, omittingEmptySubsequences: false)
let key = String(keyValue[0])
// If there's no value, make the value the empty string
switch keyValue.count {
case 1:
result[key] = ""
case 2:
result[key] = String(keyValue[1])
default:
fatalError()
}
}
return result
}
I'm still hoping there's something I'm missing here, but in the mean time, I ended up checking the data for correctness and only then calling Apple's own method.
Here's my workaround:
func dictionaryFromTXTRecordData(data: NSData) -> [String:NSData] {
let buffer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(data.bytes), count: data.length)
var pos = 0
while pos < buffer.count {
let len = Int(buffer[pos])
if len > (buffer.count - pos + 1) {
return [:]
}
let subdata = data.subdataWithRange(NSRange(location: pos + 1, length: len))
guard let substring = String(data: subdata, encoding: NSUTF8StringEncoding) else {
return [:]
}
if !substring.containsString("=") {
return [:]
}
pos = pos + len + 1
}
return NSNetService.dictionaryFromTXTRecordData(data)
}
I'm using Swift 2 here. All contributions are welcome. Swift 3 versions, Objective-C versions, improvements, corrections.
I just ran into this one using Swift 3. In my case the problem only occurred when I used NetService.dictionary(fromTXTRecord:) but did not occur when I switched to Objective-C and called NSNetService dictionaryFromTXTRecord:. When the Objective-C call encounters an entry without an equal sign it creates a key containing the data and shoves it into the dictionary with an NSNull value. From what I can tell the Swift version then enumerates that dictionary and throws a fit when it sees the NSNull. My solution was to add an Objective-C file and a utility function that calls dictionaryFromTXTRecord: and cleans up the results before handing them back to my Swift code.

Resources