Swift6.1之基础知识5 2025-06-18 周三

宏会在编译你的源代码时对其进行转换,从而让你避免手动编写重复的代码。在编译过程中,Swift 会先展开代码中的所有宏,然后再像往常一样构建代码。
宏展开始终是一种加法操作:宏会添加新代码,但绝不会删除或修改现有代码。

独立宏

要调用独立宏,需要在其名称前写入井号 (#),并在名称后的括号中写入宏的参数。

// #function; #warning() 都是Swift 标准库中的宏
func myFunction() {
    print("Currently running \(#function)")  // 打印 “Currently running myFunction()”
    #warning("Something's wrong")
}

附加宏

要调用附加宏,需要在其名称前写入 at 符号 (@) ,并在名称后的括号中写入宏的参数。
附加宏会修改它们所附加到的声明。它们为被附加到的声明添加代码,比如定义一个新的方法或者增加对某个协议的遵循。

  • 不使用宏的代码:
struct SundaeToppings: OptionSet {
    let rawValue: Int
    static let nuts = SundaeToppings(rawValue: 1 << 0)
    static let cherry = SundaeToppings(rawValue: 1 << 1)
    static let fudge = SundaeToppings(rawValue: 1 << 2)
}
  • 下面是该代码使用宏后的替代版本:
@OptionSet<Int>
struct SundaeToppings {
    private enum Options: Int {
        case nuts
        case cherry
        case fudge
    }
}
  • 作为对比,@OptionSet 宏展开后是下面这样。这段代码不是由你自己编写的,只有当你特别要求 Swift 展示宏的展开时,你才会看到它。
struct SundaeToppings {
    private enum Options: Int {
        case nuts
        case cherry
        case fudge
    }

    typealias RawValue = Int
    var rawValue: RawValue
    init() { self.rawValue = 0 }
    init(rawValue: RawValue) { self.rawValue = rawValue }
    static let nuts: Self = Self(rawValue: 1 << Options.nuts.rawValue)
    static let cherry: Self = Self(rawValue: 1 << Options.cherry.rawValue)
    static let fudge: Self = Self(rawValue: 1 << Options.fudge.rawValue)
}
extension SundaeToppings: OptionSet { }

宏的声明

  • 在大多数 Swift 代码中,当你实现某个符号(如函数或类型)时,不需要单独额外的声明。但是,宏的声明和实现是分开的。

  • 宏的声明包含其名称、所需的参数、可以被使用的位置以及它可以生成怎样的代码。

  • 宏的实现则包含通过生成 Swift 代码来展开这个宏所需的代码。

  • 你可以使用 macro 关键字引入一个宏的声明。

public macro OptionSet<RawType>() =
        #externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
  • 附加宏的名称使用大驼峰式命名法,就像结构体和类的名称那样。

  • 独立宏的名称使用小驼峰式命名法,就像变量和函数的名称那样。

  • 宏的可访问性总是被声明为 public 的。 由于声明宏的代码与使用宏的代码位于不同的模块中,因此没有任何地方可以应用一个非公开可访问的宏。

  • 对于独立宏,你可以编写 @freestanding 属性来指定其角色:

@freestanding(expression)
public macro line<T: ExpressibleByIntegerLiteral>() -> T =
        /* ... 宏实现的位置 ... */

// 上面的 #line 宏具有 expression(表达式)的角色。表达式宏可以产生一个值,或者执行一个编译时操作,比如生成一个警告。

宏的展开

在构建使用了宏的 Swift 代码时,编译器会调用宏的实现来展开它们。具体来说,Swift 会以以下方式展开宏:

  1. 编译器读取代码,创建语法的内存表示。
  2. 编译器将部分内存表示发送给宏的实现,宏将在此基础上展开。
  3. 编译器将宏的调用替换为它的展开形式。
  4. 编译器使用展开后的源代码继续进行编译。

实现一个宏

要实现一个宏,你需要两个组件:一个是执行这个宏展开的类型,另一个是用来声明这个宏并将其暴露为 API 的库。

类型转换

类型转换是一种检查实例类型,或将该实例视为其类层次结构中不同父类或子类的方法。
Swift 中的类型转换是通过 is 和 as 操作符实现的。这两个操作符为值的类型检查或类型转换提供了一种简单而富有表现力的方法

  • 你可以使用类型转换在类和子类的层次结构中检查某个类实例的类型,并将该实例转换为同一层次结构中的另一个类。
class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class Movie: MediaItem {
    var director: String
    init(name: String, director: String) {
        self.director = director
        super.init(name: name)
    }
}

class Song: MediaItem {
    var artist: String
    init(name: String, artist: String) {
        self.artist = artist
        super.init(name: name)
    }
}

let library = [
    Movie(name: "Casablanca", director: "Michael Curtiz"),
    Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
    Movie(name: "Citizen Kane", director: "Orson Welles"),
    Song(name: "The One And Only", artist: "Chesney Hawkes"),
    Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]
// "library" 的类型被推断为 [MediaItem]
// library 中存储的项目实际上仍然是 Movie 和 Song 实例。但是,如果遍历该数组的内容,返回的项目类型是 MediaItem,而不是 Movie 或 Song。为了以原始类型处理它们,你需要检查它们的类型,或将它们向下转型为不同的类型

检查类型

使用类型检查操作符 (is) 来检查实例是否属于某个子类类型。如果实例属于该子类类型,则类型检查操作符返回 true;反之则返回 false。

var movieCount = 0
var songCount = 0

for item in library {
    if item is Movie {
        movieCount += 1
    } else if item is Song {
        songCount += 1
    }
}

print("Media library contains \(movieCount) movies and \(songCount) songs")
// 打印 "Media library contains 2 movies and 3 songs"

向下转型

  • 某个类常量或变量可能实际上指向子类的实例。如果你认为情况确实如此,可以尝试使用类型转换操作符(as? 或 as!)来向下转型为子类类型。
  • 如果不确定向下转型能否成功,请使用类型转换操作符的条件形式(as?)。这种形式的操作符将始终返回一个可选值,如果向下转型不成功,该值将为 nil。这样,你就可以检查是否成功进行了向下转型。
  • 只有在确定向下转型一定会成功时,才使用类型转换操作符的强制形式(as!)。如果尝试向下转型到一个不正确的类类型,这种形式的操作符会触发运行时错误。
for item in library {
    if let movie = item as? Movie {
        print("Movie: \(movie.name), dir. \(movie.director)")
    } else if let song = item as? Song {
        print("Song: \(song.name), by \(song.artist)")
    }
}

// Movie: Casablanca, dir. Michael Curtiz
// Song: Blue Suede Shoes, by Elvis Presley
// Movie: Citizen Kane, dir. Orson Welles
// Song: The One And Only, by Chesney Hawkes
// Song: Never Gonna Give You Up, by Rick Astley

Any 和 AnyObject

Swift 为处理非特定类型提供了两种通用类型:

  • Any 可以表示任何类型的实例,包括函数类型。
  • AnyObject 可以表示任何类类型的实例。

只有在明确需要它们提供的行为和功能时,才使用 Any 和 AnyObject。在代码中最好明确指定你希望使用的类型。

var things: [Any] = []

things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })

for thing in things {
    switch thing {
    case 0 as Int:
        print("zero as an Int")
    case 0 as Double:
        print("zero as a Double")
    case let someInt as Int:
        print("an integer value of \(someInt)")
    case let someDouble as Double where someDouble > 0:
        print("a positive double value of \(someDouble)")
    case is Double:
        print("some other double value that I don't want to print")
    case let someString as String:
        print("a string value of \"\(someString)\"")
    case let (x, y) as (Double, Double):
        print("an (x, y) point at \(x), \(y)")
    case let movie as Movie:
        print("a movie called \(movie.name), dir. \(movie.director)")
    case let stringConverter as (String) -> String:
        print(stringConverter("Michael"))
    default:
        print("something else")
    }
}

// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called Ghostbusters, dir. Ivan Reitman
// Hello, Michael
  • Any 类型代表任何类型的值,包括可选类型。如果你使用的是可选值,而预期值是 Any 类型,Swift 会发出警告。如果你您确实需要将可选值用作 Any 值,可以使用 as 操作符显式地将可选值转换为 Any 值,如下所示。
let optionalNumber: Int? = 3
things.append(optionalNumber)        // 会警告
things.append(optionalNumber as Any) // 不会警告

嵌套类型

要在一个类型中嵌套另一个类型,只需将其定义写在外部类型的大括号内。类型可以根据需要嵌套到任意层次。
要在定义上下文之外使用嵌套类型,需要在其名称前加上其外部类型的名称前缀:

struct BlackjackCard {
    // 嵌套的 Suit 枚举
    enum Suit: Character {
        case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
    }

    // 嵌套的 Rank 枚举
    enum Rank: Int {
        case two = 2, three, four, five, six, seven, eight, nine, ten
        case jack, queen, king, ace
        struct Values {
            let first: Int, second: Int?
        }
        var values: Values {
            switch self {
            case .ace:
                return Values(first: 1, second: 11)
            case .jack, .queen, .king:
                return Values(first: 10, second: nil)
            default:
                return Values(first: self.rawValue, second: nil)
            }
        }
    }

    // BlackjackCard 属性和方法
    let rank: Rank, suit: Suit
    var description: String {
        var output = "suit is \(suit.rawValue),"
        output += " value is \(rank.values.first)"
        if let second = rank.values.second {
            output += " or \(second)"
        }
        return output
    }
}

let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades)
print("theAceOfSpades: \(theAceOfSpades.description)")
// 输出 "theAceOfSpades: suit is ♠, value 是 1 或者 11"

let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
// heartsSymbol 是 "♡"

扩展

扩展(Extensions) 用于为现有的类、结构体、枚举或协议类型添加新功能。这包括了扩展那些您无法访问原始源代码的类型的能力(即追溯建模)。扩展和 Objective-C 的分类很相似。

语法

  • 使用 extension 关键字声明扩展:
extension SomeType {
    // 在这里给 SomeType 添加新的功能
}
  • 扩展可以扩充一个现有的类型,给它添加一个或多个协议。在添加协议的遵循声明时,协议名称的写法和类或者结构体一样:
extension SomeType: SomeProtocol, AnotherProtocol {
    // 协议所需要的实现写在这里
}

计算属性

  • 扩展展可以给现有类型添加计算实例属性和计算类属性。
  • 扩展可以添加新的计算属性,但是它们不能添加存储属性,也不能为现有属性添加属性观察器。
extension Double {
    var km: Double { return self * 1_000.0 }
    var m: Double { return self }
    var cm: Double { return self / 100.0 }
    var mm: Double { return self / 1_000.0 }
    var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
print("One inch is \(oneInch) meters")
// 打印“One inch is 0.0254 meters”
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// 打印“Three feet is 0.914399970739201 meters”
let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long")
// 打印“A marathon is 42195.0 meters long”

构造器

  • 扩展可以为现有类型添加新的构造器。这使你可以扩展其他类型以接受你自己的自定义类型作为构造器参数,或提供类型的原始实现中未包含的其他构造选项。
  • 扩展可以为一个类添加新的便利构造器(convenience initializer),但是它们不能为一个类添加新的指定构造器(designated initializer)或析构器(deinitializer)。指定构造器和析构器必须始终由类的原始实现提供。
struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
}

let defaultRect = Rect()
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
    size: Size(width: 5.0, height: 5.0))

你可以通过扩展 Rect 结构体来额外提供一个允许指定 center 和 size 的构造器:

extension Rect {
    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)
    }
}

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
    size: Size(width: 3.0, height: 3.0))
// centerRect 的 origin 是 (2.5, 2.5) 并且它的 size 是 (3.0, 3.0)

方法

扩展可以为现有类型添加新的实例方法和类方法。

extension Int {
    func repetitions(task: () -> Void) {
        for _ in 0..<self {
            task()
        }
    }
}

3.repetitions {
    print("Hello!")
}
// Hello!
// Hello!
// Hello!

下标

扩展可以为现有类型添加新的下标。

extension Int {
    subscript(digitIndex: Int) -> Int {
        var decimalBase = 1
        for _ in 0..<digitIndex {
            decimalBase *= 10
        }
        return (self / decimalBase) % 10
    }
}
746381295[0]
// 返回 5
746381295[1]
// 返回 9
746381295[2]
// 返回 2
746381295[8]
// 返回 7

嵌套类型

扩展可以给现有的类,结构体,和枚举添加新的嵌套类型:

extension Int {
    enum Kind {
        case negative, zero, positive
    }
    var kind: Kind {
        switch self {
        case 0:
            return .zero
        case let x where x > 0:
            return .positive
        default:
            return .negative
        }
    }
}

func printIntegerKinds(_ numbers: [Int]) {
    for number in numbers {
        switch number.kind {
        case .negative:
            print("- ", terminator: "")
        case .zero:
            print("0 ", terminator: "")
        case .positive:
            print("+ ", terminator: "")
        }
    }
    print("")
}
printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
// 打印“+ + - 0 - 0 + ”
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容