譯者:泊學(xué)
提交 review 前必讀
做為下面三份文檔的一部分,它們的內(nèi)容是彼此關(guān)聯(lián)的:
- SE-0023 API設(shè)計指南
- SE-0006 在標(biāo)準(zhǔn)庫中應(yīng)用設(shè)計指南
- SE-0005 更好的把Objective-C APIs轉(zhuǎn)換成Swift版本
這三份文檔的內(nèi)容是相互關(guān)聯(lián)的(例如:標(biāo)準(zhǔn)庫中一個 API 的調(diào)整和某個 API guideline 是對應(yīng)的,或根據(jù)某條設(shè)計指南制定的 Clang importer 規(guī)則,等等)。正因為存在這些內(nèi)容交叉,為了保證討論是可維護(hù)的,我們希望你:
- 在提交 review 之前,對以上三份文檔中的全部內(nèi)容,有一個基本的了解;
- 在提交以上三個文檔的 review 時,請參照每個文檔的 review 聲明。在你提交 review 時,如果文檔間交叉引用有助于幫你闡述觀點,你應(yīng)該包含它們(這也是被提倡的做法)。
簡介
這份提議描述了我們?nèi)绾胃倪M(jìn) Swift 的 Clang Importer,它有兩個功能:首先,把 C 和 Objective-C 的 APIs 映射成 Swift 版本;其次,翻譯 Objective-C 中的函數(shù)、類型、方法和屬性等的名字,讓它們滿足 API設(shè)計指南 中的要求,這些要求,是我們在設(shè)計Swift 3時,建立的原則。
我們的方法專注于 Objective-C 版本的 Cocoa 編碼指南 和 Swift 版本的 API 設(shè)計指南 之間的差異。使用一些簡單的語言學(xué)分析方法,協(xié)助我們把 Objective-C 中的名字自動轉(zhuǎn)換成更加 Swift “原汁原味”的名字。
轉(zhuǎn)換的結(jié)果,可以在 Swift 3 API Guidelines Review 這個 repository 中查看。這個 repository 中包含了用 Swift 2 和 Swift 3 編寫的 Objective-C APIs 項目,以及一些已經(jīng)遷移到 Swift 3 版本的示例代碼。你也可以通過 對比這兩個分支 來查看所有的改動。
動機
Objective-C 版本的 Cocoa 編碼指南 為使用 Objective-C 創(chuàng)建簡單、一致的 API 提供了完整的框架。但是 Swift 是一門不同的編程語言,特別是,它是一門支持類型推導(dǎo)、泛型編程和重載等語言特性的強類型語言。于是,基于 Objective-C 編寫的 APIs 搭配上Swift就有點兒水土不服,這些 API 在 Swift 里用起來顯得很啰嗦。例如:
let content =
listItemView.text.stringByTrimmingCharactersInSet(
NSCharacterSet.whitespaceAndNewlineCharacterSet())
這明顯是一個 Objective-C 風(fēng)格的函數(shù)調(diào)用。如果我們用 Swift 編寫,結(jié)果看上去應(yīng)該是這樣的:
let content =
listItemView.text.trimming(.whitespaceAndNewlines)
這顯然是更遵循 Swift API 設(shè)計指南 中的用法,特別是,我們忽略掉了那些編譯器已經(jīng)能強制約束我們使用的類型名稱(例如:view,string, character set等)。這份提議的目的,就是讓從 Objective-C 引入 API 更加 “Swift原汁原味” ,讓 Swift 開發(fā)者在使用 Objective API 時,有和使用 Swift “原生代碼”更為一致的開發(fā)體驗。
這份提議中的解決方案對 Objective-C 框架(例如:Cocoa 和 Cocoa Touch)和任何可以在“Swift 混合項目”中使用的 Objective-C API 是相同的。要說明的是, Swift 核心庫 重新實現(xiàn)了 Objective-C 框架中的 API,所以,對這些 API(名稱的)的改動都會在 Swift 3 核心庫的實現(xiàn)中體現(xiàn)出來。
提議的解決方案
提議的解決方案引入了一種定義 Objective-C Cocoa 編碼指南 和 Swift API設(shè)計指南 區(qū)別的方式,這種方式可以幫助我們通過設(shè)置一系列規(guī)則,參照 Cocoa 編碼指南以及 Objective-C 中既定的習(xí)俗,把前者變換成后者。這是對用Clang importer進(jìn)行名稱翻譯的一種啟發(fā)式擴展。例如:把 Objective-C 中的全局 enum 常量變成 Swift 中的 cases(這要去掉 Objective-C 為全局enum常量名設(shè)置的前綴)以及把 Objective-C 中的工廠方法(例如:+[NSNumber numberWithBool:])映射成 Swift 中的初始化方法(NSNumber(bool: true))。
這份提議中描述的啟發(fā)式方法需要通過覆蓋大量的 Objective-C API 進(jìn)行迭代、調(diào)校和試驗,以確保它最終可以正常工作。但是,它仍舊是不可能完美工作的,一定會有一些 API ,經(jīng)過 “翻譯” 之后,會導(dǎo)致其不如原來表意清晰。因此,我們的目標(biāo)是確保絕大多數(shù)的Objective C API都可以在翻譯之后更加的 Swift 原汁原味。并且允許 Objective-C API 的作者對于那些不滿意的翻譯,在 Objective-C 頭文件中,通過 API 注釋說明問題。
這份提議的解決方案對 Clang importer 引入了以下這些改變:
-
泛化
swift_name屬性的應(yīng)用范圍:Clang 的swift_name現(xiàn)在只能用于重命名enum的 cases 以及工廠方法。當(dāng)它被引入到Swift后,它應(yīng)該被泛化成允許重命名任意的 C 或 Objective-C 的語言元素,以方便 C 或 Objective-C API 的作者更好的調(diào)校重命名的過程。 - 去掉冗余的類型名稱:Objective-C Cocoa 編碼指南要求方法聲明中要帶有每一個參數(shù)的描述。當(dāng)這個描述重申了參數(shù)的類型時,方法的名字就違背了 Swift 編碼指南中關(guān)于“忽略不需要的字符”的設(shè)計要求。因此,執(zhí)行翻譯時,我們應(yīng)該去掉那些描述類型的部分。
- 添加默認(rèn)參數(shù):如果 Objective-C API 的聲明強烈暗示參數(shù)需要參數(shù)默認(rèn)值,應(yīng)該為這樣的 API 在引入 Swift 時,添加參數(shù)默認(rèn)值。例如,一個表示選項集合的參數(shù),可以被設(shè)置成[]。
- 為第一個參數(shù)添加label:如果方法的第一個參數(shù)有默認(rèn)值, 應(yīng)該為這個參數(shù)設(shè)置一個參數(shù)label 。
- 給Bool語義的屬性添加“is”前綴:Bool屬性應(yīng)該在讀取的時候,表達(dá)斷言的語義 ,但是 Objective-C Cocoa 編碼指南中,禁止在屬性名中使用單詞“is” 。因此,當(dāng)引入這樣的屬性時,為它們添加“is”前綴。
-
表達(dá)值語義的名字,首字母小寫:在 Swift API 設(shè)計指南中,要求對 “非類型聲明(non-type declarations)” 使用小寫字母。包括,
enum中的case以及屬性或函數(shù)的聲明。因此,引入Objective-C中沒有前綴的值時,讓這些名字的首字母小寫(例如:一個叫做URLHandler的屬性應(yīng)該變成urlHandler)。 -
讓實現(xiàn)
compare(_:) -> NSComparisonResult的類遵從Comparable protocol:在 Objective-C 中,類對象的比較結(jié)果,都是通過“排序”的方式判斷的(注:例如NSOrderedDescending和NSOrderedAscending)。導(dǎo)入過程中,通過讓這些類遵從Comparableprotocol,可以讓比較操作的實現(xiàn)更正規(guī)(注:通過Comparable提供的運算符方法)。
為了感受下這些轉(zhuǎn)換規(guī)則帶來的實際效果,看下 UIBezierPath 從 Swift 2:
class UIBezierPath : NSObject, NSCopying, NSCoding {
convenience init(ovalInRect: CGRect)
func moveToPoint(_: CGPoint)
func addLineToPoint(_: CGPoint)
func addCurveToPoint(_: CGPoint,
controlPoint1: CGPoint, controlPoint2: CGPoint)
func addQuadCurveToPoint(_: CGPoint,
controlPoint: CGPoint)
func appendPath(_: UIBezierPath)
func bezierPathByReversingPath() -> UIBezierPath
func applyTransform(_: CGAffineTransform)
var empty: Bool { get }
func containsPoint(_: CGPoint) -> Bool
func fillWithBlendMode(_: CGBlendMode, alpha: CGFloat)
func strokeWithBlendMode(_: CGBlendMode, alpha: CGFloat)
func copyWithZone(_: NSZone) -> AnyObject
func encodeWithCoder(_: NSCoder)
}
移植到 Swift 3 后的變化:
class UIBezierPath : NSObject, NSCopying, NSCoding {
convenience init(ovalIn rect: CGRect)
func move(to point: CGPoint)
func addLine(to point: CGPoint)
func addCurve(to endPoint: CGPoint,
controlPoint1 controlPoint1: CGPoint,
controlPoint2 controlPoint2: CGPoint)
func addQuadCurve(to endPoint: CGPoint,
controlPoint controlPoint: CGPoint)
func append(_ bezierPath: UIBezierPath)
func reversing() -> UIBezierPath
func apply(_ transform: CGAffineTransform)
var isEmpty: Bool { get }
func contains(_ point: CGPoint) -> Bool
func fill(_ blendMode: CGBlendMode, alpha alpha: CGFloat)
func stroke(_ blendMode: CGBlendMode, alpha alpha: CGFloat)
func copy(with zone: NSZone = nil) -> AnyObject
func encode(with aCoder: NSCoder)
}
可以看到,在 Swift 3 版本里,原來 API 里很多描述類型信息的部分都被去掉了。轉(zhuǎn)換后的結(jié)果,更接近 Swift API 設(shè)計指南中的要求?,F(xiàn)在,Swift 開發(fā)者可以通過類似 foo.copy() 這樣的方式,拷貝任何遵從 NSCopying 的對象,而不用再像原來 foo.copyWithZone(nil) 這樣的方式。
<A name="implementation-experience"></A>
實現(xiàn)過程
這份提議的一個試驗性實現(xiàn)在 Swift main repository 中。Swift 編譯器提供了一些開關(guān)幫助我們查看按照這份提議中的描述,被引入的 Objective-C API 以及 Swift 代碼自身的轉(zhuǎn)換結(jié)果(例如,通過 utils/omit-needless-words.py 腳本)。這些開關(guān)是:
-
--enable-omit-needless-words:這個開關(guān)啟用了對 Clang importer 絕大多數(shù)的改動(上一節(jié)中提到的 1,2,4,5)。它主要適合用來打印在 Master 和 Swift 2.2 分支 上,Swift 對Objective-C 模塊提供的接口。在 Swift 3 API Guidelines分支 上,它默認(rèn)是開啟的; -
--enable-infer-default-arguments:這個開關(guān)啟用了 Clang importer 中,對參數(shù)默認(rèn)值的干涉(上一節(jié)的 3); -
--swift-migration:僅在 Swift 2.2 分支上才有的開關(guān),這個選項通過添加 "Fix-Its",執(zhí)行把名稱從 Swift 2 遷移到 Swift 3 的基本轉(zhuǎn)換。通過和其它編譯器開關(guān)(例如:-fixit-code,-fixit-all)以及一個收集和應(yīng)用 “Fix-Its” 的腳本(utils/apply-fixit-edits.py)一起使用,這個開關(guān)為我們提供的基礎(chǔ)遷移工作可以幫助我們了解 Swift 代碼在各種聲明和調(diào)用場景里,按照這份提議被轉(zhuǎn)換后的樣子;
為了使用轉(zhuǎn)換后的名稱,真正編譯 Swift 3 代碼,可以使用 Swift 3 API Guidelines 分支。編譯器默認(rèn)啟用了上述功能并帶有隨之一起改動的標(biāo)準(zhǔn)庫。
設(shè)計細(xì)節(jié)
這部分描述了上述改變規(guī)則中第 2-5 條的試驗性實現(xiàn)細(xì)節(jié)。真正的實現(xiàn)在 Swift 代碼樹中,主要是 lib/Basic/StringExtras.cpp 中的 omitNeedlessWords 函數(shù)。
接下來的描述,和參與名稱翻譯的 Objective-C API 緊密相關(guān)。例如:startWithQueue:compeltionHandler: 是個有兩個selector 片段的方法,startWithQueue 和 completionHandler。這個 selector 翻譯成 Swift 版本應(yīng)該是startWithQueue(_:completionHandler:)。
去掉冗余的名稱
Objective-C API 中經(jīng)常會包含參數(shù)或返回值的類型名稱,它們應(yīng)該在 Swift API 中統(tǒng)統(tǒng)被去掉。下面的這些規(guī)則,用于識別并且去掉這些表示類型名稱的單詞。[[詳見 API 設(shè)計原則之忽略不需要的單詞]]。
定義類型名稱
匹配的過程是在老版本 Swift API 的 selector 片段里,搜索特定的類型名稱后綴,這些類型名稱定義如下:
- 對于絕大部分 Objective-C 類型來說,類型名稱就是忽略
nullable之后,Swift 引入的名稱,例如:
| Objective-C type | Type name |
|---|---|
| float | Float |
| nullable NSString | String |
| UIDocument | UIDocument |
| nullable UIDocument | UIDocument |
| NSInteger | NSInteger |
| UIUInteger | NSUInteger |
| CGFloat | CGFloat |
- 當(dāng) Objective-C 類型是一個 block 時,Swift 中的類型名稱是
Block; - 當(dāng) Objective-C 的類型是一個函數(shù)指針或引用時,Swift中的類型名稱是
Function; - 當(dāng) Objective-C 的類型是一個除
NSInteger、NSUInteger或CGFloat之外的 typedef 時,Swift 中的類型名稱則是這些typedef實際代表的類型的名稱。例如,Objective-C 中的類型是UILayoutPriority,實際上它是一個float類型的 typedef,我們將(在老版本的 Swift API 里)嘗試匹配字符串Float。[詳見 API 設(shè)計原則之為弱類型提供信息補償]。
匹配類型名稱
為了在 selector 片段中刪除冗余的類型信息,我們需要在 selector 中匹配包含上述類型信息的字符串。
全部匹配都是由以下兩個基本規(guī)則來管理的:
- 在單詞開始和結(jié)束的邊界進(jìn)行匹配:無論是 selector 片段內(nèi)部,還是類型名稱,單詞邊界位置都是一個字符串的開始或結(jié)束,以及每一個大寫字母前面。把每一個大寫字母作為單詞邊界可以讓我們匹配到“大寫字母縮略詞”,而無需單獨維護(hù)一個特殊的縮略詞或前綴列表;
例如,下面的 URL 是匹配的:
func documentForURL(_: NSURL) -> NSDocument?
但是,下面的 View,由于在單詞中間,它是不能被匹配的。
var thumbnailPreview : UIView // not matched
- 匹配的字符擴展到類型名稱的結(jié)尾:因為我們支持匹配一個類型名稱的后綴,因此:
func constraintEqualToAnchor(anchor: NSLayoutAnchor) -> NSLayoutConstraint?
可以被簡化成:
func constraintEqualTo(anchor: NSLayoutAnchor) -> NSLayoutConstraint?
基于以上兩個原則,我們可以執(zhí)行以下一系列的匹配過程:
-
基本匹配:
- selector 片段中的字串匹配類型名稱的字串,例如:
appendString中的String匹配NSString中的String:func appendString(_: NSString); - Selector 片段中的
Index匹配類型名稱中的Int,例如:func characterAtIndex(_: Int) -> unichar;
- selector 片段中的字串匹配類型名稱的字串,例如:
-
集合相關(guān)匹配(Collection matches):
- Selector 片段中的
Indexes或Indices匹配類型名稱中的IndexSet,例如:func removeObjectsAtIndexes(_: NSIndexSet); - 如果 selector 片段中的復(fù)數(shù)名詞的單數(shù)形式匹配集合中元素的類型,那么 selector 中的復(fù)數(shù)名詞匹配集合類型名稱,例如:
func arrangeObjects(_: [AnyObject]) -> [AnyObject]
- Selector 片段中的
特殊后綴匹配:
- 在 selector 片段中,用一個空字符串后綴匹配類型名稱中的
Type或_t,例如:
// 注:用一個空字符串匹配到了Type,因此SaveOperation加上
// 空字符串就匹配了SaveOperationType,
// 于是Selector中的SaveOperation的部分就可以刪除了。
func writableTypesForSaveOperation(
_: NSSaveOperationType) -> [String]
// 注:用一個空字符串匹配到了Type。
func objectForKey(_: KeyType) -> AnyObject
// 注:用一個空字符串匹配到了_t。
func startWithQueue(_: dispatch_queue_t,
completionHandler: MKMapSnapshotCompletionhandler)
- selector片段中的空字符串可以匹配類型名稱中“數(shù)字+D”形式的后綴,例如:
// Coordinate+空字符串匹配到了2D
func pointForCoordinate(_: CLLocationCoordinate2D) -> NSPoint
<A name="pruning-restrictions"></A>
名稱限制約束
(譯者注:刪除類型名稱時的限制)
如果在刪除 selector 名稱時違背了以下任何限制,那么刪除行為將不會進(jìn)行:
- 不要刪光所有的 selector;
- 不要把 selector 的第一個片段轉(zhuǎn)換成 Swift 關(guān)鍵字:
在 Swift 里,Objective-C 方法中的第一個 selector 片段會變成構(gòu)建一個方法名稱的基礎(chǔ)或者一個屬性的名字。
它們之中的任何一種,都不能是 Swift 關(guān)鍵字,否則,用戶就需要使用反引號來使用它們。
例如,下面的用法是合理的:
extension NSParagraphStyle {
class func defaultParagraphStyle() -> NSParagraphStyle
}
let defaultStyle =
NSParagraphStyle.defaultParagraphStyle() // OK
如果我們刪掉 ParagraphStyle,用起來就會很糟糕:
extension NSParagraphStyle {
class func `default`() -> NSParagraphStyle
}
let defaultStyle =
NSParagraphStyle.`default`() // Awkward
Objective-C方法名中的其它selector片段,會變成Swift方法的參數(shù)label,這個label是允許使用Swift關(guān)鍵字的,例如:
receiver.handle(someMessage, for: somebody) // OK
- 不要把方法名稱轉(zhuǎn)換成 “get”,“Set”,“with”, “for” 或 “using”,用這些單詞形成的方法名稱表意非??斩矗?/li>
- 不要刪除方法名稱中那些介紹參數(shù)的后綴,除非這個后綴前面直接連接一個介詞、動詞或動名詞:
這種啟發(fā)式轉(zhuǎn)換幫助我們避免破壞那些介紹參數(shù)的名詞短語。直接刪掉名詞短語后綴通常不會帶來我們預(yù)期的表意結(jié)果。例如:
func setTextColor(_: UIColor)
...
button.setTextColor(.red()) // clear
如果我們刪掉方法名中的Color,只剩下Text,調(diào)用方法時表達(dá)的語意就會讓人困惑:
func setText(_: UIColor)
...
button.setText(.red()) // appears to be setting the text!
- 如果方法的名字匹配它所在的類型中的一個屬性,不要轉(zhuǎn)換方法名:
這種啟發(fā)式轉(zhuǎn)換使我們可以避免讓那些修改類屬性的方法轉(zhuǎn)換出過于泛泛的名字,例如:
var gestureRecognizers: [UIGestureRecognizer]
func addGestureRecognizer(_: UIGestureRecognizer)
如果我們刪掉方法中的 GestureRecognizer,只留下 add,對于一個實際上是在修改 gesturerecognizers 屬性的方法來說,這個名字明顯太泛泛了。
var gestureRecognizers: [UIGestureRecognizer]
func add(_: UIGestureRecognizer) // should indicate that we're adding to the property
刪除類型名稱的步驟
我們按照下面的步驟刪除冗余的名字:
-
刪除頭部的結(jié)果類型信息。特別是,當(dāng)以下情形的時候:
- 當(dāng)方法返回一個自身所在的類型時;
- 并且這個類型的名稱匹配方法中第一個 selector 片段;
- 匹配到的名詞后面,緊跟一個介詞;
就可以刪除掉這個匹配。
通常,匹配以上這些條件的屬性和方法,它們都用于把自身類型變成其它某種等價形式的值。
例如:
extension NSColor {
func colorWithAlphaComponent(_: CGFloat) -> NSColor
}
let translucentForeground =
foregroundColor.colorWithAlphaComponent(0.5)
可以被簡化成:
extension NSColor {
func withAlphaComponent(_: CGFloat) -> NSColor
}
let translucentForeground =
foregroundColor.withAlphaComponent(0.5)
-
刪掉多余的介詞By。特別是,當(dāng)以下情形的時候:
- 在第一步中刪掉了開始的名詞之后;
- 方法名稱中,剩余的部分用
By+動名詞的形式;
就可以刪掉多余的 By 了。
這種啟發(fā)式方法可以讓我們使用類似a = b.frobnicating(c)的方法調(diào)用形式。例如:
extension NSString {
func stringByApplyingTransform(_: NSString,
reverse: Bool) -> NSString?
}
let sanitizedInput =
rawInput.stringByApplyingTransform(
NSStringTransformToXMLHex, reverse: false)
就可以通過第一步和第二步,被簡化成:
extension NSString {
func applyingTransform(
_: NSString, reverse: Bool
) -> NString?
}
let sanitizedInput =
rawInput.applyingTransform(NSStringTransformToXMLHex,
reverse: false)
- 在方法簽名的最后一個 selector 片段中,刪除任何匹配到的類型名稱。特別是以下類型:
| 方法名稱的尾部是 | 刪除匹配到的 |
|---|---|
| 用于介紹參數(shù)的selector片段 | 參數(shù)類型名稱 |
| 一個屬性名稱 | 屬性的類型名稱 |
| 一個不帶參數(shù)的方法 | 返回值的類型名稱 |
例如,下面這些情況:
extension NSDocumentController {
func documentForURL(
_ url: NSURL) -> NSDocument? // parameter introducer
}
extension NSManagedObjectContext {
var parentContext: NSManagedObjectContext? // property
}
extension UIColor {
class func darkGrayColor() -> UIColor // zero-argument method
}
...
myDocument = self.documentForURL(locationOfFile)
if self.managedObjectContext.parentContext != changedContext {
return
}
foregroundColor = .darkGrayColor()
可以被簡化成:
extension NSDocumentController {
func documentFor(_ url: NSURL) -> NSDocument?
}
extension NSManagedObjectContext {
var parent : NSManagedObjectContext?
}
extension UIColor {
class func darkGray() -> UIColor
}
...
myDocument = self.documentFor(locationOfFile)
if self.managedObjectContext.parent != changedContext {
return
}
foregroundColor = .darkGray()
- 只要匹配到的類型名稱前面直接連接動詞,即便這個類型名稱在方法名中間也可以刪掉它,例如:
extension UIViewController {
func dismissViewControllerAnimated(
flag: Bool,
completion: (() -> Void)? = nil)
}
可以被簡化成:
extension UIViewController {
func dismissAnimated(
flag: Bool,
completion: (() -> Void)? = nil)
}
為什么刪除一定要按照順序執(zhí)行呢?
下面的簡化過程有些在方法名稱的第一個selector片段中匹配,有些在方法名稱的結(jié)尾匹配。當(dāng)名稱限制約束阻止我們從開始和結(jié)尾進(jìn)行刪除時,從頭部開始刪除名稱可以保持方法家族的一致性,例如,對于 NSFontDescriptor 來說:
func fontDescriptorWithSymbolicTraits(
_: NSFontSymbolicTraits) -> NSFontDescriptor
func fontDescriptorWithSize(
_: CGFloat) -> UIFontDescriptor
func fontDescriptorWithMatrix(
_: CGAffineTransform) -> UIFontDescriptor
...
按照頭部匹配規(guī)則,它們可以變成這樣:
func withSymbolicTraits(
_: UIFontDescriptorSymbolicTraits) -> UIFontDescriptor
func withSize(
_: CGFloat) -> UIFontDescriptor
func withMatrix(
_: CGAffineTransform) -> UIFontDescriptor
...
如果我們堅持從尾部進(jìn)行刪除,刪掉第一個方法中的 SymbolicTraits 之后,我們就無法再繼續(xù)刪除頭部的 fontDescriptor 了,因為這違背了我們在名稱限制約束中定義的原則,只留下一個 with 表意是不夠的。
func fontDescriptorWith(
_: NSFontSymbolicTraits) -> NSFontDescriptor // inconsistent
func withSize(_: CGFloat) -> UIFontDescriptor
func withMatrix(_: CGAffineTransform) -> UIFontDescriptor
...
注:這樣一來,我們就破壞了原來家族方法的名稱一致性。
添加參數(shù)默認(rèn)值
出了那些只有一個參數(shù)的 setter 方法之外,在以下情況時,應(yīng)該給參數(shù)添加默認(rèn)值:
- 讓可以為空的 trailing closure 參數(shù)值為
nil; - 讓可以為空的
NSZone參數(shù)默認(rèn)為nil。Zones幾乎不在Swift中使用,它們應(yīng)該總是為nil的; - 讓參數(shù)名中帶有 "Options" 字樣的參數(shù)值默認(rèn)為
[],表示空的選項集合; - 和選項、屬性或信息有關(guān)的 NSDictionary 參數(shù),默認(rèn)值為
[:];
把這些規(guī)則集合起來,這個啟發(fā)式方法可以把下面的代碼:
rootViewController.presentViewController(
alert, animated: true, completion: nil)
UIView.animateWithDuration(0.2, delay: 0.0,
options: [],
animations: { self.logo.alpha = 0.0 }) {
_ in self.logo.hidden = true
}
變成這個樣子:
rootViewController.present(alert, animated: true)
UIView.animateWithDuration(0.2, delay: 0.0,
animations: { self.logo.alpha = 0.0 }) {
_ in self.logo.hidden = true
}
為第一個參數(shù)添加label
如果方法名稱中第一個 selector 片段中包含一個介詞,就把這個 selector 片段從最后一個介詞處分開,把這個 selector 中,這個介詞后面的部分變成第一個參數(shù)的 label。
除了為大量 API 的第一個參數(shù)添加 label 之外,當(dāng)?shù)谝粋€參數(shù)有默認(rèn)值時,這種啟發(fā)式方法還能消除方法被調(diào)用時,方法名稱表達(dá)的模糊語義。例如:
extension UIBezierPath {
func enumerateObjectsWith(
_: NSEnumerationOptions = [],
using: (AnyObject, UnsafeMutablePointer) -> Void)
}
array.enumerateObjectsWith(.Reverse) { // OK
// ..
}
array.enumerateObjectsWith() { // ?? With what?
// ..
}
變成:
extension NSArray {
func enumerateObjects(
options _: NSEnumerationOptions = [],
using: (AnyObject, UnsafeMutablePointer) -> Void)
}
array.enumerateObjects(options: .Reverse) { // OK
// ..
}
array.enumerateObjects() { // OK
// ..
}
為 Bool 語義的屬性添加 “is” 前綴
在 Objective-C 里,表達(dá) Bool 語義的屬性,使用對應(yīng)的 getter 方法作為這個屬性在 Swift 中的名字。例如:
objective-c
@interface NSBezierPath : NSObject
@property (readonly,getter=isEmpty) BOOL empty;
會變成:
extension NSBezierPath {
var isEmpty: Bool
}
if path.isEmpty { ... }
實現(xiàn)比較方法時遵從的準(zhǔn)則
現(xiàn)如今,為了實現(xiàn)對象比較,例如對 NSDate 來說,開發(fā)者經(jīng)常會擴展 NSDate 讓它遵從 Comparable 或使用 NSDate 的compare(_:) -> NSComparisonResult 方法。在這些場景里,對 NSDate 使用表操作符會有效提高代碼可讀性,例如 someDate < today 要比 someDate.comare(today) == .OrderedAscending 要清晰的多。由于轉(zhuǎn)換過程可以確定一個類是否實現(xiàn)了 Objective-C 中的比較方法,所有實現(xiàn)了這個方法的類都可以按照遵從 Comparable protocol 的方式引入。
不僅僅是 NSDate,F(xiàn)oundation 中的一些其它類也會被這個改變影響,例如:
func compare(other: NSDate) -> NSComparisonResult
func compare(decimalNumber: NSNumber) -> NSComparisonResult
func compare(otherObject: NSIndexPath) -> NSComparisonResult
func compare(string: String) -> NSComparisonResult
func compare(otherNumber: NSNumber) -> NSComparisonResult
對已有代碼的影響
這份提議中的改變?yōu)槭褂?Objective-C 框架的已有的 Swift 代碼引入了大量破壞性改變(breaking change)。以至于,我們需要一個遷移工具把 Swift 2 代碼遷移到 Swift 3。在實現(xiàn)過程中描述的 -swift3-migration 開關(guān)為這樣的轉(zhuǎn)換工具提供了基本信息。另外,編譯器需要為那些引用了舊版本 Objective-C 名稱的Swift代碼提供良好的錯誤信息(帶有修改建議),除此之外,還應(yīng)該提供一個輔助的通過舊版本名稱進(jìn)行查詢的機制。
聲明
為了最終形成 Swift API 設(shè)計指南 ,這份自動名稱轉(zhuǎn)換提議由Dmitri Hrybenko, Ted Kremenek, Chris Lattner, Alex Migicovsky, Max Moiseev, Ali Ozer和Tony Parker開發(fā)。
補充添加進(jìn)來的comparable部分之前由 Chris Amanse 提交到了 core-libraries 郵件列表中。Philippe Hausler 進(jìn)行 review 之后,添加到了這份提議中。