余晖落尽暮晚霞,黄昏迟暮远山寻
本站
当前位置:网站首页 > 编程知识 > 正文

Swift 小贴士:语言的扩展和自定义(turbowarp自定义扩展)

xiyangw 2022-12-04 09:43 90 浏览 0 评论

作为一名软件工程师,好处之一就是如果我们对手上的工具不甚满意的话,我们可以自行对这个工具进行完善。Swift 让这个优化过程变得更为轻松,它提供了许多特性从而允许我们能够自然而然地扩展和自定义这门语言。

在本篇文章中,我打算为大家分享一系列 Swift 小贴士,以表述 Swift 是如何让我的生活更加美好的。我希望本文能够抛砖引玉,让您对这门语言有更深甚至更好的想法,并付诸行动(切记要三思而后行!)。

干掉重复的标识符

您可能已经熟悉了 Objective-C 中的一个使用惯例:那就是枚举值以及其字符常量通常都有长得吓人的描述名:

label.textAlignment?=?NSTextAlignmentCenter;

(这让我想起了从中学课上学习到的一条准则:在答案中要复述问题,简称 RQIA。问:在这个例子中是哪一个文本对齐方式呢?答:在这个例子中是居中文本对齐方式。这对老师批阅试卷来说是非常有效的一种做法,因为老师很可能记不住问题,但是在其他情况下这种做法会显得十分繁杂。)

Swift 减少了冗余度,因为枚举值可以在类型名之后加上点语法进行访问,即使你省略掉了类型姓名它仍然能够推断出来:

label.textAlignment?=?NSTextAlignment.Center

//?更为简洁:
label.textAlignment?=?.Center

然而,很多时候我们很可能不会用到枚举,遇上的往往是这样很长很长的一个构造器:

animation.timingFunction?=?CAMediaTimingFunction(name:?kCAMediaTimingFunctionEaseInEaseOut)

代码中会有多少个“timingFunction”?很可能多到无法想象。

有一个不为人知的小技巧,就是缩略形式的点语法对于所有类型的静态成员来说都是有效的。通过在扩展中增加自定义的属性就可以应用上这个技巧了……

extension?CAMediaTimingFunction
{
????//?这个属性是懒加载属性,第一次被访问时才会被初始化。
????//?(@nonobjc?标记是必须的,这可以阻止编译器试图为一个
????//??静态属性创建动态访问器(也就是令其不可继承)。
????@nonobjc?static?let?EaseInEaseOut?=?CAMediaTimingFunction(name:?kCAMediaTimingFunctionEaseInEaseOut)

????//?另一个方法就是使用计算性属性,这也同样有效,
????//?但是*每次*访问它的时候都将重新计算,可能带来性能问题:
????static?var?EaseInEaseOut:?CAMediaTimingFunction?{
????????//?.init?is?short?for?self.init
????????return?.init(name:?kCAMediaTimingFunctionEaseInEaseOut)
????}
}

这样我们就可以很方便地简化这个操作了:

animation.timingFunction?=?.EaseInEaseOut

上下文环境

处理 Core Graphics 上下文、色区之类的代码同样也会非常非常长:

CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext,
????CGColorCreate(CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB),?[0.792,?0.792,?0.816,?1]))

我们仍然使用万能的扩展:

extension?CGContext
{
????static?func?currentContext?->?CGContext??{
????????return?UIGraphicsGetCurrentContext
????}
}

extension?CGColorSpace
{
????static?let?GenericRGB?=?CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)
}

CGContextSetFillColorWithColor(.currentContext,
????CGColorCreate(.GenericRGB,?[0.792,?0.792,?0.816,?1]))

看起来要简单不少。当然,还有很多方法可以扩展 Core Graphics,从而让它符合您的需求。

自动布局

这是不是似曾相识?

spaceConstraint?=?NSLayoutConstraint(
????item:?label,
????attribute:?.Leading,
????relatedBy:?.Equal,
????toItem:?button,
????attribute:?.Trailing,
????multiplier:?1,?constant:?20)
widthConstraint?=?NSLayoutConstraint(
????item:?label,
????attribute:?.Width,
????relatedBy:?.LessThanOrEqual,
????toItem:?nil,
????attribute:?.NotAnAttribute,
????multiplier:?0,?constant:?200)

spaceConstraint.active?=?true
widthConstraint.active?=?true

