Swift:漂亮的 print() Pt.2

作者:Andyy Hope,原文链接,原文日期:2016-04-14
译者:SketchK;校对:Crystal Sun;定稿:CMB

时尚日志,由你做主

之前的文章中,我们讨论了在输出日志中使用 emojis 的好处,它可以帮助我们更好的去消化和吸收大量的信息,不过我提供的实现方式并不怎么样,没有足够多的例子供你将其应用在自己的代码中。

我将遵守之前的约定继续讨论这个话题,向你展示如何使用 emojis 来实现输出日志的功能,只需在 print 函数上再多花费一点儿工夫。

节省成本

在接下来的文章中,我会打破 Swift 的命名规范,这样做我可不缺理由。为了降低新方案的成本, 要在尽可能减少键盘敲击次数的情况下达到同样的目标,比如字母大小写和标题大小写的问题。不管怎么样,如果看到文章的最后,你还在为一些细节而纠结的话, 你绝对应该把它们改成你想要的样子。

介绍 log


enum log { }

这里使用枚举类型代替类或结构的原因很多。原因之一是,我们永远不需要实例化一个日志。选择枚举而不是函数,是想确保实现一个安全的日志输出方案。不用着急,一会你就会明白我所说的“安全”的含义了.

枚举成员与值关联


enum log {
    case ln(_ line: String)
    case url(_ url: String)
    case obj(_ any: AnyObject)
}

可能有些人还不知道 ln(line) 曾经在 swift 语言中出现过。 print() 在 Swift 2.0 之后替代了 println() , 且主要用于日志输出。我在这里举了一些例子来解释 log 枚举的可扩展性。

要先为每一个枚举值设置关联值,毕竟得现有东西才能输出日志吧?请注意,这里忽略了参数标签,因为已经使用参数名称来描述函数的参数了。

看一下目前的情况吧:


print(log.ln(“Hello World”))
// ln("Hello World")

print("Hello World")
// "Hello World"

嗯,看样子似乎是完成了。但这看起来并不是一个可以替代 print 的方案。主要原因有这些:

  • 还是要敲击很多次键盘

  • 除了原始信息外还有许多不必要的内容

  • 外表不怎么样

  • 没有一个 emojis

  • 千言万语,就一句:“这方案太糟糕了”

现在需要完善上面的五个问题, 以便实现之前定下的目标.

自定义运算符


postfix operator / { }

先假定你们大多数人在这之前都没有遇到过自定义运算符的需求。没关系,我也是最近才用上这个功能, 不过用的也不是太多.

要创建一个 postfix 后置运算符,展示的内容会出现在运算符的左侧,想让它出现在日志代码的后面, 只用敲击一次键盘就能实现。

选择 / 符号是因为它最接近注释符号但不会真正产生注释,另外它也是少数几个不用 shift 键来就可以直接打出来字符。

...感觉自己就像是政客,在不停的想办法减少实现预算。

实现


