SE-0005 更好的把 Objective-C APIs 轉(zhuǎn)換成 Swift 版本

譯者:泊學(xué)

提交 review 前必讀

做為下面三份文檔的一部分,它們的內(nèi)容是彼此關(guān)聯(lián)的:

這三份文檔的內(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 2Swift 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 引入了以下這些改變:

  1. 泛化 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)校重命名的過程。
  2. 去掉冗余的類型名稱:Objective-C Cocoa 編碼指南要求方法聲明中要帶有每一個參數(shù)的描述。當(dāng)這個描述重申了參數(shù)的類型時,方法的名字就違背了 Swift 編碼指南中關(guān)于“忽略不需要的字符”的設(shè)計要求。因此,執(zhí)行翻譯時,我們應(yīng)該去掉那些描述類型的部分。
  3. 添加默認(rèn)參數(shù):如果 Objective-C API 的聲明強烈暗示參數(shù)需要參數(shù)默認(rèn)值,應(yīng)該為這樣的 API 在引入 Swift 時,添加參數(shù)默認(rèn)值。例如,一個表示選項集合的參數(shù),可以被設(shè)置成[]。
  4. 為第一個參數(shù)添加label:如果方法的第一個參數(shù)有默認(rèn)值, 應(yīng)該為這個參數(shù)設(shè)置一個參數(shù)label
  5. 給Bool語義的屬性添加“is”前綴Bool屬性應(yīng)該在讀取的時候,表達(dá)斷言的語義 ,但是 Objective-C Cocoa 編碼指南中,禁止在屬性名中使用單詞“is” 。因此,當(dāng)引入這樣的屬性時,為它們添加“is”前綴。
  6. 表達(dá)值語義的名字,首字母小寫:在 Swift API 設(shè)計指南中,要求對 “非類型聲明(non-type declarations)” 使用小寫字母。包括,enum 中的 case 以及屬性或函數(shù)的聲明。因此,引入Objective-C中沒有前綴的值時,讓這些名字的首字母小寫(例如:一個叫做URLHandler的屬性應(yīng)該變成urlHandler)。
  7. 讓實現(xiàn)compare(_:) -> NSComparisonResult的類遵從Comparable protocol:在 Objective-C 中,類對象的比較結(jié)果,都是通過“排序”的方式判斷的(注:例如 NSOrderedDescendingNSOrderedAscending )。導(dǎo)入過程中,通過讓這些類遵從 Comparable protocol,可以讓比較操作的實現(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 片段的方法,startWithQueuecompletionHandler。這個 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、NSUIntegerCGFloat 之外的 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 中的 Stringfunc appendString(_: NSString);
    • Selector 片段中的 Index 匹配類型名稱中的 Int,例如:func characterAtIndex(_: Int) -> unichar;
  • 集合相關(guān)匹配(Collection matches)

    • Selector 片段中的 IndexesIndices 匹配類型名稱中的 IndexSet,例如:func removeObjectsAtIndexes(_: NSIndexSet);
    • 如果 selector 片段中的復(fù)數(shù)名詞的單數(shù)形式匹配集合中元素的類型,那么 selector 中的復(fù)數(shù)名詞匹配集合類型名稱,例如:func arrangeObjects(_: [AnyObject]) -> [AnyObject]

特殊后綴匹配

  • 在 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

刪除類型名稱的步驟

我們按照下面的步驟刪除冗余的名字:

  1. 刪除頭部的結(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)
  1. 刪掉多余的介詞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)
  1. 在方法簽名的最后一個 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()
  1. 只要匹配到的類型名稱前面直接連接動詞,即便這個類型名稱在方法名中間也可以刪掉它,例如:
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 或使用 NSDatecompare(_:) -> 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 之后,添加到了這份提議中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容