Swift 基础语法

原文

英文原文

简单值

类型不会自动转换,需要手动转换:

let label = "The width is "
let width = 94
let widthLabel = label + String(width)

字符串插值语法 ()

let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."

数组与字典

var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"

//初始化多个值
Array(repeating: GridItem(), count: 3)

var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"

//断言
let m1: [String:Int] = ["lisi": 13, "wangwu": 12]
let number = m1["lisi"]! + 5

let emptyArray: [String] = []
let emptyDictionary: [String: Float] = [:]

元组

var m: (max: Int, Int, sum:Int) = (1,2,3)
print(m.0)
print(m.max)
print(m.1)

控制流语句

for ... in ...

// 遍历数组
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
print(teamScore)

// 遍历字典
let interestingNumbers = [
    "Prime": [2, 3, 5, 7, 11, 13],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (_, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)
// 输出 "25"

if let xxx = XXX

var optionalString: String? = "Hello"
print(optionalString == nil)

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}

?? 默认值

let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)"

级联 ?

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength

switch case

let vegetable = "red pepper"
switch vegetable {
case "celery":
    print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
    print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
    print("Is it a spicy \(x)?")
default:
    print("Everything tastes good in soup.")
}

for ... in range

//不包含上界
var total = 0
for i in 0..<4 {
	total += i
}
print(total)

//包含上界
var total = 0
for i in 0...4 {
	total += i
}
print(total)

for (index,item) in xxx.enumertated

let names = ["Alice", "Bob", "John"]
        
names.enumerated().forEach { (index, name) in
    print("\(index): \(name)")
}

for (index, name) in names.enumerated() {
    print("\(index): \(name)")
}

函数与闭包

函数

func greet(person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person:"Bob", day: "Tuesday")

参数的 tag

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")

函数闭包

函数作为返回值

func makeIncrementer() -> ((Int) -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)

函数作为参数

func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -> Bool {
    return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)

闭包

numbers.map({
    (number: Int) -> Int in
    let result = 3 * number
    return result
})

注:其中关键字 in 前面是函数签名,后面是函数 body

简写 1

let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)

简写 2

let reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
print(reversedNames)

简写 3

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)

简写 4

// 直接传另一个运算符作为参数
let r  = names.sorted(by: >)

尾随闭包

// 函数的声明
func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}

// 正常使用闭包传参,**需要写参数标签**
someFunctionThatTakesAClosure(closure: {
    // 闭包主体部分
})

// 当需要将一个很长的闭包表达式作为最后一个参数传递给函数,尾随闭包是一个书写在**函数圆括号之后的闭包表达式,不用写出它的参数标签**
someFunctionThatTakesAClosure() {
    // 闭包主体部分
}

// 如果闭包表达式是函数或方法的唯一参数,还可以把 () 也省略掉:
someFunctionThatTakesAClosure {
    // 闭包主体部分
}

举例:

// 尾随闭包省略参数标签
reversedNames = names.sorted() { $0 > $1 }

// 尾随闭包省略参数标签与圆括号
reversedNames = names.sorted { $0 > $1 }

除了忽略尾闭包参数标签,还可以选择忽略第一个闭包的参数标签,但剩余闭包参数标签得写出来

//函数声明
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
    if let picture = download("photo.jpg", from: server) {
        completion(picture)
    } else {
        onFailure()
    }
}
//尾闭包调用
loadPicture(from: someServer) { picture in
    someView.currentPicture = picture
} onFailure: {
    print("Couldn't download the next picture.")
}

示例:(swiftui 中)

//列出所有参数标签
Button(action: {
    config.done()
}, label: {
    Text("Save")
})

//最后一个尾闭包不写参数标签,写在()后面
Button(action: {
    config.done()
}) {
    Text("Save")
}

//多个闭包,第一个尾闭包不写参数标签,其余尾闭包参数标签需要写出来
Button {
    config.done()
} label: {
    Text("Save")
}

自动闭包

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}

// 正常闭包,需要写花括号
serve(customer: { customersInLine.remove(at: 0) } )

func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
// 自动闭包,传参时不需要写花括号
serve(customer: customersInLine.remove(at: 0))

逃逸闭包