非常难以阅读,是吧?苹果意识到这是一个很常见的问题,因此重新设计了新的 NSLayoutAnchor API(可以在 iOS 9 以及 OS X 10.11 上使用)来简化自动布局的构造:

spaceConstraint?=?label.leadingAnchor.constraintEqualToAnchor(button.trailingAnchor,?constant:?20)
widthConstraint?=?label.widthAnchor.constraintLessThanOrEqualToConstant(200)
spaceConstraint.active?=?true
widthConstraint.active?=?true

不过,我觉得我们可以做得更好。在我的设想当中,下面这行代码比内置的 API 更容易阅读和使用:

spaceConstraint?=?label.constrain(.Leading,?.Equal,?to:?button,?.Trailing,?plus:?20)
widthConstraint?=?label.constrain(.Width,?.LessThanOrEqual,?to:?200)

//?"让标签的左边缘和按钮的右边缘建立关联,距离为?20"
//?"让标签的宽度小于或等于?200"

我们可以对 UIView 或者 NSView 建立一对扩展,从而让这个设想成为可能。这些辅助方法可能看起来又臭又长,但是它们使用起来却十分方便,并且在可维护性上也是十分优越的。(这里我实际上包含了一些带有默认值的额外参数——比如 multiplierpriority以及identifier,因此你可以更好地对约束进行构建)。

extension?UIView
{
????func?constrain(
????????attribute:?NSLayoutAttribute,
????????_?relation:?NSLayoutRelation,
????????to?otherView:?UIView,
????????_?otherAttribute:?NSLayoutAttribute,
????????times?multiplier:?CGFloat?=?1,
????????plus?constant:?CGFloat?=?0,
????????atPriority?priority:?UILayoutPriority?=?UILayoutPriorityRequired,
????????identifier:?String??=?nil)
????????->?NSLayoutConstraint
????{
????????let?constraint?=?NSLayoutConstraint(
????????????item:?self,
????????????attribute:?attribute,
????????????relatedBy:?relation,
????????????toItem:?otherView,
????????????attribute:?otherAttribute,
????????????multiplier:?multiplier,
????????????constant:?constant)
????????constraint.priority?=?priority
????????constraint.identifier?=?identifier
????????constraint.active?=?true
????????return?constraint
????}
????
????func?constrain(
????????attribute:?NSLayoutAttribute,
????????_?relation:?NSLayoutRelation,
????????to?constant:?CGFloat,
????????atPriority?priority:?UILayoutPriority?=?UILayoutPriorityRequired,
????????identifier:?String??=?nil)
????????->?NSLayoutConstraint
????{
????????let?constraint?=?NSLayoutConstraint(
????????????item:?self,
????????????attribute:?attribute,
????????????relatedBy:?relation,
????????????toItem:?nil,
????????????attribute:?.NotAnAttribute,
????????????multiplier:?0,
????????????constant:?constant)
????????constraint.priority?=?priority
????????constraint.identifier?=?identifier
????????constraint.active?=?true
????????return?constraint
????}
}

你好啊,运算符

在我们使用自定义运算符之前,我必须要告诫大家:切记要三思而后行。运算符使用起来很简单,但是很可能最后会搞得一团糟。一步一个脚印,对自己代码不要过分自信,我确信您最终就可以找到自定义运算符的真正用处。

重载运算符

如果您要让一个元素可以拖动的话,那么很可能就要写如下所示的代码:

//?开始触摸?/?鼠标摁下:

let?touchPos?=?touch.locationInView(container)
objectOffset?=?CGPoint(x:?object.center.x?-?touchPos.x,?y:?object.center.y?-?touchPos.y)

//?手指拖动?/?鼠标移动:

let?touchPos?=?touch.locationInView(container)
object.center?=?CGPoint(x:?touchPos.x?+?objectOffset.x,?y:?touchPos.y?+?objectOffset.y)

这里我们仅仅只是做了很简单的加减法,但是由于 CGPoint 由 xy两个元素组成,因此我们必须要再次写下每个表达式。这里使用了某些便利函数来实现。

objectOffset代表了触摸位置和对象位置之间的距离。表述这个距离的最好方式实际上并不是 CGPoint,而是使用不常见的CGVector,它使用的是dxdy来表示距离,也就是所谓的“变量增量”。

因此,两点之间的减法操作会生成一个矢量是符合逻辑的,为了实现这个功能,我们需要重载 -运算符:

///?-?Returns:?由?`rhs`?指向?`lhs`?的一个向量
func?-(lhs:?CGPoint,?rhs:?CGPoint)?->?CGVector
{
????return?CGVector(dx:?lhs.x?-?rhs.x,?dy:?lhs.y?-?rhs.y)
}

然后反过来,我们向一个点上加上矢量将会生成另一个点:

///?-?Returns:?距离?`lhs`?点?`rhs`?矢距的新点
func?+(lhs:?CGPoint,?rhs:?CGVector)?->?CGPoint
{
????return?CGPoint(x:?lhs.x?+?rhs.dx,?y:?lhs.y?+?rhs.dy)
}

现在那堆代码就变得更加易懂了!

//?触控开始:
objectOffset?=?object.center?-?touch.locationInView(container)

//?手指拖动:
object.center?=?touch.locationInView(container)?+?objectOffset

练习:想想对于点和矢量来说还有哪些运算符可以用?找出并实现它们。建议:-(CGPoint, CGVector)*(CGVector, CGFloat)以及-(CGVector)

发挥创造力

还有一些东西更有创造性。Swift 提供了一系列 复合赋值运算符(compound assignment operators),同时执行算术操作以及赋值:

a?+=?b???//?等同于?"a?=?a?+?b"
a?%=?b???//?等同于?"a?=?a?%?b"

但是仍然有很多运算符没有内置的复合赋值形式。一个最常见的例子就是 ??,空合(nil-coalescing)运算符。就如同Ruby 的 ||=只有当变量为空(或者为 false )的时候才开始赋值。这对于 Swift 可选值来说是一个非常好的特性,并且添加起来十分简单:

infix?operator???=?{?associativity?right?precedence?90?assignment?}?//?匹配其他的赋值运算符

///?如果?`lhs`?为?`nil`,?那么就用?`rhs`?的值对其进行赋值
func???=(inout?lhs:?T?,?@autoclosure?rhs:??->?T)
{
????lhs?=?lhs????rhs
}

这可能让人望而却步——我们一点一点来进行分析:

  • infix operator声明告诉 Swift 将??=视为一个运算符。

  • 通过 为函数添加泛型,这样可以处理所有类型的值。

  • inout允许它修改左操作数。

  • @autoclosure将开启短路求值(short-circuit evaluation)功能,如果可以的话只对右操作数进行求值(这个同样也依赖于??本身的短路求值功能)。

最后,结果如我所愿,简洁明了,易于使用:

a???=?b???//?相当于?"a?=?a????b"

调度队列

注意:关于如何让 Swift 恰当地使用 GCD(Grand Central Dispatch) 可能需要为其专门写一篇文章,但是这里我会尽可能将基础部分说完。你可以在这个Gist上找到更多的想法。

Swift 2 引入了协议扩展,因此之前许多全局标准库函数变成了准成员函数,比如说map(seq, transform)现在变成了seq.map(transform)join(separator, seq)现在变成了seq.joinWithSeparator(separator)等等。因此,这些理论上不是实例方法的函数仍然可以使用点语法来进行访问,减少一堆括号导致代码散乱无章的现象发生。

然而,所有的付出并没有完全收获,对于 Swift 标准库之外的自由函数,比如说 dispatch_async以及UIImageJPEGRepresentation。这些函数用起来十分笨重,如果您的代码中大量应用了这些函数的话,不妨来看看 Swift 是如何减轻您的负担的。我们以一些 GCD 的例子开始。

syncasync

这十分简单,我们直接上代码:

extension?dispatch_queue_t
{
????final?func?async(block:?dispatch_block_t)?{
????????dispatch_async(self,?block)
????}
????
????//?`block`?这里应该被标记为?@noescape,然而我们无法这么做????final?func?sync(block:?dispatch_block_t)?{
????????dispatch_sync(self,?block)
????}
}

这两个便利调用方法可以直接放入正常的队列处理函数,并且允许我们使用点语法进行访问,在此之前我们是没办法这么做的。

注意:由于 GCD 对象转换至 Swift 的方法十分古怪,dispatch_queue_t实际上是一个协议,虽然它也可以作为类正常工作。我这里用final给函数做了标记以表明我们的意图,也就是不能在动态队列中使用。虽然我的理解是这里本质上是协议扩展,在与此类似的情况下,千万不要使用它。

