Swift 特殊修飾符總結(jié)

@available

@available: 可用來(lái)標(biāo)識(shí)計(jì)算屬性、函數(shù)、類、協(xié)議、結(jié)構(gòu)體、枚舉等類型的生命周期。(依賴于特定的平臺(tái)版本 或 Swift 版本)。它的后面一般跟至少兩個(gè)參數(shù),參數(shù)之間以逗號(hào)隔開。其中第一個(gè)參數(shù)是固定的,代表著平臺(tái)和語(yǔ)言,可選值有以下這幾個(gè):

iOS
iOSApplicationExtension
macOS
macOSApplicationExtension
watchOS
watchOSApplicationExtension
tvOS
tvOSApplicationExtension
swift

可以使用*指代支持所有這些平臺(tái)。
有一個(gè)我們常用的例子,當(dāng)需要關(guān)閉ScrollView的自動(dòng)調(diào)整inset功能時(shí):

if #available(iOS 11.0, *) {

  scrollView.contentInsetAdjustmentBehavior = .never

} else {

  automaticallyAdjustsScrollViewInsets = false

}

還有一種用法是放在函數(shù)、結(jié)構(gòu)體、枚舉、類或者協(xié)議的前面,表示當(dāng)前類型僅適用于某一平臺(tái):

@available(iOS 12.0, *)
func adjustDarkMode() {
  /* code */
}
@available(iOS 12.0, *)
struct DarkModeConfig {
  /* code */
}
@available(iOS 12.0, *)
protocol DarkModeTheme {
  /* code */
}

版本和平臺(tái)的限定可以寫多個(gè):

@available(OSX 10.15, iOS 13, tvOS 13, watchOS 6, *)
public func applying(_ difference: CollectionDifference<Element>) -> ArraySlice<Element>?

注意:作為條件語(yǔ)句的available前面是#,作為標(biāo)記位時(shí)是@
剛才說(shuō)了,available后面參數(shù)至少要有兩個(gè),后面的可選參數(shù)這些:

deprecated:從指定平臺(tái)標(biāo)記為過期,可以指定版本號(hào)

obsoleted=版本號(hào):從指定平臺(tái)某個(gè)版本開始廢棄(注意棄用的區(qū)別,deprecated是還可以繼續(xù)使用,只不過是不推薦了,obsoleted是調(diào)用就會(huì)編譯錯(cuò)誤)該聲明
message=信息內(nèi)容:給出一些附加信息
unavailable:指定平臺(tái)上是無(wú)效的
renamed=新名字:重命名聲明

我們看幾個(gè)例子,這個(gè)是Array里flatMap的函數(shù)說(shuō)明:

@available(swift, deprecated: 4.1, renamed: "compactMap(_:)", message: "Please use compactMap(_:) for the case where closure returns an optional value")
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

它的含義是針對(duì)swift語(yǔ)言,該方式在swift4.1版本之后標(biāo)記為過期,對(duì)應(yīng)該函數(shù)的新名字為compactMap(:),如果我們?cè)?.1之上的版本使用該函數(shù)會(huì)收到編譯器的警告,即??Please use compactMap(:) for the case where closure returns an optional value。
在Realm庫(kù)里,有一個(gè)銷毀NotificationToken的方法,被標(biāo)記為unavailable:

extension RLMNotificationToken {
    @available(*, unavailable, renamed: "invalidate()")
    @nonobjc public func stop() { fatalError() }
}

標(biāo)記為unavailable就不會(huì)被編譯器聯(lián)想到。這個(gè)主要是為升級(jí)用戶的遷移做準(zhǔn)備,從可用stop()的版本升上了,會(huì)紅色報(bào)錯(cuò),提示該方法不可用。因?yàn)橛衦enamed,編譯器會(huì)推薦你用invalidate(),點(diǎn)擊fix就直接切換了。所以這兩個(gè)標(biāo)記參數(shù)常一起出現(xiàn)。

@discardableResult

帶返回的函數(shù)如果沒有處理返回值會(huì)被編譯器警告??。但有時(shí)我們就是不需要返回值的,這個(gè)時(shí)候我們可以讓編譯器忽略警告,就是在方法名前用@discardableResult聲明一下。可以參考Alamofire中request的寫法:

