对于 NSLog,有没有一种 Swift 替代方案(@“% s”,__PRETTY_FUNCION__)

在 Objective C 中,您可以使用以下方法记录被调用的方法:

NSLog(@"%s", __PRETTY_FUNCTION__)

这通常是从日志宏中使用的。

虽然 Swift 不支持宏(我认为) ,但我仍然希望使用一个通用的日志语句,其中包含被调用函数的名称。在斯威夫特可能吗?

更新: 我现在使用这个全局函数来记录日志,可以在这里找到: Https://github.com/evermeer/stuff#print 你可以用以下方法安装:

pod 'Stuff/Print'

密码如下:

public class Stuff {


public enum logLevel: Int {
case info = 1
case debug = 2
case warn = 3
case error = 4
case fatal = 5
case none = 6


public func description() -> String {
switch self {
case .info:
return "❓"
case .debug:
return "✳️"
case .warn:
return "⚠️"
case .error:
return "🚫"
case .fatal:
return "🆘"
case .none:
return ""
}
}
}


public static var minimumLogLevel: logLevel = .info


public static func print<T>(_ object: T, _ level: logLevel = .debug, filename: String = #file, line: Int = #line, funcname: String = #function) {
if level.rawValue >= Stuff.minimumLogLevel.rawValue {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy HH:mm:ss:SSS"
let process = ProcessInfo.processInfo
let threadId = "?"
let file = URL(string: filename)?.lastPathComponent ?? ""
Swift.print("\n\(level.description()) .\(level) ⏱ \(dateFormatter.string(from: Foundation.Date())) 📱 \(process.processName) [\(process.processIdentifier):\(threadId)] 📂 \(file)(\(line)) ⚙️ \(funcname) ➡️\r\t\(object)")
}
}
}

你可以这样使用:

Stuff.print("Just as the standard print but now with detailed information")
Stuff.print("Now it's a warning", .warn)
Stuff.print("Or even an error", .error)


Stuff.minimumLogLevel = .error
Stuff.print("Now you won't see normal log output")
Stuff.print("Only errors are shown", .error)


Stuff.minimumLogLevel = .none
Stuff.print("Or if it's disabled you won't see any log", .error)

这将导致:

✳️ .debug ⏱ 02/13/2017 09:52:51:852 📱 xctest [18960:?] 📂 PrintStuffTests.swift(15) ⚙️ testExample() ➡️
Just as the standard print but now with detailed information


⚠️ .warn ⏱ 02/13/2017 09:52:51:855 📱 xctest [18960:?] 📂 PrintStuffTests.swift(16) ⚙️ testExample() ➡️
Now it's a warning


🚫 .error ⏱ 02/13/2017 09:52:51:855 📱 xctest [18960:?] 📂 PrintStuffTests.swift(17) ⚙️ testExample() ➡️
Or even an error


🚫 .error ⏱ 02/13/2017 09:52:51:855 📱 xctest [18960:?] 📂 PrintStuffTests.swift(21) ⚙️ testExample() ➡️
Only errors are shown
28087 次浏览

Swift has #file, #function, #line and #column. From Swift Programming Language:

#file - String - The name of the file in which it appears.

#line - Int - The line number on which it appears.

#column - Int - The column number in which it begins.

#function - String - The name of the declaration in which it appears.

As of XCode beta 6, you can use reflect(self).summary to get the class name and __FUNCTION__ to get the function name, but things are a bit mangled, right now. Hopefully, they'll come up with a better solution. It might be worthwhile to use a #define until we're out of beta.

This code:

NSLog("[%@ %@]", reflect(self).summary, __FUNCTION__)

gives results like this:

2014-08-24 08:46:26.606 SwiftLessons[427:16981938] [C12SwiftLessons24HelloWorldViewController (has 2 children) goodbyeActiongoodbyeAction]

EDIT: This is more code, but got me closer to what I needed, which I think is what you wanted.

func intFromString(str: String) -> Int
{
var result = 0;
for chr in str.unicodeScalars
{
if (chr.isDigit())
{
let value = chr - "0";
result *= 10;
result += value;
}
else
{
break;
}
}


return result;
}