mySerialQueue.sync?{
????print("I’m?on?the?queue!")
????threadsafeNum++
}

dispatch_get_global_queue(QOS_CLASS_BACKGROUND,?0).async?{
????expensivelyReticulateSplines
????print("Done!")
????
????dispatch_get_main_queue.async?{
????????print("Back?on?the?main?queue.")
????}
}

关于 sync有一个更为优化的版本,我们从 Swift 标准库函数中的with*族获取到了灵感,让我们在闭包中返回所计算出的值:

extension?dispatch_queue_t
{
????final?func?sync(block:??->?Result)?->?Result?{
????????var?result:?Result?
????????dispatch_sync(self)?{
????????????result?=?block
????????}
????????return?result!
????}
}

//?Grab?some?data?while?on?the?serial?queue
let?currentItems?=?mySerialQueue.sync?{
????print("I’m?on?the?queue!")
????return?mutableItems.copy
}

群策群力

还有两个简单的扩展,通过 dispatch groups可以轻松地实现:

extension?dispatch_queue_t
{
????final?func?async(group:?dispatch_group_t,?_?block:?dispatch_block_t)?{
????????dispatch_group_async(group,?self,?block)
????}
}

extension?dispatch_group_t
{
????final?func?waitForever?{
????????dispatch_group_wait(self,?DISPATCH_TIME_FOREVER)
????}
}

现在这个额外的 group参数就可以被包含进async的常规调用当中了。

let?group?=?dispatch_group_create

concurrentQueue.async(group)?{
????print("I’m?part?of?the?group")
}

concurrentQueue.async(group)?{
????print("I’m?independent,?but?part?of?the?same?group")
}

group.waitForever
print("Everything?in?the?group?has?now?executed")

注意:我们可以使用group.async(queue),就和queue.async(group)一样简单。哪一个更好完全取决于个人爱好,您甚至可以两个都实现。

精炼并简洁

如果您的项目中使用了 Objective-C 以及 Swift 两种语言,您或许会遇到这样一个尴尬的局面:Obj-C 的 API 和 Swift 的风格相差太大。这时候就需要NS_REFINED_FOR_SWIFT来救场了。

通过这个宏指令(Xcode 7 新出的)所标记的函数、方法和变量在 Obj-C 代码中可以正常使用,但当它们桥接到 Swift 的时候,名称前面就会加上“__”。

@interface?MyClass?:?NSObject

///?@return?指定?@c?的索引,如果未找到的话则返回?NSNotFound?
-?(NSUInteger)indexOfThing:(id)thing?NS_REFINED_FOR_SWIFT;

@end

//?当桥接到?Swift?的时候,这个方法变为:

public?class?MyClass:?NSObject
{
????public?func?__indexOfThing(thing:?AnyObject)?->?UInt
}

通过 Objc-C 方法,您就可以使用相同的名称来提供一个对 Swift 更友好的 API(通常在现有加下划线的原始版本的基础上来实现)。

extension?MyClass
{
????///?-?Returns:?指定?`thing`?的索引,如果未找到的话返回?`nil`
????func?indexOfThing(thing:?AnyObject)?->?Int?
????{
????????let?idx?=?Int(__indexOfThing(thing))?//?调用原始方法
????????if?idx?==?NSNotFound?{?return?nil?}
????????return?idx
????}
}

现在我们就可以使用 if let来使用这段代码了!

展望

Swift 是一门年轻的语言,其中每个代码库(codebase)都是不同的。大量微型函数库正在涌现,每位作者对于运算符、辅助方法以及命名规范都有着各自的标准。这个情况就需要苹果团队中需要谨慎采纳依赖库和标准库设置。

采用本文所述的技术不是为了写最酷、最时髦的 Swift 代码。诚然,维护您任务的某人——也是将来的您自己——可能会以一份全新的方式来思考这些代码。他们的目的只是为了更好地阅读代码,因此不要因为怎样简单怎样来,而是怎样明了怎样来。

感谢博文视点为本期翻译活动提供赞助

相关推荐

“三次握手,四次挥手”你真的懂吗?

