In Objective-C, you can print the call stack by doing the following:
NSLog(#"%#", [NSThread callStackSymbols]);
How do you do this in Swift without using Foundation class?
As Jacobson says, use the following:
Swift 2:
print(NSThread.callStackSymbols())
Swift 3 / Swift 4:
print(Thread.callStackSymbols)
That's Swift code. It's using a Foundation method, but so does 90%+ of what you do on iOS.
EDIT:
Note that the formatting looks better if you use:
Thread.callStackSymbols.forEach{print($0)}
From the debugger command line you can type
e Thread.callStackSymbols.forEach{print($0)}
For Swift 3 use:
print(Thread.callStackSymbols)
or for better formatting
for symbol: String in Thread.callStackSymbols {
print(symbol)
}
This improves the output a little.
for symbol: String in NSThread.callStackSymbols() {
NSLog("%#", symbol)
}
print(Thread.callStackSymbols.joined(separator: "\n"))
With this code, one can see the calls in different lines each.
1 MyApp 0x0000000100720780 $s9MyAppModule....
2 CoreFoundation 0x0000000181f04c4c EA9C1DF2-94C7-379B-BF8D-970335B1552F + 166988
3 CoreFoundation 0x0000000181f99554 EA9C1DF2-94C7-379B-BF8D-970335B1552F + 775508
4 CoreFoundation 0x0000000181f6eb34 EA9C1DF2-94C7-379B-BF8D-970335B1552F + 600884
5 CoreFoundation 0x0000000181f19754 _CFXNotificationPost + 696
6 Foundation 0x0000000183634138 86D8A58D-B71F-34C6-83E0-014B2B835F1D + 106808
Here's a great utility class I found on github:
https://github.com/nurun/swiftcallstacktrace
You get a tuple (class,method) of any stack trace symbol so you can do a clean printout.
CallStackAnalyser.classAndMethodForStackSymbol(NSThread.callStackSymbols()[2])
Edit: swift 4.1 update
https://github.com/GDXRepo/CallStackParser
I needed to write the callstack to a log file so I tweaked it like so.
var ErrorStack = String()
Thread.callStackSymbols.forEach {
print($0)
ErrorStack = "\(ErrorStack)\n" + $0
}
Thread.callStackSymbols() is nice to have. But the traceback is ugly. Demangling would be nice. The Swift 4.1+ demangler linked in #MikeS's answer is extremely comprehensive and impressive, but it's also over 4000 lines of code, overkill if you just need app, class and method, and it's quite a lot to add to a project, which I'd prefer to not to risk forgetting not to ship my app with :-)
This is a quick prototype of something that does some basic demangling of appname, class and method (which are the easy part to figure out). It's not polished. For example, it doesn't check for nil/failures in the regex ops, since it just gets a line from the callstack, which should be consistent enough to avoid problems. However, improved versions of it are welcome answers.
I added it to a class I named Debug, where I keep other debugging stuff, and invoke it from wherever in my app as:
Debug.whence()
... note: "where" is a Swift reserved word, and whence means basically the same thing.
It prints a line of this form (only one line, not full stack):
EventEditorViewController.configureNavigationItem():419 I'll probably add an argument to take an optional object arg and then do a refined display of the object and its address without some of the parameters and syntax swift's builtin obj dump logging does, so that it would display obj info and where it is being traced.
This probably can only parse lines inside the app. It probably can't demangle non-Swift calls (like Foundation), not sure. If you need a more comprehensive demangler, check #MikeS's answer.
static func whence(_ lineNumber: Int = #line) {
func matchRegex(_ matcher: String, string : String) -> String? {
let regex = try! NSRegularExpression(pattern: matcher, options: [])
let range = NSRange(string.startIndex ..< string.endIndex, in: string)
guard let textCheckingResult = regex.firstMatch(in: string, options: [], range: range) else {
return nil
}
return (string as NSString).substring(with:textCheckingResult.range(at:1)) as String
}
func singleMatchRegex(_ matcher: String, string : String) -> String? {
let regex = try! NSRegularExpression(pattern: matcher, options: [])
let range = NSRange(string.startIndex ..< string.endIndex, in: string)
let matchRange = regex.rangeOfFirstMatch(in: string, range: range)
if matchRange == NSMakeRange(NSNotFound, 0) {
return nil
}
return (string as NSString).substring(with: matchRange) as String
}
var string = Thread.callStackSymbols[1]
string = String(string.suffix(from:string.firstIndex(of: "$")!))
let appNameLenString = matchRegex(#"\$s(\d*)"#, string: string)!
let appNameLen = Int(appNameLenString)!
string = String(string.dropFirst(appNameLenString.count + 2))
let appName = singleMatchRegex(".{\(appNameLen)}", string: string)!
string = String(string.dropFirst(appNameLen))
let classNameLenString = singleMatchRegex(#"\d*"#, string: string)!
let classNameLen = Int(classNameLenString)!
string = String(string.dropFirst(classNameLenString.count))
let className = singleMatchRegex(".{\(classNameLen)}", string: string)!
string = String(string.dropFirst(classNameLen))
let methodNameLenString = matchRegex(#".(\d*)"#, string: string)!
let methodNameLen = Int(methodNameLenString)!
string = String(string.dropFirst(methodNameLenString.count + 1))
let methodName = singleMatchRegex(".{\(methodNameLen)}", string: string)!
let _ = appName
print("\(className).\(methodName)():\(lineNumber)")
}
Related
I'm very, very new to Swift and admittedly struggling with some of its constructs. I have to work with a text file and do many manipulations - here's an example to illustrate the point:
let's say I have a text like this (multi line)
Mary had a little lamb
#name: a name
#summary: a paragraph of text
{{something}}
a whole bunch of multi-line text
x----------------x
I want to be able to do simple things like find the location of #name, then split it to get the name and so on. I've done this in javascript and it was pretty simple with the use of substr and the regex matches.
In swift, which is supposed to be swift and easy and what not, I'm finding this exceedingly confusing.
Can someone help with how one might do
Find the location of the start of a substring
Extract all text between from the end of a substring to the end of text
Sorry if this is trivial - but the Apple documentation feels very complicated, and lots of examples are years old. I can't also seem to find easy application of regex.
You can use string range(of: String) method to find the range of your string, get its upperBound and search for the end of the line from that position of the string:
Playground testing:
let sentence = """
Mary had a little lamb
#name: a name
#summary: a paragraph of text
{{something}}
a whole bunch of multi-line text
"""
if let start = sentence.range(of: "#name:")?.upperBound,
let end = sentence[start...].range(of: "\n")?.lowerBound {
let substring = sentence[start..<end]
print("name:", substring)
}
If you need to get the string from there to the end of the string you can use PartialRangeFrom:
if let start = sentence.range(of: "#summary:")?.upperBound {
let substring = sentence[start...]
print("summary:", substring)
}
If you find yourself using that a lot you can extend StringProtocol and create your own method:
extension StringProtocol {
func substring<S:StringProtocol,T:StringProtocol>(between start: S, and end: T, options: String.CompareOptions = []) -> SubSequence? {
guard
let lower = range(of: start, options: options)?.upperBound,
let upper = self[lower...].range(of: end, options: options)?.lowerBound
else { return nil }
return self[lower..<upper]
}
func substring<S:StringProtocol>(after string: S, options: String.CompareOptions = []) -> SubSequence? {
guard
let lower = range(of: string, options: options)?.upperBound else { return nil }
return self[lower...]
}
}
Usage:
let name = sentence.substring(between: "#name:", and: "\n") // " a name"
let sumary = sentence.substring(after: "#summary:") // " a paragraph of text\n\n{{something}}\n\na whole bunch of multi-line text"
You can use regular expressions as well:
let name = sentence.substring(between: "#\\w+:", and: "\\n", options: .regularExpression) // " a name"
You can do this with range() and distance():
let str = "Example string"
let range = str.range(of: "amp")!
print(str.distance(from: str.startIndex, to: range.lowerBound)) // 2
let lastStr = str[range.upperBound...]
print(lastStr) // "le string"
there. I am a beginner in Swift and am trying to convert an older program to Swift3. I´ve managed to fix a bunch of errors, but I cannot get this function to work.
fileprivate func extractEntitlements(_ entitlementData: Data) -> NSDictionary? {
var originalEntitlementsData = entitlementData
let xmlStart = "<?xml".data(using: String.Encoding.ascii, allowLossyConversion: true)
let bytes = (originalEntitlementsData as NSData).bytes
for i in 0...(originalEntitlementsData.count - xmlStart!.count) {
if memcmp((xmlStart! as NSData).bytes, bytes + i, Int(xmlStart!.count)) == 0 {
let end = originalEntitlementsData.count - i
**originalEntitlementsData = originalEntitlementsData.subdata(in: NSMakeRange(i, end))**
break;
}
}
return NSString(data: originalEntitlementsData, encoding: String.Encoding.ascii.rawValue)?.propertyList() as? NSDictionary
}
Here is the error I get:
There are a bunch of questions regarding this error, but I am not being successful implementing a solution. Any tips on how I should proceed?
Thanks guys!
Ranges are more complicated and simpler at the same time in swift.
You want subdata(in: start..<end), which makes a Range<Int>, which is the type you need. However, in this case start and end refer to the beginning and end indexes of the range, not the location and length as you pass to NSMakeRange().
As #jrturton already said, subdata(in:) takes a Range<Int> argument,
so it should be
originalEntitlementsData = originalEntitlementsData.subdata(in: i..<i+end)
in your case. But note that all the conversions to NSData, taking
the .bytes, explicit loop and memcmp are not needed if you
take advantage of the existing range(of:) method of Data:
var originalEntitlementsData = entitlementData
let xmlStart = "<?xml".data(using: .utf8)!
if let range = originalEntitlementsData.range(of: xmlStart) {
originalEntitlementsData = originalEntitlementsData.subdata(in: range.lowerBound..<originalEntitlementsData.endIndex)
// Alternatively:
// originalEntitlementsData.removeSubrange(0..<range.lowerBound)
}
What's the best way to go about removing the first six characters of a string? Through Stack Overflow, I've found a couple of ways that were supposed to be solutions but I noticed an error with them. For instance,
extension String {
func removing(charactersOf string: String) -> String {
let characterSet = CharacterSet(charactersIn: string)
let components = self.components(separatedBy: characterSet)
return components.joined(separator: "")
}
If I type in a website like https://www.example.com, and store it as a variable named website, then type in the following
website.removing(charactersOf: "https://")
it removes the https:// portion but it also removes all h's, all t's, :'s, etc. from the text.
How can I just delete the first characters?
In Swift 4 it is really simple, just use dropFirst(n: Int)
let myString = "Hello World"
myString.dropFirst(6)
//World
In your case: website.dropFirst(6)
Why not :
let stripped = String(website.characters.dropFirst(6))
Seems more concise and straightforward to me.
(it won't work with multi-char emojis either mind you)
[EDIT] Swift 4 made this even shorter:
let stripped = String(website.dropFirst(6))
length is the number of characters you want to remove (6 in your case)
extension String {
func toLengthOf(length:Int) -> String {
if length <= 0 {
return self
} else if let to = self.index(self.startIndex, offsetBy: length, limitedBy: self.endIndex) {
return self.substring(from: to)
} else {
return ""
}
}
}
It will remove first 6 characters from a string
var str = "Hello-World"
let range1 = str.characters.index(str.startIndex, offsetBy: 6)..<str.endIndex
str = str[range1]
print("the end time is : \(str)")
I have a string in Swift that looks like this:
["174580798","151240033","69753978","122754394","72373738","183135789","178841809","84104360","122823486","184553211","182415131","70707972"]
I need to convert it into an NSArray.
I've looked at other methods on SO but it is breaking each character into a separate array element, as opposed to breaking on the comma. See: Convert Swift string to array
I've tried to use the map() function, I've also tried various types of casting but nothing seems to come close.
Thanks in advance.
It's probably a JSON string so you can try this
let string = "[\"174580798\",\"151240033\",\"69753978\",\"122754394\",\"72373738\",\"183135789\",\"178841809\",\"84104360\",\"122823486\",\"184553211\",\"182415131\",\"70707972\"]"
let data = string.dataUsingEncoding(NSUTF8StringEncoding)!
let jsonArray = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(), error: nil) as! [String]
as the type [String] is distinct you can cast it forced
Swift 3+:
let data = Data(string.utf8)
let jsonArray = try! JSONSerialization.jsonObject(with: data) as! [String]
The other two answers are working, although SwiftStudiers isn't the best regarding performance. vadian is right that your string most likely is JSON. Here I present another method which doesn't involve JSON parsing and one which is very fast:
import Foundation
let myString = "[\"174580798\",\"151240033\",\"69753978\",\"122754394\",\"72373738\",\"183135789\",\"178841809\",\"84104360\",\"122823486\",\"184553211\",\"182415131\",\"70707972\"]"
func toArray(var string: String) -> [String] {
string.removeRange(string.startIndex ..< advance(string.startIndex, 2)) // Remove first 2 chars
string.removeRange(advance(string.endIndex, -2) ..< string.endIndex) // Remote last 2 chars
return string.componentsSeparatedByString("\",\"")
}
toArray(myString) // ["174580798", "151240033", "69753978", ...
You probably want the numbers though, you can do this in Swift 2.0:
toArray(myString).flatMap{ Int($0) } // [174'580'798, 151'240'033, 69'753'978, ...
which returns an array of Ints
EDIT: For the ones loving immutability and functional programming, have this solution:
func toArray(string: String) -> [String] {
return string[advance(string.startIndex, 2) ..< advance(string.endIndex, -2)]
.componentsSeparatedByString("\",\"")
}
or this:
func toArray(string: String) -> [Int] {
return string[advance(string.startIndex, 2) ..< advance(string.endIndex, -2)]
.componentsSeparatedByString("\",\"")
.flatMap{ Int($0) }
}
Try this. I've just added my function which deletes any symbols from string except numbers. It helps to delete " and [] in your case
var myString = "[\"174580798\",\"151240033\",\"69753978\",\"122754394\",\"72373738\",\"183135789\",\"178841809\",\"84104360\",\"122823486\",\"184553211\",\"182415131\",\"70707972\"]"
var s=myString.componentsSeparatedByString("\",\"")
var someArray: [String] = []
for i in s {
someArray.append(deleteAllExceptNumbers(i))
}
println(someArray[0]);
func deleteAllExceptNumbers(str:String) -> String {
var rez=""
let digits = NSCharacterSet.decimalDigitCharacterSet()
for tempChar in str.unicodeScalars {
if digits.longCharacterIsMember(tempChar.value) {
rez += tempChar.description
}
}
return rez.stringByReplacingOccurrencesOfString("\u{22}", withString: "")
}
Swift 1.2:
If as has been suggested you are wanting to return an array of Int you can get to that from myString with this single concise line:
var myArrayOfInt2 = myString.componentsSeparatedByString("\"").map{$0.toInt()}.filter{$0 != nil}.map{$0!}
In Swift 2 (Xcode 7.0 beta 5):
var myArrayOfInt = myString.componentsSeparatedByString("\"").map{Int($0)}.filter{$0 != nil}.map{$0!}
This works because the cast returns an optional which will be nil where the cast fails - e.g. with [, ] and ,. There seems therefore to be no need for other code to remove these characters.
EDIT: And as Kametrixom has commented below - this can be further simplified in Swift 2 using .flatMap as follows:
var myArrayOfInt = myString.componentsSeparatedByString("\"").flatMap{ Int($0) }
Also - and separately:
With reference to Vadian's excellent answer. In Swift 2 this will become:
// ...
do {
let jsonArray = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! [String]
} catch {
_ = error // or do something with the error
}
I'm having a problem with understand how I can work with substrings in Swift. Basically, I'm getting a JSON value that has a string with the following format:
Something
I'm trying to get rid of the HTML anchor tag with Swift so I'm left with Something. My thought was to find the index of every < and > in the string so then I could just do a substringWithRange and advance up to the right index.
My problem is that I can't figure out how to find the index. I've read that Swift doesn't support the index (unless you extend it.)
I don't want to add CPU cycles unnecessarily. So my question is, how do I find the indexes in a way that is not inefficient? Or, is there a better way of filtering out the tags?
Edit: Converted Andrew's first code sample to a function:
func formatTwitterSource(rawStr: String) -> String {
let unParsedString = rawStr
var midParseString = ""
var parsedString = ""
if let firstEndIndex = find(unParsedString, ">") {
midParseString = unParsedString[Range<String.Index>(start: firstEndIndex.successor(), end: unParsedString.endIndex)]
if let secondStartIndex = find(midParseString, "<") {
parsedString = midParseString[Range<String.Index>(start: midParseString.startIndex, end: secondStartIndex)]
}
}
return parsedString
}
Nothing too complicated. It takes in a String that has the tags in it. Then it uses Andrew's magic to parse everything out. I renamed the variables and made them clearer so you can see which variable does what in the process. Then in the end, it returns the parsed string.
You could do something like this, but it isn't pretty really. Obviously you would want to factor this into a function and possibly allow for various start/end tokens.
let testText = "Something"
if let firstEndIndex = find(testText, ">") {
let testText2 = testText[Range<String.Index>(start: firstEndIndex.successor(), end: testText.endIndex)]
if let secondStartIndex = find(testText2, "<") {
let testText3 = testText2[Range<String.Index>(start: testText2.startIndex, end: secondStartIndex)]
}
}
Edit
Working on this a little further and came up with something a little more idiomatic?
let startSplits = split(testText, { $0 == "<" })
let strippedValues = map(startSplits) { (s) -> String? in
if let endIndex = find(s, ">") {
return s[Range<String.Index>(start: endIndex.successor(), end: s.endIndex)]
}
return nil
}
let strings = map(filter(strippedValues, { $0 != "" })) { $0! }
It uses a little more functional style there at the end. Not sure I much enjoy the Swift style of map/filter compared to Haskell. But anyhow, the one potentially dangerous part is that forced unwrapping in the final map. If you can live with a result of [String?] then it isn't necessary.
Even though this question has been already answered, I am adding solution based on regex.
let pattern = "<.*>(.*)<.*>"
let src = "Something"
var error: NSError? = nil
var regex = NSRegularExpression(pattern: pattern, options: .DotMatchesLineSeparators, error: &error)
if let regex = regex {
var result = regex.stringByReplacingMatchesInString(src, options: nil, range: NSRange(location:0,
length:countElements(src)), withTemplate: "$1")
println(result)
}