作者:Jacob Bandes-Storch免绿,原文鏈接俺榆,原文日期:2015-11-28
譯者:mmoaay味悄;校對(duì):千葉知風(fēng)刻获;定稿:shanks
做程序員有一點(diǎn)優(yōu)勢:如果工具不好用郎仆,你自己就可以對(duì)它進(jìn)行優(yōu)化只祠。而 Swift 讓這一點(diǎn)變得尤其簡單,它包含的幾個(gè)特性可以讓你以一種自然的方式對(duì)這門語言進(jìn)行擴(kuò)展和自定義扰肌。
在本文中抛寝,我將分享 Swift 給我編程體驗(yàn)帶來提升的幾個(gè)例子。我希望在讀了本文之后曙旭,你可以認(rèn)識(shí)到使用這門語言時(shí)你自己的痛點(diǎn)在哪盗舰,并付諸實(shí)踐。(當(dāng)然需要先思考9瘐铩)
存在爭議的重復(fù)標(biāo)識(shí)符
下面是你在 Objective-C
中很熟悉的一種情況:枚舉值和字符串常量會(huì)有很長的描述詳細(xì)的名字:
label.textAlignment = NSTextAlignmentCenter;
(這讓我想起了中學(xué)科學(xué)課的格言:在作答時(shí)重復(fù)一下問題钻趋,或者 RQIA,文字是怎么對(duì)齊的剂习?文字是居中對(duì)齊的蛮位。 這在作答方式在超出上下文環(huán)境的時(shí)候很有用,但是其他情況下就顯得比較冗余了进倍。)
Swift 減少了這種冗余土至,因?yàn)槊杜e值可以通過類型名+點(diǎn)符號(hào)來訪問,而且如果你省略了類型名猾昆,它仍然可以被自動(dòng)推斷出來:
label.textAlignment = NSTextAlignment.Center
// 更簡明的:
label.textAlignment = .Center
但有時(shí)候你用的不是枚舉陶因,而是被一個(gè)又臭又長的構(gòu)造器給困住了。
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
有多少 “timingFunction” 呢垂蜗?太多了好嘛楷扬。
一個(gè)不那么為人所知的事實(shí)是解幽,縮寫點(diǎn)符號(hào)對(duì)任何類型的任何 static
成員都有效。結(jié)合在 extension
中添加自定義 property
的能力烘苹,我們得到如下代碼…
extension CAMediaTimingFunction
{
// 這個(gè)屬性會(huì)在第一次被訪問時(shí)初始化躲株。
// (需要添加 @nonobjc 來防止編譯器
// 給 static(或者 final)屬性生成動(dòng)態(tài)存取器。)
@nonobjc static let EaseInEaseOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
// 另外一個(gè)選擇就是使用計(jì)算屬性, 它同樣很有效,
// 但 *每次* 被訪問時(shí)都會(huì)重新求值:
static var EaseInEaseOut: CAMediaTimingFunction {
// .init 是 self.init 的簡寫
return .init(name: kCAMediaTimingFunctionEaseInEaseOut)
}
}
現(xiàn)在我們得到了一個(gè)優(yōu)雅的簡寫:
animation.timingFunction = .EaseInEaseOut
Context
中的重復(fù)標(biāo)識(shí)符
用來處理 Core Graphics Context
镣衡、顏色空間等的代碼往往也是冗長的霜定。
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(),
CGColorCreate(CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB), [0.792, 0.792, 0.816, 1]))
再次使用棒棒的 extension
:
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]))
更簡單了是不?而且顯然會(huì)有更多的方式對(duì) Core Graphics
類型進(jìn)行擴(kuò)展廊鸥,以使其適應(yīng)你的需求望浩。
Auto Layout 中的重復(fù)標(biāo)識(shí)符
下面的代碼看起來熟悉么?
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
理解起來相當(dāng)困難惰说,是么磨德?Apple 認(rèn)識(shí)到這是個(gè)普遍存在的問題,所以重新設(shè)計(jì)了新的 NSLayoutAnchor
API(在 iOS9 和 OS X 10.11 上適用)來處理這個(gè)問題:
spaceConstraint = label.leadingAnchor.constraintEqualToAnchor(button.trailingAnchor, constant: 20)
widthConstraint = label.widthAnchor.constraintLessThanOrEqualToConstant(200)
spaceConstraint.active = true
widthConstraint.active = true
然而吆视,我認(rèn)為還可以做的更好典挑。在我看來,下面的代碼比內(nèi)置的接口更容易閱讀和使用:
spaceConstraint = label.constrain(.Leading, .Equal, to: button, .Trailing, plus: 20)
widthConstraint = label.constrain(.Width, .LessThanOrEqual, to: 200)
// "設(shè)置 label 的 leading edge 和 button 的 trailing edge 相距 20"
// "設(shè)置 label 的 width 小于等于 200啦吧。"
上面的代碼是通過給 UIView
或者 NSView
添加一些 extension
來實(shí)現(xiàn)的您觉。這些輔助函數(shù)看起來可能會(huì)有些拙劣,但是用起來會(huì)特別方便授滓,而且很容易維護(hù)顾犹。(這里我已經(jīng)提供了另外一些包含默認(rèn)值的參數(shù)——一個(gè) multiplier
,priority
和 identifier
——所以你可以選擇更進(jìn)一步滴進(jìn)行自定義約束褒墨。)
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
}
}
你好~操作符
首先我必須提醒一下:如果要使用自定義操作符,一定要三思而后行擎宝。使用它們很簡單郁妈,但最終可能會(huì)得到一堆像屎一樣的代碼。一定要對(duì)代碼的健康性持懷疑態(tài)度绍申,然后你會(huì)發(fā)現(xiàn)在某些場景下噩咪,自定義操作符確實(shí)是很有用的。
重載它們
如果你有開發(fā)過拖動(dòng)手勢相關(guān)的功能极阅,你可能會(huì)寫過類似下面的代碼:
// 觸摸開始 / 鼠標(biāo)按下:
let touchPos = touch.locationInView(container)
objectOffset = CGPoint(x: object.center.x - touchPos.x, y: object.center.y - touchPos.y)
// 觸摸移動(dòng) / 鼠標(biāo)拖動(dòng):
let touchPos = touch.locationInView(container)
object.center = CGPoint(x: touchPos.x + objectOffset.x, y: touchPos.y + objectOffset.y)
在這段代碼里面我們只做了簡單的加法和減法胃碾,但因?yàn)?CGPoint
包含 x
和 y
,所以每個(gè)表達(dá)式我們都要寫兩次筋搏。所以我們需要一些簡化操作的函數(shù)仆百。
objectOffset
代表觸摸位置和對(duì)象位置的距離。描述這種距離最好的方式并不是 CGPoint
奔脐,而是不那么為人所知的 CGVector俄周, 它不使用 x
和 y
吁讨,而是用 dx
和 dy
來表示距離或者 “deltas“。
所以兩個(gè)點(diǎn)相減得到一個(gè)向量就比較符合邏輯了峦朗,這樣一來我們就得到了 -
操作符的一個(gè)重載:
/// - 返回: 從 `rhs` 到 `lhs`的向量建丧。
func -(lhs: CGPoint, rhs: CGPoint) -> CGVector
{
return CGVector(dx: lhs.x - rhs.x, dy: lhs.y - rhs.y)
}
然后,相反滴波势,把一個(gè)向量和一個(gè)點(diǎn)相加得到另外一個(gè)點(diǎn):
// - 返回: `lhs` 偏移`rhs` 之后得到的一個(gè)點(diǎn)
func +(lhs: CGPoint, rhs: CGVector) -> CGPoint
{
return CGPoint(x: lhs.x + rhs.dx, y: lhs.y + rhs.dy)
}
現(xiàn)在下面的代碼看起來就感覺很好了翎朱!
// 觸摸開始:
objectOffset = object.center - touch.locationInView(container)
// 觸摸移動(dòng):
object.center = touch.locationInView(container) + objectOffset
練習(xí):想一想其它可以用在點(diǎn)和向量上的操作符,并對(duì)它們進(jìn)行重載尺铣。
建議:-(CGPoint, CGVector)
拴曲、*(CGVector, CGFloat)
和-(CGVector)
。
獨(dú)門絕技
下面是一些更有創(chuàng)造性的內(nèi)容迄埃。Swift 提供了一些復(fù)合賦值操作符疗韵,這些操作符在執(zhí)行某個(gè)操作的同時(shí)進(jìn)行賦值:
a += b // 等價(jià)于 "a = a + b"
a %= b // 等價(jià)于 "a = a % b"
但是仍然存在其它不包含內(nèi)置復(fù)合賦值形式的操作符。最常見的例子就是 ??
侄非,空合并運(yùn)算符蕉汪。也就是 Ruby 中的 ||=
,例如逞怨,首先實(shí)現(xiàn)只有在變量是 nil
(或者不是)的情況下才賦值的版本者疤。這對(duì) Swift 中的可選值意義非凡,而且實(shí)現(xiàn)起來也很簡單:
infix operator ??= { associativity right precedence 90 assignment } // 匹配其它的賦值操作符
/// 如果 `lhs` 為 `nil`, 把 `rhs` 的值賦給它
func ??=<T>(inout lhs: T?, @autoclosure rhs: () -> T)
{
lhs = lhs ?? rhs()
}
這段代碼看起來可能很復(fù)雜——這里我們做了下面幾件事情叠赦。
-
infix operator
聲明用來告訴 Swift 把??=
當(dāng)作一個(gè)操作符驹马。 - 使用
<T>
將函數(shù)泛型化,從而使其可以支持任何類型的值除秀。 -
inout
表示允許修改左側(cè)的運(yùn)算數(shù) -
@autoclosure
用來支持短路賦值糯累,有需要的話可以只對(duì)右側(cè)做賦值操作。(著也依賴于??
本身對(duì)短路的支持册踩。)
但在我看來泳姐,上述代碼實(shí)現(xiàn)的功能是非常清晰而且易用的:
a ??= b // 等價(jià)于 "a = a ?? b"
調(diào)度
關(guān)于如何在 Swift 中使用 GCD,最好的方式是閱讀官方文檔暂吉,但在本文中我仍然會(huì)介紹一些基礎(chǔ)知識(shí)點(diǎn)胖秒。如果想了解更多,請(qǐng)參照這份概要慕的。
Swift 2 引入了協(xié)議擴(kuò)展阎肝,因此,很多之前的全局標(biāo)準(zhǔn)庫函數(shù)變成了準(zhǔn)成員函數(shù):如 map(seq, transform)
變成了現(xiàn)在的 seq.map(transform)
肮街,join(separator, seq)
變成了現(xiàn)在的 seq.joinWithSeparator(separator)
等等风题。這樣的話,那些嚴(yán)格說來不屬于類實(shí)例方法的函數(shù)仍然可以用 .
符號(hào)訪問,而且還減少了逗號(hào)(PS:原文為parentheses俯邓,可能是作者筆誤)的數(shù)目骡楼,從而不會(huì)把代碼弄得太亂。
然而這種變化并沒有應(yīng)用到 Swift 標(biāo)準(zhǔn)庫外的自由函數(shù)稽鞭,比如 dispatch_async()
和 UIImageJPEGRepresentation()
鸟整。這些函數(shù)仍然很難用,如果你經(jīng)常使用它們朦蕴,還是很值得思考一下如何利用 Swift 來幫你改造一下它們篮条。下面是一些入門的 GCD 例子。
sync
或者非 sync
這些都很簡單吩抓;我們馬上開始:
extension dispatch_queue_t
{
final func async(block: dispatch_block_t) {
dispatch_async(self, block)
}
// 這里的 `block` 需要是 @noescape 的, 但不能是鏈接中這樣的: <http://openradar.me/19770770>
final func sync(block: dispatch_block_t) {
dispatch_sync(self, block)
}
}
上面簡化的兩個(gè)函數(shù)直接調(diào)用了普通的調(diào)度函數(shù)涉茧,但可以讓我們通過 .
符號(hào)調(diào)用它們,這是我們之前做不到的疹娶。
注:GCD 對(duì)象是以一種古怪的方式導(dǎo)出到 Swift 的伴栓,盡管以類的方式也可以實(shí)現(xiàn),但實(shí)際上
dispatch_queue_t
只是一個(gè)協(xié)議而已雨饺。在這里我把兩個(gè)函數(shù)都標(biāo)注了final
來表明我們的意圖:我們不希望在這里使用動(dòng)態(tài)調(diào)度钳垮,盡管在我看來這種情況下使用協(xié)議擴(kuò)展是很好的,但是不要在哪都用额港。
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.")
}
}
更進(jìn)一步的 sync
的版本是從 Swift 標(biāo)準(zhǔn)庫函數(shù)中的 with*
家族獲取到的靈感饺窿,我們要做的事情是返回一個(gè)在閉包中計(jì)算得到的結(jié)果:
extension dispatch_queue_t
{
final func sync<Result>(block: () -> Result) -> Result {
var result: Result?
dispatch_sync(self) {
result = block()
}
return result!
}
}
// 在串行隊(duì)列上抓取一些數(shù)據(jù)
let currentItems = mySerialQueue.sync {
print("I’m on the queue!")
return mutableItems.copy()
}
群體思維
另外兩個(gè)簡單的擴(kuò)展可以讓我們很好的使用 dispatch group
:
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)
}
}
現(xiàn)在調(diào)用 async
的時(shí)候就可以包含一個(gè)額外的 group
參數(shù)了。
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)
移斩。具體用哪個(gè)全看你自己——或者你甚至可以兩個(gè)都實(shí)現(xiàn)肚医。
優(yōu)雅的重定義
如果你的項(xiàng)目同時(shí)包含 Objective-C 和 Swift,你可能會(huì)碰到這種讓人頭大的情況:Obj-C 的 API 看起來不是那么 Swift 化向瓷。需要 NS_REFINED_FOR_SWIFT
來拯救我們了肠套。
在 Obj-C 中使用標(biāo)記了 (new in Xcode 7) 宏的函數(shù)、方法和變量是正常的猖任,但是導(dǎo)出到 Swift 之后糠排,它們會(huì)包含一個(gè) “__
“前綴。
@interface MyClass : NSObject
/// @返回 @c 東西的下標(biāo), 如果沒有提供就返回 NSNotFound超升。
- (NSUInteger)indexOfThing:(id)thing NS_REFINED_FOR_SWIFT;
@end
// 當(dāng)導(dǎo)出到 Swift 時(shí), 它就變成了:
public class MyClass: NSObject
{
public func __indexOfThing(thing: AnyObject) -> UInt
}
現(xiàn)在把 Obj-C 的方法放到一邊,你可以重用同樣的名字來提供一個(gè)更友好的 Swift 版本 API(實(shí)現(xiàn)方式通常是調(diào)用帶“__
“前綴的原始版本):
extension MyClass
{
/// - 返回: 給定 `thing` 的下標(biāo), 如果沒有就返回 `nil`哺徊。
func indexOfThing(thing: AnyObject) -> Int?
{
let idx = Int(__indexOfThing(thing)) // 調(diào)用原始方法
if idx == NSNotFound { return nil }
return idx
}
}
現(xiàn)在你可以心滿意足滴 “if let
“了室琢!
更進(jìn)一步
Swift 還很年輕,它各個(gè)代碼庫的風(fēng)格也都是不同的落追。而大量的第三方微型庫也涌現(xiàn)出來盈滴,這些庫的代碼體現(xiàn)了作者在操作符,輔助函數(shù)和命名規(guī)范上所持的不同觀點(diǎn)。這種情況也就需要在處理依賴關(guān)系以及在團(tuán)隊(duì)中建立規(guī)范時(shí)更挑剔巢钓。
使用本文中技術(shù)最重要的原因不是寫最新潮和最酷炫的 Swift 代碼病苗。當(dāng)然,負(fù)責(zé)維護(hù)你代碼的人——也許是未來的你——可能會(huì)持不同的觀點(diǎn)症汹。為了他們硫朦,親愛的讀者,你需要在為了讓代碼變的清晰合理的情況下擴(kuò)展 Swift背镇,而不是為了讓代碼變的簡單而去擴(kuò)展它咬展。
譯者:Playground已經(jīng)上傳到github!
本文由 SwiftGG 翻譯組翻譯瞒斩,已經(jīng)獲得作者翻譯授權(quán)破婆,最新文章請(qǐng)?jiān)L問 http://swift.gg。