微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

【译】哥们儿,我的方法哪儿去了?

原文链接Dude,Where's my Call?
译文原链:【译】哥们儿,我的方法哪儿去了?

想象有一天你正在给 Swift 编译器喂一些看起来无害的代码

// xcrun -sdk macosx swiftc -emit-executable cg.swift

import CoreGraphics

let path = CGPathCreateMutable()
CGPathMovetoPoint(path,nil,0.0,23.0)

然后一个冲击波打来:

cg.swift:7:12: error: 'CGPathCreateMutable()' has been replaced by 'CGMutablePath.init()'
<unkNown>:0: note: 'CGPathCreateMutable()' has been explicitly marked unavailable here
cg.swift:8:1: error: 'CGPathMovetoPoint' has been replaced by instance method 'CGMutablePath.moveto(_:x:y:)'
<unkNown>:0: note: 'CGPathMovetoPoint' has been explicitly marked unavailable here

它们哪儿去了?被重命名了。

Swift 3 一个重大的特性就是由 Swift-Evolution 提议 SE-0005 (Better Translation of Objective-C APIs Into Swift)SE-0006 (Apply API Guidelines to the Standard Library) 带来的”超级重命名“,这次超级重命名重命名了 C 和 Objective-C API 中的一些方法以给它们一种更 Swift 的感觉。Xcode 里面有一个移植器会将你的 Swift 2 代码转换成新的风格。它会执行很多机械的改变,给你留一些由于其他语言改变需要扫尾的工作,例如移除 C 的 for 循环

有一些重命名相当轻微,比如 NSView 中的这个:

// Swift 2
let localPoint = someView.convertPoint(event.locationInWindow,fromView: nil)

// Swift 3
let localPoint = someView.convert(event.locationInWindow,from: nil)

在这里 Point方法名里移除了。你知道自己正在处理一个 point,所以没必要重复这一事实。fromView 重命名为了 from 因为 View 只是提供了冗余的类型信息,并没有让这个调用更清楚。

其他的改变更大一些,比如 Core Graphics:

// Swift 2 / (Objective-C)
let path = CGPathCreateMutable()
CGPathMovetoPoint (path,points[i].x,points[i].y)
CGPathAddLinetoPoint (path,points[i + 1].x,points[i + 1].y)
CGContextAddpath (context,path)
CGContextstrokePath (context)

// Swift 3
let path = CGMutablePath()
path.move (to: points[i])
path.addLine (to: points[i + 1])

context.addpath (path)
context.strokePath ()

喔噢。这变化太大了。这个 API 现在看起来就是让人喜欢的 Swift 风格 API 而不是旧式的 C API。Apple 在 Swift 里面完全改变了 Core Graphics API (还有 GCD)以让它们更好用。你在 Swift 3 里不能再用老式的 CG C 风格的 API,因此你需要开始习惯新的风格。我已经将 GrafDemo (我这些 Core Graphics 博文的示例程序) 在自动翻译器中跑过(两次)了。你可以在这pull 请求中看到 Swift 3 第一个版本前后的变化,在这pull 请求中看到 Xcode8b6 的 Swift 3 版本前后变化。

他们干什么了?

Core Graphics API 就是一堆全局变量和全局自由方法。就是说,方法并不是直接和某些比如说类或者结构体这样的实例绑定的。用 CGContextAddArcToPoint 来操作 CGContext 仅仅是一个传统,不过你传进去一个 CGColor 也不会有人拦着你。无非就是会在运行时爆炸而已。只是在 C 风格的面向对象你才有一个隐晦类型作为第一个参数传过去,作为某种神奇饼干。CGContext* 方法需要一个 CGContextRefCGColor* 方法需要一个 CGColorRef

通过一些编译器的魔法,Apple 将这些隐晦引用转成了类,并且添加了一些方法给这些类以将其映射到 C API。当编译器看到类似这样的东西时:

let path = CGMutablePath()
path.addLines(between: self.points)
context.addpath(path)
context.strokePath()

实际上,在背后,正在发出这一系列调用

let path = CGPathCreateMutable()
CGPathAddLines(path,self.points,self.points.count)
CGContextAddpath(context,path)
CGContextstrokePath(context)

“新的”类

以下是已经接受 Swift 3.0 治疗的常见的隐晦类型 (忽略了一些专用的类型比如 CGdisplayMode 或者 CGEvent),还有一两个作为代表的方法

  • CGAffineTransform - translateBy(x:30,y:50),rotate(by: CGFloat.pi / 2.0)

  • CGPath / CGMutablePath - contains(point,using: evenOdd),.addRelativeArc(center: x,radius: r,startAngle: sa,delta: deltaAngle)

  • CGContext - context.addpath(path),context.clip(to: cgrectArray)

  • CGBitmapContext (folded in to CGContext) - let c = CGContext(data: bytes,width: 30,height: 30,bitsPerComponent: 8,bytesPerRow: 120,space: colorspace,bitmapInfo: 0)

  • CGColor - let color = CGColor(red: 1.0,green: 0.5,blue: 0.333,alpha: 1.0)

  • CGFont - let font = CGFont("Helvetica"),font.fullName

  • CGImage - image.masking(imageMask),image.cropping(to: rect)

  • CGLayer - let layer = GCLayer(context,size: size,auxilaryInfo: aux),layer.size

  • CGPDFContext (folded in to CGContext) / CGPDFDocument - context.beginpdfpage(pageInfo)

CGRectCGPoint 在 Swift 3 之前早已有了一些很不错的扩展。

怎么做到的?

编译器有一个内置的语法转换器,它将 Objective-C 的明明风格转换成更 Swift 些的形式。去掉重复的单词和那些仅仅是重复类型信息的单词。还去掉了一些之前是在方法调用左括号之前的单词并将它们移到括号里面作为参数标签。通过这样自动清理了一大堆调用方法

当然,人类喜欢搞一些微妙复杂的言辞,因此在 Swift 编译器里有一个允许手动重写自动翻译器翻译的部分的机制。这是具体的实现了(别在输出产品时依靠他们),不过他们提供了深入了解用于让现存 API 出现在 Swift 中所做的那些工作的机会。

其中一个涉及到的机制是 ”overlay“,它是当你引入一个框架或者 C 库时编译器引用的第二个库。Swift Lexicon 将 overlay 形容为”当库在系统中不发被修改时在系统中增强和扩大这个库“。一些一直都存在很棒的 CGRectCGPoint 扩展,例如someRect.divide(30.0,fromEdge: .MinXEdge),怎么来的?他们来自 overlay。工具链想啊”噢,我看到你在链接 Core Graphics。让我再加点方便方法吧。“

还有另外一个机制,apinotes,特别是 CoreGraphics.apinotes,一字一词地控制着 Core Graphics 中地命名和可见性。

例如,在 Swift 中像 CGRectMake 这样用来初始化基础结构体的调用没有作用,因为已经有它们的初始化方法了。所以就让这些调用方法不可用了:

# The below are inline functions that are irrelevant due to memberwise inits
- Name: CGPointMake
  Availability: nonswift
- Name: CGSizeMake
  Availability: nonswift
- Name: CGVectorMake
  Availability: nonswift
- Name: CGRectMake
  Availability: nonswift

然后还有其他的映射——如果你在 Swift 中看到这个,那就调用那个方法

# The below are fixups that inference didn't quite do what we wanted,and are
# pulled over from what used to be in the overlays
- Name: CGRectIsNull
  SwiftName: "getter:CGRect.isNull(self:)"
- Name: CGRectIsEmpty
  SwiftName: "getter:CGRect.isEmpty(self:)"

如果编译器看到了比如 rect.isEmpty() 这样的东西,它会发送一个请求给 CGRectIsEmpty

以下还是一些方法功能重命名

# The below are attempts at providing better names than inference
- Name: CGPointApplyAffineTransform
  SwiftName: CGPoint.applying(self:_:)
- Name: CGSizeApplyAffineTransform
  SwiftName: CGSize.applying(self:_:)
- Name: CGRectApplyAffineTransform
  SwiftName: CGRect.applying(self:_:)

当编译器看到 rect.applying(transform),它就知道调用 CGRectApplyAffineTransform

编译器只能自动重命名 Objective-C API,因为其遵循良好的系统命名法。C API (比如 Core Graphics)需要通过 overlay 和 apinote 来实现。

你能做什么