@discardableResult
public func request(
    _ url: URLConvertible,
    method: HTTPMethod = .get,
    parameters: Parameters? = nil,
    encoding: ParameterEncoding = URLEncoding.default,
    headers: HTTPHeaders? = nil)
    -> DataRequest
{
    return SessionManager.default.request(
        url,
        method: method,
        parameters: parameters,
        encoding: encoding,
        headers: headers
    )
}

@inlinable

這個(gè)關(guān)鍵詞是可內(nèi)聯(lián)的聲明,它來(lái)源于C語(yǔ)言中的inline。C中一般用于函數(shù)前,做內(nèi)聯(lián)函數(shù),它的目的是防止當(dāng)某一函數(shù)多次調(diào)用造成函數(shù)棧溢出的情況。因?yàn)槁暶鳛閮?nèi)聯(lián)函數(shù),會(huì)在編譯時(shí)將該段函數(shù)調(diào)用用具體實(shí)現(xiàn)代替,這么做可以省去函數(shù)調(diào)用的時(shí)間。
內(nèi)聯(lián)函數(shù)常出現(xiàn)在系統(tǒng)庫(kù)中,OC中的runtim中就有大量的inline使用:

static inline id autorelease(id obj)
{
    ASSERT(obj);
    ASSERT(!obj->isTaggedPointer());
    id *dest __unused = autoreleaseFast(obj);
    ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
    return obj;
}

Swift中的@inlinable和C中的inline基本相同,它在標(biāo)準(zhǔn)庫(kù)的定義中也廣泛出現(xiàn),可用于方法,計(jì)算屬性,下標(biāo),便利構(gòu)造方法或者deinit方法中。
例如Swift對(duì)Array中map函數(shù)的定義:
@inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
復(fù)制代碼其實(shí)Array中聲明的大部分函數(shù)前面都加了@inlinable,當(dāng)應(yīng)用某一處調(diào)用該方法時(shí),編譯器會(huì)將調(diào)用處用具體實(shí)現(xiàn)代碼替換。
需要注意內(nèi)聯(lián)聲明不能用于標(biāo)記為private或者fileprivate的地方。
這很好理解,對(duì)私有方法的內(nèi)聯(lián)是沒有意義的。內(nèi)聯(lián)的好處是運(yùn)行時(shí)更快,因?yàn)樗÷粤藦臉?biāo)準(zhǔn)庫(kù)調(diào)用map實(shí)現(xiàn)的步驟。但這個(gè)快也是有代價(jià)的,因?yàn)槭蔷幾g時(shí)做替換,這增加了編譯的開銷,會(huì)相應(yīng)的延長(zhǎng)編譯時(shí)間。
內(nèi)聯(lián)更多的是用于系統(tǒng)庫(kù)的特性,目前我了解的Swift三方庫(kù)中僅有CocoaLumberjack使用了@inlinable這個(gè)特性

@warn_unqualified_access (?kwɑ?l?fa?d)

通過命名我們可以推斷出其大概含義:對(duì)“不合規(guī)”的訪問進(jìn)行警告。這是為了解決對(duì)于相同名稱的函數(shù),不同訪問對(duì)象可能產(chǎn)生歧義的問題。
比如說(shuō),Swift 標(biāo)準(zhǔn)庫(kù)中Array和Sequence均實(shí)現(xiàn)了min()方法,而系統(tǒng)庫(kù)中也定義了min(::),對(duì)于可能存在的二義性問題,我們可以借助于@warn_unqualified_access。

extension Array where Self.Element : Comparable {
  @warn_unqualified_access
  @inlinable public func min() -> Element?
}
extension Sequence where Self.Element : Comparable {
  @warn_unqualified_access
  @inlinable public func min() -> Self.Element?
}

這個(gè)特性聲明會(huì)由編譯器在可能存在二義性的場(chǎng)景中對(duì)我們發(fā)出警告。這里有一個(gè)場(chǎng)景可以便于理解它的含義,我們自定義一個(gè)求Array中最小值的函數(shù):

extension Array where Element: Comparable {
    func minValue() -> Element? {
        return min()
    }
}

我們會(huì)收到編譯器的警告:Use of 'min' treated as a reference to instance method in protocol 'Sequence', Use 'self.' to silence this warning。它告訴我們編譯器推斷我們當(dāng)前使用的是Sequence中的min(),這與我們的想法是違背的。因?yàn)橛羞@個(gè)@warn_unqualified_access限定,我們能及時(shí)的發(fā)現(xiàn)問題,并解決問題:self.min()。