// 闭包参数不在函数内执行,而在函数外执行

// 存储闭包的数组
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    //将传进行的闭包参数放到外面的数组中,并不执行
    completionHandlers.append(completionHandler)
}

对象和类

类与构造函数

class NamedShape {
    var numberOfSides: Int = 0
    var name: String

    init(name: String) {
        self.name = name
    }

    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

注:每个属性都要有初始化 —— 无论是通过声明(就像 numberOfSides)还是通过构造器(就像 name)。

getter/setter 的使用

class Person {
    private var name: String
    
    private var _age: Int = 0
    
    var age: Int {
        get {
            _age
        }
        set {
            _age = newValue
        }
    }
    
    init(name: String) {
        self.name = name
    }
    
    func printName() {
        print("\(name)")
    }
}


let p = Person(name: "Lisi")
p.printName()
p.age = 15
print(p.age)

willSet/didSet 在设置一个新值之前或者之后运行代码

单例

// 日期格式化

class AppDateFormatter {
    // 初始化单例
    static let shared = AppDateFormatter()
    
    private init() {}
    
    private let mediumDateFormatter: DateFormatter = {
        let df = DateFormatter()
        df.dateStyle = .medium
        df.timeStyle = .none
        
        return df
    }()
    
    private let mediumTimeFormatter: DateFormatter = {
        let df = DateFormatter()
        df.dateStyle = .none
        df.timeStyle = .medium
        
        return df
    }()
    
    private let mediumDateTimeFormatter: DateFormatter = {
        let df = DateFormatter()
        df.dateStyle = .medium
        df.timeStyle = .medium
        
        return df
    }()

    func mediumDateString(from date: Date) -> String {
        return mediumDateFormatter.string(from: date)
    }
    
    func mediumTimeString(from date: Date) -> String {
        return mediumTimeFormatter.string(from: date)
    }
    
    func mediumDateTimeString(from date: Date) -> String {
        return mediumDateTimeFormatter.string(from: date)
    }
}

枚举

enum Rank: Int {
    case ace = 1
    case two, three, four, five, six, seven, eight, nine, ten
    case jack, queen, king
    func simpleDescription() -> String {
        switch self {
        case .ace:
            return "ace"
        case .jack:
            return "jack"
        case .queen:
            return "queen"
        case .king:
            return "king"
        default:
            return String(self.rawValue)
        }
    }
}
let ace = Rank.ace
let aceRawValue = ace.rawValue

if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}

枚举的简写

enum CompassPoint {
    case north;
    case south;
    case east;
    case west;
}

// 当被赋值的变量或参数的类型已知时,可使用点语法进行简写
var directionToHead: CompassPoint = .east

原始值(默认值)

enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

//当默认值为整数或者字符串类型的枚举时,Swift 将会自动赋值
// 整数
enum CompassPoint: Int {
    case north = 1;
    case south;
    case east;
    case west;
}

var directionToHead: CompassPoint = .south
// 打印1
print(directionToHead.rawValue)

// 字符串
enum CompassPoint:String {
    case north;
    case south;
    case east;
    case west;
}

var directionToHead: CompassPoint = .south
// 打印 south
print(directionToHead.rawValue)

关联值(枚举类的成员是一个构造函数)

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}

递归枚举(枚举类的成员中存在关联值类型是枚举类本身)

enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

// 在枚举类型开头加上 indirect 关键字来表明它的所有成员都是可递归的
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

结构体

使用 struct 来创建一个结构体。结构体和类有很多相同的地方,包括方法和构造器。它们之间最大的一个区别就是结构体是传值,类是传引用

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()

结构体和枚举是值类型。当它被赋值给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝。Swift 中所有的基本类型:整数(integer)、浮点数(floating-point number)、布尔值(boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,其底层也是使用结构体实现的。

协议

协议

protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}

类、枚举和结构体都可以遵循协议

class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += "  Now 100% adjusted."
    }
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription

struct SimpleStructure: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    // struct 中 mutating 关键字用来标记一个会修改结构体的方法
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription

属性

存储属性

简单来说,一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。

计算属性

除存储属性外,类、结构体和枚举可以定义计算属性。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。