你可以通过 NS_SWIFT_NAME 做一些类似 apinote 机制的事情。你可以用这个宏来注释 C/Objective-C 头文件,表示在 Swift 里要用那个名字。编译器会对你的 NS_SWIFT_NAME 采用同样的替换(”如果看到 X,就调用 Y“)。

例如,这是一个 Intents(Siri) 框架中的调用

- (void)resolveWorkoutNameForEndWorkout:(INEndWorkoutIntent *)intent
                         withCompletion:(void (^)(INSpeakableStringResolutionResult *resolutionResult))completion
     NS_SWIFT_NAME(resolveWorkoutName(forEndWorkout:with:));

从 Objective-C 中调用它的话看起来是这样:

NSObject<INEndWorkoutIntentHandling> *workout = ...;

[workout resolveWorkoutNameForEndWorkout: intent  withCompletion: ^(INSpeakableStringResolutionResult) {
     ...
}];

而在 Swift 中是这样:

let workout: INEndWorkoutIntentHandling = ...
workout.resolveWorkoutName(forEndWorkout: workout) {
    response in
    ...
}

NS_SWIFT_NAME,和 Objective-C 中的轻量级泛型,nullability 注释,以及 Swift 编译器中的自动 Objective-C API 重命名一起,可以让你立刻有一种接口都回到 Swift 世界中的感觉。

使用自制的 overlay 和 apinote 是可以的,但那些原本是在 Swift 和 Apple 的 SDK 结合在一起时用的。你可以在你自己的框架中分发 apinote,但是 overlay 需要从 Swift 编译器树中编译。

为了自己创建更 Swift 的 API,你必须尽可能地做好头文件旁听(比如添加 nullability 注释和 NS_SWIFT_NAME),然后在你的项目中放一些 Swift 文件来伪造 overlay 以覆盖任何多余情况。这些 ”overlay” 文件在有 ABI 稳定性前都需要作为源文件传送。

轻掠过 iOS 10 头文件,看起来新的 API 喜欢用 NS_SWIFT_NAME,而老一点的更久远一些的 API 用 apinote。这样有一些道理因为这些头文件是在不同 Swift 版本中共享的,而给更久远的头文件可能添加新的 NS_SWIFT_NAME 可能会在编译器未改变的情况下破坏当前的代码。而且,apinote 可以由编译器团队或者社区成员添加,而头文件的改变需要拥有这个头文件的团队的注意。而那个团队可能已经准备好正要发布他们的功能了。

它好吗?

Swift 3 版本的 Core Graphics 绝对是更优秀更加 Swift 化。老实说,我也想在 Objective-C 上这样用。你可能因此失掉一些可 Google 性,并且需要当你在 Stack Overflow 的文章或者网上的教程中看到现有的 CG 代码时做一些脑内转换。不过那也不必这些日子普通的 Swift 代码所需的脑力运动多多少。

有一些由于 CG 类似 OO 本质及其如何进入 Swift 中带来的 API 的不协调。在这CoreGraphics.apinotes 中:

- Name: CGBitmapContextGetWidth
  SwiftName: getter:CGContext.width(self:)
- Name: CGPDFContextBeginPage
  SwiftName: CGContext.beginpdfpage(self:_:)

CGBitmapContextCGPDFContext 方法都被 CGContext 偷去了。这意味着你可以对任何 CGContext 要它的宽度,或者叫它开始一个 PDF 页面。如果你找一个非位图 context 要它的宽,你会得到这样的运行时错误

<Error>: CGBitmapContextGetWidth: invalid context 0x100e6c3c0.
If you want to see the backtrace,please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.

因此即使这个 API 非常 Swift 化了,编译器并不能捕获某些类型的 API 错用。Xcode 会高高兴兴地给你其实实际上不合适的方法补全。某种意义上来说,C API 更安全一点,因为 CGBitmapContextGetWidth 很清楚地告诉你它要的是一个位图 context 即使第一个参数从技术上来说就还是一个 CGContextRef。我希望这仅仅是一个 bug (rdar://27626070)。

如果你想了解更多想超级重命名以及像 NS_SWIFT_NAME 这样的工具,看看这个吧 WWDC 2016 Session 403 - iOS API Design Guidelines

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。