记得刚毕业找工作面试的时候,经常会被问到:你知道“3次握手,4次挥手”吗?这时候我会“胸有成竹”地“背诵”前期准备好的“答案”,第一次怎么怎么,第二次……答完就没有下文了,面试官貌似也没有深入下去的意...

面试官问:三次握手与四次挥手是怎么完成的?

作者|饶全成来源|码农桃花源记得刚毕业找工作面试的时候,经常会被问到:你知道“3次握手,4次挥手”吗?这时候我会“胸有成竹”地“背诵”前期准备好的“答案”,第一次怎么怎么,第二次……答完就没有...

三次握手和四次挥手的高阶面试题,建议收藏

昨天村长的讲解,真是一语点醒,这样的解释胜过死记硬背。但对于学习者,如果不能有直观感受,可能还是觉得不接地气,今天介绍两个工具,一个是网络抓包工具Wireshark,一个是linux命令tcpdum...

三次握手和四次挥手到底是个什么鬼东西

之前总有是有面试官喜欢问,你知道什么是三次握手么?什么是四次挥手么?为什么握手需要三次,挥手需要四次呢?今天我们就来详细的聊一下这个。1.什么是TCPTCP协议,简单称呼一下的话,那就是传输控制协议,...

加深理解TCP的三次握手与四次挥手

在了解三次握手和四次挥手之前,先要知道TCP报文内部包含了那些东西。熟悉了解TCP报文对日后学习网络和排除方面有很大的帮助,所以,今天为了加深对三次握手的理解,从新去认识TCP报文格式。TCP报文格式...

三次握手 与 四次挥手_三次握手四次挥手大白话

三次握手:①首先Client端发送连接请求报文②Server段接受连接后回复ACK报文,并为这次连接分配资源。③Client端接收到ACK报文后也向Server段发生ACK报文...

动画讲解TCP的3次握手,4次挥手,让你一次看明白

专注于Java领域优质技术,欢迎关注作者:老钱占小狼博客TCP三次握手和四次挥手的问题在面试中是最为常见的考点之一。很多读者都知道三次和四次,但是如果问深入一点,他们往往都无法作出准确回答。本篇尝试...

linux下实现免密传输文件或登录到其他服务器

使用scp传输文件到其他服务器的时候,提示需要输密码,如下:[root@18csetup]#scpLINUX.X64_180000_db_home.zip192.168.133.120:/u0...

Linux如何通过salt免密SCP传输上百台机的脚本?看chatGPT的回答

如何通过salt免密SCP传输上百台机的shell脚本”,下面是chatGPT给出的结果。scp批量免密脚本给出的详细shell脚本如下:#!/bin/bash#源文件路径和目标路径SRC_...

Linux/Mac scp命令上传文件_将hdfs上的文件下载到本地的命令是

语法scp[可选参数]file_sourcefile_target参数说明:-1:强制scp命令使用协议ssh1-2:强制scp命令使用协议ssh2-4:强制scp命令只使用IPv4寻...

Linux常用功能——文件远程传输_linux 远程传输文件

scp是securecopy的简写,是linux系统下基于ssh登陆进行安全的远程文件拷贝命令,用于在Linux下进行远程拷贝文件的命令。和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器...

使用 scp 命令定时拉取服务器备份文件

我们的服务器,每周五必须要做下备份,但总是忘记执行备份这件事情,或者是服务器备份做了,但没有做异地备份。所以通过定时任务自动备份,备份成功之后,在其它服务器上面通过定时任务scp命令自动拉取备份文...

windows下最轻便的FTP/SCP文件管理器

这次推荐的工具叫做winscp,这个工具如果是IT从业人员,又是做服务端相关工作的话,可能无人不知,如果是刚入门,推荐立马上手试试。如果看了觉得有用,欢迎收藏、点赞、关注。官方网站:https://w...

我不是网管 - Linux中使用SCP命令安全复制文件

SCP是linux发行版中的命令行工具,用于通过网络安全地跨系统复制文件和目录。SCP代表安全复制,因为它使用ssh协议复制文件。拷贝时,scp命令建立ssh连接到远程系统。换句话说...

WinSCP软件双系统(Win-Linux)文件传输教程

WinSCP软件是windows下的一款使用ssh协议的开源图形化SFTP客户端,也就是一个文件传输的软件,它有什么优点吗,咱们嵌入式开发中经常会将windows中的文件复制到linux系统当中,比较...

取消回复欢迎 发表评论: