is there any possibility to create a custom log function?
This is how the default println("hello world") looks like:
2015-03-04 18:33:55.788 MyApp[12345:671253923] Hello World
I would like to output something like:
18:33 MyClass > myFunc [line 1] Hello World
First, for the time, you can get the current hour and minute as String:
func printTime()->String{
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitHour | .CalendarUnitMinute, fromDate: date)
let hour = components.hour
let minutes = components.minute
return "\(hour):\(minutes)"
}
And for the function etc. you can use the Swift Literal Expressions __FILE__, __FUNCTION__ and __LINE__.
But you don't want to set it each time you want to log. So you could do something like that:
func prettyPrint(print: String, file:String = __FILE__, functionName: String = __FUNCTION__, line:Int = __LINE__) {
println("\(printTime()) \(file) > \(functionName) [line \(line)] \(print)")
}
You call prettyPrint like that:
prettyPrint("hey")
And you will get the following output:
/Path/To/Your/File/MyClass.swift > hello [line 81] hey
But as you only want the name of your class, you can remove the path with the following function:
func getFile(path:String = __FILE__)->String{
var parts = path.componentsSeparatedByString("/")
return parts[parts.count-1]
}
Or, as ChikabuZ mentioned in his answer you can directly check the class:
let className = NSStringFromClass(self.classForCoder).pathExtension
Final Function
And here the final function(s):
func getFile(path:String = __FILE__)->String{
var parts = path.componentsSeparatedByString("/")
return parts[parts.count-1]
}
func prettyPrint(print: String, functionName: String = __FUNCTION__, line:Int = __LINE__) {
println("\(printTime()) \(getFile()) > \(functionName) [line \(line)] \(print)")
}
func printTime()->String{
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitHour | .CalendarUnitMinute, fromDate: date)
let hour = components.hour
let minutes = components.minute
return "\(hour):\(minutes)"
}
And the result will be:
MyClass.swift > hello [line 81] hey
You should also note #emaloney's answer to this question. Specifically that
println()-based solutions result in output being captured by the Apple System Log (ASL).
Ideally switch to NSLog or a full blown logging system
You should make an extension to NSObject, something like this:
class MyClass: NSObject
{
func myFunc()
{
myPrint("Hello World")
}
}
extension NSObject
{
func myPrint(text: String)
{
let timeFormatter = NSDateFormatter()
timeFormatter.dateStyle = NSDateFormatterStyle.NoStyle
timeFormatter.timeStyle = NSDateFormatterStyle.ShortStyle
let time = timeFormatter.stringFromDate(NSDate())
let className = NSStringFromClass(self.classForCoder).pathExtension
let function = __FUNCTION__
let line = "\(__LINE__)"
let result = time + " " + className + " > " + function + " " + line + " " + text
println(result)
}
}
let myClass = MyClass()
myClass.myFunc()
Unfortunately, none of the println()-based solutions result in output being captured by the Apple System Log (ASL).
The ASL is the logging facility provided by the Mac OS and iOS that is used by NSLog() (and on a Mac is visible through the Console application). Because NSLog() uses ASL, log entries recorded by NSLog() will be visible through the device console. Messages logged through println() will not be captured in ASL, and as a result, provide no opportunity to go back to the console for diagnostic purposes after something has happened.
There are two big downsides to NSLog(), however: (1) you can't customize the output format, and (2) it does a lot of work on the calling thread and can therefore negatively impact performance.
CleanroomLogger is a new Swift-based open-source API for logging. If you're familiar with log4j or CocoaLumberjack, then you'll understand CleanroomLogger.
You can customize your own output format by providing your own LogFormatter implementation to ensure that your log messages are formatted exactly as you want.
For more information on CleanroomLogger, visit the GitHub project page.
Related
I have a txt text file. The text file contains the text attached to each date.
Example:
30.10.2022 : Hello Stack Overflow.
31.10.2022 : Hello programmers.
01.11.2022 : We are the best.
02.11.2022 : Swift, is the best programming language.
etc…
I need to bind each sentence to date and show only the sentence that corresponds to today's date.
Example:
If today is 30.10.2022 - show in textView only the result: Hello Stack Overflow. And the same for every day... If the date changes from 30.10.2022 to 31.10.2022, we will see the result in textView - Hello programmers!
I tried to split my text from file text to dictionary and got an error - Swift/ContiguousArrayBuffer.swift:575: Fatal error: Index out of range.
I want to note that yesterday everything was working, but today it is not working, despite the fact that I did not change the code. Got confused. I can't understand what I missed.
Code with error...
import Foundation
class NameDaysModel {
private var dataArray: [String] = []
func loadDictionary() -> Dictionary<String, String> {
let pathToFile = Bundle.main.path(forResource: "NameDays", ofType: "txt")
if let path = pathToFile {
let countriesString = try! String(contentsOfFile: path, encoding: String.Encoding.utf8)
dataArray = countriesString.components(separatedBy: "\n")
}
var dictionary = [String: String]()
for line in dataArray {
let components = line.components(separatedBy: " : ")
dictionary[components[0]] = components[1] // = Thread 1: Fatal error: Index out of range
}
return dictionary
}
private func getDate() -> String {
let date = NSDate()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd.MM.yyyy"
let str = dateFormatter.string(from: date as Date)
return str
}
func getNameDay() -> String {
let date = getDate()
let nameDays = loadDictionary()
return nameDays["\(date)"] ?? ""
}
}
Based on the error you're getting, it sounds like some of the lines in your text file don't contain the string " : ". In that case your components array from the line let components = line.components(separatedBy: " : ") would only contain 1 element: The entire line of text.
You need to either make sure your data always contains a " : " in every line or add checking code that makes sure each value of components contains at least 2 elements before attempting to index into components[1].
Last night I had to convert my Swift 2.3 code to Swift 3.0 and my code is a mess after the conversion.
In Swift 2.3 I had the following code:
let maxChar = 40;
let val = "some long string";
var startRange = val.startIndex;
var endRange = val.startIndex.advancedBy(maxChar, limit: val.endIndex);
let index = val.rangeOfString(" ", options: NSStringCompareOptions.BackwardsSearch , range: startRange...endRange , locale: nil)?.startIndex;
Xcode converted my code to this which doesn't work:
let maxChar = 40;
let val = "some long string";
var startRange = val.startIndex;
var endRange = val.characters.index(val.startIndex, offsetBy: maxChar, limitedBy: val.endIndex);
let index = val.range(of: " ", options: NSString.CompareOptions.backwards , range: startRange...endRange , locale: nil)?.lowerBound
The error is in the parameter range in val.rage, saying No '...' candidates produce the expected contextual result type 'Range?'.
I tried using Range(startRange...endRange) as suggestd in the docs but I'm getting en error saying: connot invoke initiliazer for type ClosedRange<_> with an arguement list of type (ClosedRange). Seems like I'm missing something fundametnal.
Any help is appreciated.
Thanks!
Simple answer: the fundamental thing you are missing is that a closed range is now different from a range. So, change startRange...endRange to startRange..<endRange.
In more detail, here's an abbreviated version of your code (without the maxChar part):
let val = "some long string";
var startRange = val.startIndex;
var endRange = val.endIndex;
let index = val.range(
of: " ", options: .backwards, range: startRange..<endRange)?.lowerBound
// 9
Now you can use that as a basis to restore your actual desired functionality.
However, if all you want to do is split the string, then reinventing the wheel is kind of silly:
let arr = "hey ho ha".characters.split(separator:" ").map{String($0)}
arr // ["hey", "ho", "ha"]
I'm writing ID3 tags to a file using AVMetaDataItem
var soundFileMetadata = [AVMetadataItem]()
soundFileMetadata.append(createMetadata(AVMetadataiTunesMetadataKeyArtist, "MyArtist")!)
soundFileMetadata.append(createMetadata(AVMetadataiTunesMetadataKeySongName, "MySong")!)
soundFileMetadata.append(createMetadata(AVMetadataiTunesMetadataKeyAlbum, "MyAlbum")!)
soundFileMetadata.append(createMetadata(AVMetadataiTunesMetadataKeyUserGenre, "MyGenre")!)
soundFileMetadata.append(createMetadata(AVMetadataiTunesMetadataKeyComposer, "MyComposer")!)
Here is the createMetadata convenience method:
func createMetadata(tagKey: String, _ tagValue: AnyObject?,
keySpace:String = AVMetadataKeySpaceiTunes) -> AVMutableMetadataItem? {
if let tagValue = tagValue {
let tag = AVMutableMetadataItem()
tag.keySpace = keySpace
tag.key = tagKey
tag.value = (tagValue as? String) ?? (tagValue as? Int)
return tag
}
return nil
}
I then tried to write also the year tag, with no success:
let comps = NSDateComponents()
comps.year = 2010;
let yearTag = AVMutableMetadataItem()
yearTag.keySpace = AVMetadataKeySpaceID3
yearTag.key = AVMetadataID3MetadataKeyYear
yearTag.value = NSCalendar.currentCalendar().dateFromComponents(comps)
soundFileMetadata.append(yearTag)
In this case I get this error:
FigMetadataCreateConverter signalled err=-12482 (kFigMetadataConverterError_UnsupportedFormat) (Unsupported format conversion) at /SourceCache/CoreMedia/CoreMedia-1562.238/Prototypes/Metadata/Converters/FigMetadataConverterCommon.c line 118
Note that this is a simple error printed in console, not an exception!
Also writing it as a String, as an Int o even a Float, leads me to the same error.
Same is for Track/Disc count, Track/Disc number tags.
First question is: how to write them?
I also have another question.
Currently I've an AVAudioRecorder, I found no way to write tags directly to the output file of the recorder, so I commit the recorder file, open it with AVURLAsset and re-export it with AVAssetExportSession:
self.recorder.stop()
let urlAsset = AVURLAsset(URL: srcSoundFileURL)
let assetExportSession: AVAssetExportSession! = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetPassthrough)
assetExportSession.outputFileType = AVFileTypeAppleM4A
assetExportSession.outputURL = tmpSoundFileURL
assetExportSession.metadata = soundFileMetadata
assetExportSession.exportAsynchronouslyWithCompletionHandler({
....
})
Second question is: is there any way to avoid this double-step action?
I've managed to add the year tag with your code with a few modifications:
let yearTag = AVMutableMetadataItem()
yearTag.keySpace = AVMetadataKeySpaceiTunes
yearTag.key = AVMetadataiTunesMetadataKeyReleaseDate
yearTag.value = "2123"
I couldn't make it work with the ID3 keys so I thought this could be the problem, and indeed it works with these iTunes keys. Also, the value has to be a String (or NSString), not a date object.
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)")
}
I'll keep this as short and simple as I can.
I have an NSDate extension that I use for a number of things throughout my app, but I have run into a problem with (strangely) a very simple function.
The relevant part of the extension is:
import Foundation
extension NSDate {
func nineAMMonday() -> NSDate {
let greg = NSCalendar.currentCalendar()
let componenets = NSDateComponents()
componenets.weekday = 2
componenets.hour = 9
componenets.minute = 0
componenets.second = 0
return greg.dateFromComponents(componenets)!
}
}
Its purpose is simply to return an NSDate which is referring to a Monday morning.
So far so good...
The issue is when I try to call this function, I get an error:
Missing Argument For Parameter #1 In Call
I read here https://stackoverflow.com/a/26156765/3100991 about someone who had a similar problem, however their code was long and confusing and I didn't understand the answer. I think it would be useful for the general community if a simple explanation and fix was provided.
Thanks for your help in advance!
Loic
I just try that (in the playground) and it works.
I think your error is in the call of your method.
since your method is an instance method you have to call it on an existing date object.
So try to call it like that:
import Foundation
extension NSDate {
func nineAMMonday() -> NSDate {
let greg = NSCalendar.currentCalendar()
let componenets = NSDateComponents()
componenets.weekday = 2
componenets.hour = 9
componenets.minute = 0
componenets.second = 0
return greg.dateFromComponents(componenets)!
}
}
// Call
var date = NSDate();
date.nineAMMonday()
But in your case the best thing to do is a class func.
import Foundation
extension NSDate {
class func nineAMMonday() -> NSDate {
let greg = NSCalendar.currentCalendar()
let componenets = NSDateComponents()
componenets.weekday = 2
componenets.hour = 9
componenets.minute = 0
componenets.second = 0
return greg.dateFromComponents(componenets)!
}
}
// Call
NSDate.nineAMMonday()