postfix func / (target: log) { 
    switch target {
    case ln(let line):
        log("✏️", line)
    case url(let url):
        log("?", url)
    case obj(let object):
        log("?", object)
}

这段代码看起来像声明,有代码主体(body), 增加了在约束,确保自定义操作符接收的参数是 log 枚举值,这在一定程度上符合我所说的的“安全代码”。“安全代码”这个概念还体现在使用枚举替代类或结构体,因为枚举中的 switch 一定会把所有情况都检查一遍。这样,每次想在输出日志中添加一个新的 emoji 时, 也必须将其添加到操作符中的 switch 语句里。


private func log<T>(emoji: String, _ object: T) {
    print(emoji + “ “ + String(object))
}

终于得到了这个看似非常简单 log 函数。它是一个私有函数,可不被 .swift 文件之外的任何东西访问到,另外它的第二个参数是一个泛型,这个参数能够接受任意类型的值。

正如你所看到的一样,这是一个相当简单的打印语句,把 emoji 表情和对象用空格连起来。

用起来


log.ln(“Pretty”)/
✏️ Pretty
log.url(url)/
? http://www.andyyhope.com
log.obj(date)/
? 2016–04–02 23:23:05 +0000
Maybe i should use a screenshot here instead?

现在我们有一个可以替代系统原有输出日志的方案了, 新方案只需要进行两次敲击,相比其他的日志输出工具,脱颖而出、畅快淋漓。不过这还没完呢...

性能提升

大多数的程序员都会有这么一个共识,就是调用 print 方法会降低 app 的性能。如果含有 print 的代码散落在程序的不同地方, 在 debug 的时候还是可以接受, 可是要把 app 上传到 AppStore 时,最好还是移除这些内容。

"你是在说,每一次我都必须在提交 App Store 前注释掉所有的 print 语句, 然后再在 debug 的时候把它们恢复回来么?" —— 你

预编译标识符

可以用 Xcode 给工程创建配置文件, 在默认情况下 Xcode 会为每个工程提供两个配置文件 : Debug 和 Release.

在使用模拟器或用 USB 连接真机时,默认模式是 debug,用手机打开从 AppStore 下载的 app 时,默认模式是 relsase。

把刚才写好的代码放入到用于标识 debug 状态的预编译标识符里, 这样就不用在每次打包的时候对这些代码进行注释/恢复/增加/删除等操作。相当于告诉编译器:“Hi,哥们,除了 release 模式下, 你都得运行这段代码!”

Build Settings

  1. 点击 Project Navigator 图标

  2. 点击 Project 名称

  3. 点击 Build Settings 按钮

  4. 搜索 “Compiler Flag”

  5. 展开 “Other C Flags”

  6. 点击 “+” 按钮

  7. 输入 “-D DEBUG”

最终把整个 print 函数放到预编译的标识符中。


private func log<T>(emoji: String, _ object: T) {
    #if DEBUG
        print(emoji + “ “ + String(object))
    #endif
}

哦了!现在只有在开发状态下 print 才会生效。把 build configuration scheme 设置改为Release,运行 app,这样就可以检测之前的操作是否成功,当然别忘了检测完把状态切回到 Debug 模式。

做一个支持 Carthage 或 Cocoapod 的框架

读到这, 也许会想:“这些个点子太好了, 如果 Andyy 将这些功能弄成一个...”,但事实是这样做并不好。

原因就是, 假如我提供了上面三种方式中的任意一种, 那么每当你想利用它做日志输出的时候,你就必须在 Swift 文件里引入这个库, 这样做实在太傻了, 还需要做一些额外的操作来管理它们。这也是为什么许多 NSLog 的替代品在 Objective-C 中表现不好的原因。


import Log // 这看起来像坨 ?

探索和把玩

我专门提供了一个 playground 文件,能够让你测试一下今天所读到的全部内容,如果想使用这个文件,只需要将 log.swift 文件拖入到工程中即可。另外在示例代码中会有一些额外的枚举值, 或许你会发现这些额外的例子对你的日常工作很有用, 如果是这样的话,拿走不谢!


示例代码可以在 Github 上下载到.

就像之前一样, 如果你喜欢今天读到的内容或者亲手实现了它,请记得发我一个 tweet。很渴望听到你们的声音, 让我很受用.

本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 http://swift.gghttp://swift.gg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
接着分析 (result (type_ident (component id='Bool' bind=Swift.(file).Bool))) (brace_stmt range=[re.swift:1:59 - line:14:1] (pattern_binding_decl range=[re.swift:2:5 - line:2:33] (pattern_named type='[UInt8]' 'b') Original init: (call_expr type='[UInt8]' location=re.swift:2:19 range=[re.swift:2:13 - line:2:33] nothrow (constructor_ref_call_expr type='(String.UTF8View) -> [UInt8]' location=re.swift:2:19 range=[re.swift:2:13 - line:2:19] nothrow (declref_expr implicit type='(Array<UInt8>.Type) -> (String.UTF8View) -> Array<UInt8>' location=re.swift:2:19 range=[re.swift:2:19 - line:2:19] decl=Swift.(file).Array extension.init(_:) [with (substitution_map generic_signature=<Element, S where Element == S.Element, S : Sequence> (substitution Element -> UInt8) (substitution S -> String.UTF8View))] function_ref=single) (argument_list implicit (argument (type_expr type='[UInt8].Type' location=re.swift:2:13 range=[re.swift:2:13 - line:2:19] typerepr='[UInt8]')) )) (argument_list (argument (member_ref_expr type='String.UTF8View' location=re.swift:2:29 range=[re.swift:2:21 - line:2:29] decl=Swift.(file).String extension.utf8 (declref_expr type='String' location=re.swift:2:21 range=[re.swift:2:21 - line:2:21] decl=re.(file).check(_:_:).encoded@re.swift:1:14 function_ref=unapplied))) )) Processed init: (call_expr type='[UInt8]' location=re.swift:2:19 range=[re.swift:2:13 - line:2:33] nothrow (constructor_ref_call_expr type='(String.UTF8View) -> [UInt8]' location=re.swift:2:19 range=[re.swift:2:13 - line:2:19] nothrow (declref_expr implicit type='(Array<UInt8>.Type) -> (String.UTF8View) -> Array<UInt8>' location=re.swift:2:19 range=[re.swift:2:19 - line:2:19] decl=Swift.(file).Array extension.init(_:) [with (substitution_map generic_signature=<Element, S where Element == S.Element, S : Sequence> (substitution Element -> UInt8) (substitution S -> String.UTF8View))] function_ref=single) (argument_list implicit (argument (type_expr type='[UInt8].Type' location=re.swift:2:13 range=[re.swift:2:13 - line:2:19] typerepr='[UInt8]')) )) (argument_list (argument (member_ref_expr type='String.UTF8View' location=re.swift:2:29 range=[re.swift:2:21 - line:2:29] decl=Swift.(file).String extension.utf8 (declref_expr type='String' location=re.swift:2:21 range=[re.swift:2:21 - line:2:21] decl=re.(file).check(_:_:).encoded@re.swift:1:14 function_ref=unapplied))) ))) (var_decl range=[re.swift:2:9 - line:2:9] "b" type='[UInt8]' interface type='[UInt8]' access=private readImpl=stored writeImpl=stored readWriteImpl=stored)
06-10

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值