@objc

把這個(gè)特性用到任何可以在 Objective-C 中表示的聲明上——例如,非內(nèi)嵌類,協(xié)議,非泛型枚舉(原始值類型只能是整數(shù)),類和協(xié)議的屬性、方法(包括 setter 和 getter ),初始化器,反初始化器,下標(biāo)。 objc 特性告訴編譯器,這個(gè)聲明在 Objective-C 代碼中是可用的。

用 objc 特性標(biāo)記的類必須繼承自一個(gè) Objective-C 中定義的類。如果你把 objc 用到類或協(xié)議中,它會(huì)隱式地應(yīng)用于該類或協(xié)議中 Objective-C 兼容的成員上。如果一個(gè)類繼承自另一個(gè)帶 objc 特性標(biāo)記或 Objective-C 中定義的類,編譯器也會(huì)隱式地給這個(gè)類添加 objc 特性。標(biāo)記為 objc 特性的協(xié)議不能繼承自非 objc 特性的協(xié)議。

@objc還有一個(gè)用處是當(dāng)你想在OC的代碼中暴露一個(gè)不同的名字時(shí),可以用這個(gè)特性,它可以用于類,函數(shù),枚舉,枚舉成員,協(xié)議,getter,setter等。

// 當(dāng)在OC代碼中訪問enabled的getter方法時(shí),是通過isEnabledclass ExampleClass: NSObject {

    @objc var enabled: Bool {

        @objc(isEnabled) get {

            // Return the appropriate value

        }

    }

}

這一特性還可以用于解決潛在的命名沖突問題,因?yàn)镾wift有命名空間,常常不帶前綴聲明,而OC沒有命名空間是需要帶的,當(dāng)在OC代碼中引用Swift庫(kù),為了防止?jié)撛诘拿麤_突,可以選擇一個(gè)帶前綴的名字供OC代碼使用。

Charts作為一個(gè)在OC和Swift中都很常用的圖標(biāo)庫(kù),是需要較好的同時(shí)兼容兩種語(yǔ)言的使用的,所以也可以看到里面有大量通過@objc標(biāo)記對(duì)OC調(diào)用時(shí)的重命名代碼:

@objc(ChartAnimator)

open class Animator: NSObject { }

@objc(ChartComponentBase)

open class ComponentBase: NSObject { }

@ObjcMembers

因?yàn)镾wift中定義的方法默認(rèn)是不能被OC調(diào)用的,除非我們手動(dòng)添加@objc標(biāo)識(shí)。但如果一個(gè)類的方法屬性較多,這樣會(huì)很麻煩,于是有了這樣一個(gè)標(biāo)識(shí)符@objcMembers,它可以讓整個(gè)類的屬性方法都隱式添加@objc,不光如此對(duì)于類的子類、擴(kuò)展、子類的擴(kuò)展都也隱式的添加@objc,當(dāng)然對(duì)于OC不支持的類型,仍然無(wú)法被OC調(diào)用:

@objcMembersclass MyClass : NSObject {

  func foo() { }             // implicitly @objc

  func bar() -> (Int, Int)   // not @objc, because tuple returns

      // aren't representable in Objective-C

}

extension MyClass {

  func baz() { }   // implicitly @objc

}

class MySubClass : MyClass {

  func wibble() { }   // implicitly @objc

}

extension MySubClass {

  func wobble() { }   // implicitly @objc

}

參考:Swift3、4中的@objc、@objcMembers和dynamic

@testable

@testable是用于測(cè)試模塊訪問主target的一個(gè)關(guān)鍵詞。
因?yàn)闇y(cè)試模塊和主工程是兩個(gè)不同的target,在swift中,每個(gè)target代表著不同的module,不同module之間訪問代碼需要public和open級(jí)別的關(guān)鍵詞支撐。但是主工程并不是對(duì)外模塊,為了測(cè)試修改訪問權(quán)限是不應(yīng)該的,所以有了@testable關(guān)鍵詞。使用如下:

import XCTest@testable import Project

class ProjectTests: XCTestCase {

  /* code */

}

這時(shí)測(cè)試模塊就可以訪問那些標(biāo)記為internal或者public級(jí)別的類和成員了。