@IBAction func flowAction(AnyObject)
{
let cname = _stdlib_getTypeName(self)
var parse = cname.substringFromIndex(1)                                 // strip off the "C"
var count = self.intFromString(parse)
var countStr = String(format: "%d", count)                              // get the number at the beginning
parse = parse.substringFromIndex(countStr.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
let appName = parse.substringToIndex(count)                             // pull the app name


parse = parse.substringFromIndex(count);                                // now get the class name
count = self.intFromString(parse)
countStr = String(format: "%d", count)
parse = parse.substringFromIndex(countStr.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
let className = parse.substringToIndex(count)
NSLog("app: %@ class: %@ func: %@", appName, className, __FUNCTION__)
}

It gives output like this:

2014-08-24 09:52:12.159 SwiftLessons[1397:17145716] app: SwiftLessons class: ViewController func: flowAction

I prefer to define a global log function:

[Swift 3.1]

func ZYLog(_ object: Any?, filename: String = #file, line: Int = #line, funcname: String = #function) {
#if DEBUG
print("****\(Date()) \(filename)(\(line)) \(funcname):\r\(object ?? "nil")\n")
#endif
}

[Swift 3.0]

func ZYLog<T>(_ object: T?, filename: String = #file, line: Int = #line, funcname: String = #function) {
#if DEBUG
print("****\(Date()) \(filename)(\(line)) \(funcname):\r\(object)\n")
#endif
}

[Swift 2.0]

func ZYLog<T>(object: T, filename: String = __FILE__, line: Int = __LINE__, funcname: String = __FUNCTION__) {
println("****\(filename.lastPathComponent)(\(line)) \(funcname):\r\(object)\n")
}

the output is something like:

****ZYHttpSessionManager.swift(78) POST(_:parameters:success:failure:):
[POST] user/login, {
"auth_key" = xxx;
"auth_type" = 0;
pwd = xxx;
user = "xxx";
}


****PointViewController.swift(162) loadData():
review/list [limit: 30, skip: 0]


****ZYHttpSessionManager.swift(66) GET(_:parameters:success:failure:):
[GET] review/list, {
"auth_key" = xxx;
uuid = "xxx";
}

I use, this is all that is required in a swift file, all other files will pick it up (as a global function). When you want to release the application just comment out the line.

import UIKit


func logFunctionName(file:NSString = __FILE__, fnc:String = __FUNCTION__){
println("\(file.lastPathComponent):\(fnc)")
}

Or slight function modification with:

func logFunctionName(file:String = __FILE__, fnc:String = __FUNCTION__, line:(Int)=__LINE__) {
var className = file.lastPathComponent.componentsSeparatedByString(".")
println("\(className[0]):\(fnc):\(line)")

}

/* will produce an execution trace like: AppDelegate:application(_:didFinishLaunchingWithOptions:):18 Product:init(type:name:year:price:):34 FirstViewController:viewDidLoad():15 AppDelegate:applicationDidBecomeActive:62 */

Swift 4
Here's my approach:

func pretty_function(_ file: String = #file, function: String = #function, line: Int = #line) {


let fileString: NSString = NSString(string: file)


if Thread.isMainThread {
print("file:\(fileString.lastPathComponent) function:\(function) line:\(line) [M]")
} else {
print("file:\(fileString.lastPathComponent) function:\(function) line:\(line) [T]")
}
}

Make this a global function and just call

pretty_function()

Bonus: You will see the thread is executed on, [T] for a background thread and [M] for the Main thread.

Another way to log function call:

NSLog("\(type(of:self)): %@", #function)

Here is an updated Swift 2 answer.

func LogW(msg:String, function: String = __FUNCTION__, file: String = __FILE__, line: Int = __LINE__){
print("[WARNING]\(makeTag(function, file: file, line: line)) : \(msg)")
}


private func makeTag(function: String, file: String, line: Int) -> String{
let url = NSURL(fileURLWithPath: file)
let className:String! = url.lastPathComponent == nil ? file: url.lastPathComponent!
return "\(className) \(function)[\(line)]"
}

Example of use:

LogW("Socket connection error: \(error)")

Starting from Swift 2.2 we should use:

  • #file (String) The name of the file in which it appears.
  • #line (Int) The line number on which it appears.
  • #column (Int) The column number in which it begins.
  • #function (String) The name of the declaration in which it appears.

From The Swift Programming Language (Swift 3.1) at page 894.

func specialLiterals() {
print("#file literal from file: \(#file)")
print("#function literal from function: \(#function)")
print("#line: \(#line) -> #column: \(#column)")
}
// Output:
// #file literal from file: My.playground
// #function literal from function: specialLiterals()
// #line: 10 -> #column: 42

Swift 3.0

public func LogFunction<T>(object: T, filename: String = #file, line: Int = #line, funcname: String = #function) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy HH:mm:ss:SSS"
let process = ProcessInfo.processInfo()
let threadId = "?"
print("\(dateFormatter.string(from:Date())) \(process.processName) [\(process.processIdentifier):\(threadId)] \(filename)(\(line)) \(funcname)::: \(object)")
}

Swift 3.x+

If you don't want the entire file name then here's a quick fix for that.

func trace(fileName:String = #file, lineNumber:Int = #line, functionName:String = #function) -> Void {
print("filename: \(fileName.components(separatedBy: "/").last!) function: \(functionName) line: #\(lineNumber)")
}


filename: ViewController.swift function: viewDidLoad() line: #42

I use print("\(self.classForCoder)::\(#function)") to get closer to __PRETTY_FUNCTION__ - it's less elegant than one macro, but it is what it is.