必须使用 var 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let 关键字只用来声明常量属性,表示初始化后再也无法修改的值。

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}

简化 setter 声明

struct AlternativeRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set {
            // 如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 newValue。
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

只读计算属性

struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    // 只读计算属性的声明可以去掉 get 关键字和花括号
    var volume: Double {
        return width * height * depth
    }
}

属性观察器

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("将 totalSteps 的值设置为 \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("增加了 \(totalSteps - oldValue) 步")
            }
        }
    }
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// 将 totalSteps 的值设置为 200
// 增加了 200 步
stepCounter.totalSteps = 360
// 将 totalSteps 的值设置为 360
// 增加了 160 步
stepCounter.totalSteps = 896
// 将 totalSteps 的值设置为 896
// 增加了 536 步

属性包装器

定义一个属性包装器,你需要创建一个定义 wrappedValue 属性的结构体、枚举或者类。在下面的代码中,TwelveOrLess 结构体确保它包装的值始终是小于等于 12 的数字。如果要求它存储一个更大的数字,它则会存储 12 这个数字。

@propertyWrapper
struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

上面例子以 private 的方式声明 number 变量,这使得 number 仅在 TwelveOrLess 的实现中使用。写在其他地方的代码通过使用 wrappedValue 的 getter 和 setter 来获取这个值,但不能直接使用 number。有关 private 的更多信息

使用该属性包装器

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
print(rectangle.height)
// 打印 "0"

rectangle.height = 10
print(rectangle.height)
// 打印 "10"

rectangle.height = 24
print(rectangle.height)
// 打印 "12"

等价于(不使用注解的方式)

struct SmallRectangle {
    private var _height = TwelveOrLess()
    private var _width = TwelveOrLess()
    var height: Int {
        get { return _height.wrappedValue }
        set { _height.wrappedValue = newValue }
    }
    var width: Int {
        get { return _width.wrappedValue }
        set { _width.wrappedValue = newValue }
    }
}

属性包装器的初始化

@propertyWrapper
struct SmallNumber {
    private var maximum: Int
    private var number: Int

    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, maximum) }
    }

    init() {
        maximum = 12
        number = 0
    }
    init(wrappedValue: Int) {
        maximum = 12
        number = min(wrappedValue, maximum)
    }
    init(wrappedValue: Int, maximum: Int) {
        self.maximum = maximum
        number = min(wrappedValue, maximum)
    }
}

① 使用 init() 构造函数对属性构造器进行初始化

struct ZeroRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int
}

var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)

② 使用 init(wrappedValue:) 构造函数

struct UnitRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber var width: Int = 1
}

var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// 打印 "1 1"

③ 使用 init(wrappedValue: Int, maximum: Int) 构造函数

struct MixedRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber(maximum: 9) var width: Int = 2
}

var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// 打印 "1"

mixedRectangle.height = 20
print(mixedRectangle.height)
// 打印 "12"

属性包装器中的呈现值

除了被包装值,属性包装器可以通过定义被呈现值暴露出其他功能。

@propertyWrapper
struct SmallNumber {
    private var number: Int
    // 该变量可以反应当前属性是否发生改变
    private(set) var projectedValue: Bool

    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                // 当前属性有改变过
                projectedValue = true
            } else {
                number = newValue
                // 当前属性未发生改变
                projectedValue = false
            }
        }
    }

    init() {
        self.number = 0
        self.projectedValue = false
    }
}

enum Size {
    case small, large
}

struct SizedRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int

    mutating func resize(to size: Size) -> Bool {
        switch size {
        case .small:
            height = 10
            width = 20
        case .large:
            height = 100
            width = 100
        }
        // 返回语句检查 $height 和 $width 来确认是否属性包装器调整过 height 或 width。
        return $height || $width
    }
}

下标语法

subscript(index: Int) -> Int {
    get {
      // 返回一个适当的 Int 类型的值
    }
    set(newValue) {
      // 执行适当的赋值操作
    }
}

只读下标(省略 get 关联字和大括号)

struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")

类型下标(可理解为类的静态方法是下标方法)

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
    static subscript(n: Int) -> Planet {
        return Planet(rawValue: n)!
    }
}
let mars = Planet[4]
print(mars)

KeyPath

struct User {
    let name: String
}

let users = [
    User(name: "John"),
    User(name: "Alice"),
    User(name: "Bob")
]

// 单纯闭包
let userNames1 = users.map { $0.name }
// keyPath
let userNames2 = users.map(\.name)
// 等价于
let userNames3 = users.map { $0[keyPath: \.name] }

构造方法

结构体的逐一成员构造器

struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

无参构造器

// 如果所有属性都有默认值,则自动有一个无参构造器
class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

代理构造器

struct Rect {
    var origin = Point()
    var size = Size()
    init() {}
    
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    
    // 因为值类型(结构体与枚举类)没有继承,只能代理自己的构造器,而无法继承父类构造器
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}

类的必要构造器

class SomeClass {
    // 在类的构造器前添加 required 修饰符表明所有该类的子类都必须实现该构造器
    required init() {
        // 构造器的实现代码
    }
}

技巧

构造方法设置参数默认值,这样使用的时候可跳过传递该参数

struct User {
    // 通过 actions 中的 generate memberwise initializer 快速生成
    internal init(firstName: String, middleName: String? = nil, lastName: String) {
        self.firstName = firstName
        self.middleName = middleName
        self.lastName = lastName
    }
    
    let firstName: String
    let middleName: String?
    let lastName: String
}

添加新构造器,同时保留默认的成员初始化构造器

struct User {
    let firstName: String
    let middleName: String?
    let lastName: String
}

extension User {
    // 如果在 struct 直接声明构造器,则默认成员初始化构造器将被替代,在 extension 中则不会
    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
        self.middleName = nil
    }
}

// 使用示例
var u = User(firstName: "", middleName: "", lastName: "")
var u2 = User(firstName: "", lastName: "")

通过闭包设置成员属性的默认值

class SomeClass {
    let someProperty: SomeType = {
        // 在这个闭包中给 someProperty 创建一个默认值
        // someValue 必须和 SomeType 类型相同
        return someValue
    }()
// 注:闭包结尾的花括号后面接了一对空的小括号
}

扩展

使用 extension 来为现有的类型添加功能,比如新的方法和计算属性。你可以使用扩展让某个在别处声明的类型来遵守某个协议,这同样适用于从外部库或者框架引入的类型

extension Int: ExampleProtocol {
    var simpleDescription: String {
        return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
}
print(7.simpleDescription)

具有特定类型的实现的类型添加功能(或默认实现)

protocol P {
    func foo()
}

extension P where Self: UIViewController {
    func foo() {
        print("You can reference UIViewController property here: \(view)")
    }
}

错误处理

① do...catch

do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}

注:

do 代码块中,使用 try 来标记可以抛出错误的代码。在 catch 代码块中,除非你另外命名,否则错误会自动命名为 error

② 错误断言,出现异常直接 nil,否则为可选值

let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")

③ 资源回收 defer

var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]

func fridgeContains(_ food: String) -> Bool {
	fridgeIsOpen = true
	defer {
		fridgeIsOpen = false
	}
	
	let result = fridgeContent.contains(food)
	return result
}
fridgeContains("banana")
print(fridgeIsOpen)

泛型

// 重新实现 Swift 标准库中的可选类型
enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)

注:这里 .none 是因为父类型已经给出声明,故省略。

泛型限定

func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
    where T.Element: Equatable, T.Element == U.Element
{
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
    return false
}
anyCommonElements([1, 2, 3], [3])

注:

在类型名后面使用 where 来指定对类型的一系列需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类。

注2:

<T: Equatable><T> ... where T: Equatable 的写法是等价的。

不透明类型

一个不透明类型只能对应一个具体的类型,即便函数调用者并不能知道是哪一种类型;协议类型可以同时对应多个类型,只要它们都遵循同一协议。总的来说,协议类型更具灵活性,底层类型可以存储更多样的值,而不透明类型对这些底层类型有更强的限定。

不透明类型可以使用类型推断,在关联类型中常用到,而协议类型不行(因为无法确定具体类型)。

posted on 2022-12-02 21:19  Lemo_wd  阅读(31)  评论(0编辑  收藏  举报

导航