@frozen 和@unknown default

frozen意為凍結(jié),是為Swift5的ABI穩(wěn)定準(zhǔn)備的一個(gè)字段,意味向編譯器保證之后不會(huì)做出改變。為什么需要這么做以及這么做有什么好處,他們和ABI穩(wěn)定是息息相關(guān)的,內(nèi)容有點(diǎn)多就不放這里了,之后會(huì)單獨(dú)出一篇文章介紹,這里只介紹這兩個(gè)字段的含義。

@frozen public enum ComparisonResult : Int {

    case orderedAscending = -1

    case orderedSame = 0

    case orderedDescending = 1

}

@frozen public struct String {}

extension AVPlayerItem {

  public enum Status : Int {

    case unknown = 0

    case readyToPlay = 1

    case failed = 2

  }

}

ComparisonResult這個(gè)枚舉值被標(biāo)記為@frozen即使保證之后該枚舉值不會(huì)再變。注意到String作為結(jié)構(gòu)體也被標(biāo)記為@frozen,意為String結(jié)構(gòu)體的屬性及屬性順序?qū)⒉辉僮兓F鋵?shí)我們常用的類型像Int、Float、Array、Dictionary、Set等都已被“凍結(jié)”。需要說(shuō)明的是凍結(jié)僅針對(duì)struct和enum這種值類型,因?yàn)樗麄冊(cè)诰幾g器就確定好了內(nèi)存布局。對(duì)于class類型,不存在是否凍結(jié)的概念,可以想下為什么。

對(duì)于沒有標(biāo)記為frozen的枚舉AVPlayerItem.Status,則認(rèn)為該枚舉值在之后的系統(tǒng)版本中可能變化。

對(duì)于可能變化的枚舉,我們?cè)诹谐鏊衏ase的時(shí)候還需要加上對(duì)@unknown default的判斷,這一步會(huì)有編譯器檢查:

switch currentItem.status {

    case .readyToPlay:

        /* code */

    case .failed:

        /* code */

    case .unknown:

        /* code */

    @unknown default:

        fatalError("not supported")

}

幾個(gè)重要關(guān)鍵詞

lazy

lazy是懶加載的關(guān)鍵詞,當(dāng)我們僅需要在使用時(shí)進(jìn)行初始化操作就可以選用該關(guān)鍵詞。舉個(gè)例子:

class Avatar {

  lazy var smallImage: UIImage = self.largeImage.resizedTo(Avatar.defaultSmallSize)

  var largeImage: UIImage

  init(largeImage: UIImage) {

    self.largeImage = largeImage

  }

}

對(duì)于smallImage,我們聲明了lazy,如果我們不去調(diào)用它是不會(huì)走后面的圖片縮放計(jì)算的。但是如果沒有l(wèi)azy,因?yàn)槭浅跏蓟椒ǎ鼤?huì)直接計(jì)算出smallImage的值。所以lazy很好的避免的不必要的計(jì)算。

另一個(gè)常用lazy的地方是對(duì)于UI屬性的定義:

lazy var dayLabel: UILabel = {

    let label = UILabel()

   label.text = self.todayText()

    return label

}()

這里使用的是一個(gè)閉包,當(dāng)調(diào)用該屬性時(shí),執(zhí)行閉包里面的內(nèi)容,返回具體的label,完成初始化。

使用lazy你可能會(huì)發(fā)現(xiàn)它只能通過var初始而不能通過let,這是由lazy 的具體實(shí)現(xiàn)細(xì)節(jié)決定的:它在沒有值的情況下以某種方式被初始化,然后在被訪問時(shí)改變自己的值,這就要求該屬性是可變的。

另外我們可以在Sequences中使用lazy,在講解它之前我們先看一個(gè)例子:

func increment(x: Int) -> Int {

  print("Computing next value of \(x)")

  return x+1

}

let array = Array(0..<1000)let incArray = array.map(increment)print("Result:")print(incArray[0], incArray[4])

在執(zhí)行print("Result:")之前,Computing next value of ...會(huì)被執(zhí)行1000次,但實(shí)際上我們只需要0和4這兩個(gè)index對(duì)應(yīng)的值。

上面說(shuō)了序列也可以使用lazy,使用的方式是:

let array = Array(0..<1000)let incArray = array.lazy.map(increment)print("Result:")print(incArray[0], incArray[4])

// Result:// 1 5復(fù)制代碼

在執(zhí)行print("Result:")之前,并不會(huì)打印任何東西,只打印了我們用到的1和5。就是說(shuō)這里的lazy可以延遲到我們?nèi)≈禃r(shí)才去計(jì)算map里的結(jié)果。

我們看下這個(gè)lazy的定義:

@inlinable public var lazy: LazySequence<Array<Element>> { get }

它返回一個(gè)LazySequence的結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體里面包含了Array<Element>,而map的計(jì)算在LazySequence里又重新定義了一下:

/// Returns a LazyMapSequence over this Sequence. The elements of/// the result are computed lazily, each time they are read, by/// calling transform function on a base element.

@inlinable public func map<U>(_ transform: @escaping (Base.Element) -> U) -> LazyMapSequence<Base, U>

這里完成了lazy序列的實(shí)現(xiàn)。LazySequence類型的lazy只能被用于map、flatMap、compactMap這樣的高階函數(shù)中。

參考“懶”點(diǎn)兒好

糾錯(cuò):參考文章中說(shuō):"這些類型(LazySequence)只能被用在map,flatMap,filter這樣的高階函數(shù)中" 其實(shí)是沒有filter的,因?yàn)閒ilter是過濾函數(shù),它需要完整遍歷一遍序列才能完成過濾操作,是無(wú)法懶加載的,而且我查了LazySequence的定義,確實(shí)是沒有filter函數(shù)的。

unowned weak

Swift開發(fā)過程中我們會(huì)經(jīng)常跟閉包打交道,而用到閉包就不可避免的遇到循環(huán)引用問題。在Swift處理循環(huán)引用可以使用unowned和weak這兩個(gè)關(guān)鍵詞。看下面兩個(gè)例子:

class Dog {

    var name: String

    init (name: String ) {

        self.name = name

    }

    deinit {

        print("\(name) is deinitialized")

    }

}

class Bone {

   // weak 修飾詞

    weak var owner: Dog?

    init(owner: Dog?) {

        self.owner = owner

    }

    deinit {

        print("bone is deinitialized" )

    }

}

var lucky: Dog? = Dog(name: "Lucky")var bone: Bone? = Bone(owner: lucky!)

lucky =  nil// Lucky is deinitialized

這里Dog和Bone是相互引用的關(guān)系,如果沒有weak var owner: Dog?這里的weak聲明,將不會(huì)打印Lucky is deinitialized。還有一種解決循環(huán)應(yīng)用的方式是把weak替換為unowned關(guān)鍵詞。

weak相當(dāng)于oc里面的weak,弱引用,不會(huì)增加循環(huán)計(jì)數(shù)。主體對(duì)象釋放時(shí)被weak修飾的屬性也會(huì)被釋放,所以weak修飾對(duì)象就是optional。

·unowned相當(dāng)于oc里面的unsafe_unretained,它不會(huì)增加引用計(jì)數(shù),即使它的引用對(duì)象釋放了,它仍然會(huì)保持對(duì)被已經(jīng)釋放了的對(duì)象的一個(gè) "無(wú)效的" 引用,它不能是 Optional 值,也不會(huì)被指向nil。如果此時(shí)為無(wú)效引用,再去嘗試訪問它就會(huì)crash。

這兩者還有一個(gè)更常用的地方是在閉包里面:

lazy var someClosure: () -> Void = { [weak self] in
    // 被weak修飾后self為optional,這里是判斷self非空的操作                                
    guard let self = self else { retrun }
    self.doSomethings()
}

這里如果是unowned修飾self的話,就不需要用guard做解包操作了。但是我們不能為了省略解包的操作就用unowned,也不能為了安全起見全部weak,弄清楚兩者的適用場(chǎng)景非常重要。

根據(jù)蘋果的建議:

Define a capture in a closure as an unowned reference when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time.

當(dāng)閉包和它捕獲的實(shí)例總是相互引用,并且總是同時(shí)釋放時(shí),即相同的生命周期,我們應(yīng)該用unowned,除此之外的場(chǎng)景就用weak。

參考:內(nèi)存管理,WEAK 和 UNOWNED
Unowned 還是 Weak?生命周期和性能對(duì)比

KeyPath

KeyPath是鍵值路徑,最開始是用于處理KVC和KVO問題,后來(lái)又做了更廣泛的擴(kuò)展。

// KVC問題,支持struct、classstruct
 User {

    let name: String

    var age: Int

}

var user1 = User()

user1.name = "ferry"

user1.age = 18

 //使用KVC取值let path: KeyPath = \User.name

user1[keyPath: path] = "zhang"let name = user1[keyPath: path]print(name) //zhang

// KVO的實(shí)現(xiàn)還是僅限于繼承自NSObject的類型// playItem為AVPlayerItem對(duì)象

playItem.observe(\.status, changeHandler: { (_, change) in

    /* code */    

})

這個(gè)KeyPath的定義是這樣的:

public class AnyKeyPath : Hashable, _AppendKeyPath {}

/// A partially type-erased key path, from a concrete root type to any/// resulting value type.public class PartialKeyPath<Root> : AnyKeyPath {}

/// A key path from a specific root type to a specific resulting value type.public class KeyPath<Root, Value> : PartialKeyPath<Root> {}

定義一個(gè)KeyPath需要指定兩個(gè)類型,根類型和對(duì)應(yīng)的結(jié)果類型。對(duì)應(yīng)上面示例中的path:

let path: KeyPath<User, String> = \User.name復(fù)制代碼

根類型就是User,結(jié)果類型就是String。也可以不指定,因?yàn)榫幾g器可以從\User.name推斷出來(lái)。那為什么叫根類型的?可以注意到KeyPath遵循一個(gè)協(xié)議_AppendKeyPath,它里面定義了很多append的方法,KeyPath是多層可以追加的,就是如果屬性是自定義的Address類型,形如:

struct Address {

    var country: String = ""

}let path: KeyPath<User, String> = \User.address.country復(fù)制代碼

這里根類型為User,次級(jí)類型是Address,結(jié)果類型是String。所以path的類型依然是KeyPath<User, String>。

明白了這些我們可以用KeyPath做一些擴(kuò)展:

extension Sequence {

    func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] {

        return sorted { a, b in

            return a[keyPath: keyPath] < b[keyPath: keyPath]

        }

    }

}// users is Array<User>let newUsers = users.sorted(by: \.age)

這個(gè)自定義sorted函數(shù)實(shí)現(xiàn)了通過傳入keyPath進(jìn)行升序排列功能。

參考:The power of key paths in Swift

some

some是Swift5.1新增的特性。它的用法就是修飾在一個(gè) protocol 前面,默認(rèn)場(chǎng)景下 protocol 是沒有具體類型信息的,但是用some 修飾后,編譯器會(huì)讓 protocol 的實(shí)例類型對(duì)外透明。

可以通過一個(gè)例子理解這段話的含義,當(dāng)我們嘗試定義一個(gè)遵循Equatable協(xié)議的value時(shí):

// Protocol 'Equatable' can only be used as a generic constraint because it has Self or associated type requirements
var value: Equatable {
    return 1
}

var value: Int {
    return 1
}

編譯器提示我們Equatable只能被用來(lái)做泛型的約束,它不是一個(gè)具體的類型,這里我們需要使用一個(gè)遵循Equatable的具體類型(Int)進(jìn)行定義。但有時(shí)我們并不想指定具體的類型,這時(shí)就可以在協(xié)議名前加上some,讓編譯器自己去推斷value的類型:

var value: some Equatable {
    return 1
}

在SwiftUI里some隨處可見:

struct ContentView: View {
    var body: some View {
        Text("Hello World")
    }
}

這里使用some就是因?yàn)閂iew是一個(gè)協(xié)議,而不是具體類型。

當(dāng)我們嘗試欺騙編譯器,每次隨機(jī)返回不同的Equatable類型:

var value: some Equatable {
    if Bool.random() {
        return 1
    } else {
        return "1"
    }
}

聰明的編譯器是會(huì)發(fā)現(xiàn)的,并警告我們

Function declares an opaque return type, but the return statements in its body do not have matching underlying types。
翻譯: 函數(shù)聲明一個(gè)不透明的返回類型,但是函數(shù)體中的返回語(yǔ)句沒有匹配的基礎(chǔ)類型

感謝@zhangferry

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,860評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,025評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,421評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,642評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,177評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,970評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,157評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,410評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,896評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,157評(píng)論 2 375