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

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

xiyangw 2022-12-04 09:43 16 浏览 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 代码。诚然,维护您任务的某人——也是将来的您自己——可能会以一份全新的方式来思考这些代码。他们的目的只是为了更好地阅读代码,因此不要因为怎样简单怎样来,而是怎样明了怎样来。

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

相关推荐

前后端分离 Vue + NodeJS(Koa) + MongoDB实践

作者:前端藏经阁转发链接:https://www.yuque.com/xwifrr/gr8qaw/vr51p4写在前面闲来无事,试了一下Koa,第一次搞感觉还不错,这个项目比较基础但还是比较完整了,...

MongoDB 集群如何工作?

一、什么是“MongoDB”?“MongoDB”是一个开源文档数据库,也是领先的“NoSQL”数据库,分别用“C++”“编程语言”编写,使用带有“Schema”的各种类似JSON的文档,是也分别被认为...

三部搭建mongo,和mongo UI界面

三步搭建mongo,和mongoUI界面安装首先你需要先有一个docker的环境检查你的到docker版本docker--versionDockerversion18.03.1-ce,b...

Mongodb 高可用落地方案

此落地方案,用于实现高可用。复制集这里部署相关的复制集,用于实现MongoDB的高可用。介绍MongoDB复制集用于提供相关的数据副本,当发生硬件或者服务中断的时候,将会从副本中恢复数据,并进行自动...

一次线上事故,我顿悟了MongoDB的精髓

大家好,我是哪吒,最近项目在使用MongoDB作为图片和文档的存储数据库,为啥不直接存MySQL里,还要搭个MongoDB集群,麻不麻烦?让我们一起,一探究竟,继续学习MongoDB分片的理论与实践,...

IDEA中安装MongoDB插件-再也无要nosql manager for mongodb

大家都知道MongoDB数据库作为典型的非关系型数据库被广泛使用,但基于MongoDB的可视化管理工具-nosqlmanagerformongodb也被用的较多,但此软件收费,所以国内的破解一般...

数据库监控软件Lepus安装部署详解

Lepus安装部署一、软件介绍Lepus是一套开源的数据库监控平台,目前已经支持MySQL、Oracle、SQLServer、MongoDB、Redis等数据库的基本监控和告警(MySQL已经支持复...

YAPI:从0搭建API文档管理工具

背景最近在找一款API文档管理工具,之前有用过Swagger、APIManager、Confluence,现在用的还是Confluence。我个人一直不喜欢用Swagger,感觉“代码即文档”,让代...

Mac安装使用MongoDB

下载MongoDB包:https://www.mongodb.com/download-center解压mongodb包手动解压到/usr/local/mongodb文件夹配置Mac环境变量打开环境...

保证数据安全,不可不知道的MongoDB备份与恢复

大家在项目中如果使用MongoDB作为NOsql数据库进行存储,那一定涉及到数据的备份与恢复,下面给大家介绍下:MongoDB数据备份方法在MongoDB中我们使用mongodump命令来备...

MongoDB数据备份、还原脚本和定时任务脚本

备注:mongodump和mongorestore命令需要在MongoDB的安装目录bin下备份脚本备份格式/usr/local/mongodb/bin/mongodump -h ...

等保2.0测评:mongoDB数据库

一、MongoDB介绍MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB是一个介于关系数据库和非关系数据库之间的产...

MongoDB入门实操《一》

什么是MongoDBMongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB是一个介于关系数据库和非关系数据库之...

Python安装PyMongo的方法详细介绍

欢迎点击右上角关注小编,除了分享技术文章之外还有很多福利,私信学习资料可以领取包括不限于Python实战演练、PDF电子文档、面试集锦、学习资料等。前言本文主要给大家介绍的是关于安装PyMongo的...

第四篇:linux系统中mongodb的配置

建议使用普通用户进行以下操作。1、切换到普通用户odysee。2、准备mongodb安装包,自行去官网下载。3、解压安装包并重命名为mongodb4.04、配置mongodbcdmongod...

取消回复欢迎 发表评论: