Swift 協議

協議

  • 一、協議語法
  • 二、屬性要求
  • 三、方法要求
  • 四、異變方法要求
  • 五、構造器要求
    • 1、協議構造器要求的類實現
    • 2.可失敗構造器要求
  • 六、協議作為類型
  • 七、委托
  • 八、在擴展里添加協議遵循
  • 九、有條件地遵循協議
  • 十、在擴展里聲明采納協議
  • 十一、使用合成實現來采納協議
  • 十二、協議類型的集合
  • 十三、協議的繼承
  • 十四、類專屬的協議
  • 十五、協議合成
  • 十六、檢查協議一致性
  • 十七、可選的協議要求
  • 十八、協議擴展
    • 1、提供默認實現
    • 2、為協議擴展添加限制條件

協議定義了一個藍圖,規定了用來實現某一個特定任務或者功能的方法、屬性,以及其他需要的東西。類、結構體或枚舉都可以遵循協議,并為協議定義的這些要求提供具體實現。某個類型能夠滿足某個協議的要求,就可以說該類型遵循這個協議。

除了遵循協議的類型必須實現的要求外,還可以對協議進行擴展,通過擴展來實現一部分要求或者實現一些附加功能,這樣遵循協議的類型就能夠使用這些功能。

一、協議語法

協議的定義方式與類、結構體和枚舉的定義非常相似:

protocol SomeProtocol {// 這里是協議的定義部分
}

要讓自定義類型遵循某個協議,在定義類型時,需要在類型名稱后加上協議名稱,中間以冒號(:)分隔。遵循多個協議時,各協議之間用逗號(,)分隔:

struct SomeStructure: FirstProtocol, AnotherProtocol {// 這里是結構體的定義部分
}

若是一個類擁有父類,應該將父類名放在遵循的協議名之前,以逗號分隔:

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {// 這里是類的定義部分
}

二、屬性要求

協議可以要求遵循協議的類型提供特定名稱和類型的實例屬性或類型屬性。協議不指定屬性是存儲屬性還是計算屬性,它只指定屬性的名稱和類型。此外,協議還指定屬性是可讀的還是可讀可寫的。

如果協議要求屬性是可讀可寫的,那么該屬性不能時常量屬性或只讀的計算型屬性。如果協議只要求屬性是可讀的,那么該屬性不僅可以是可讀的,如果代碼需要的話,還可以是可寫的。

協議總是用 var 關鍵字來聲明變量屬性,在類型聲明后加上 { set get } 來表示屬性是可讀可寫的,可讀屬性則用 { get } 來表示:

protocol SomeProtocol {var mustBeSettable: Int { get set }var doesNotNeedToBeSettable: Int { get }
}

在協議中定義類型屬性時,總是使用 static 關鍵字作為前綴。當類類型遵循協議時,除了 static 關鍵字,還可以使用 class 關鍵字來聲明類型屬性:

protocol AnotherProtocol {static var someTypeProperty: Int { get set }
}

如下所示,這是一個只含有一個實例屬性要求的協議:

protocol FullyNamed {var fullName: String { get }
}

FullyNamed 協議除了要求遵循協議的類型提供 fullName 屬性外,并沒有其他特別的要求。這個協議表示,任何遵循 FullyNamed 的類型,都必須有一個可讀的 String 類型的實例屬性 fullName

下面是一個遵循 FullyNamed 協議的簡單結構體:

struct Person: FullyNamed {var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName 為 "John Appleseed"

這個例子中定義了一個叫做 Person 的結構體,用來表示一個具有名字的人。從第一行代碼可以看出,它遵循了 FullyNamed 協議。

Person 結構體的每一個實例都有一個 String 類型的存儲型屬性 fullName。這正好滿足了 FullyNamed 協議的要求,也就意味著 Person 結構體正確地遵循了協議。(如果協議要求未被完全滿足,在編譯時會報錯。)

下面是一個更為復雜的類,它采納并遵循了 FullyNamed 協議:

class Starship: FullyNamed {var prefix: String?var name: Stringinit(name: String, prefix: String? = nil) {self.name = nameself.prefix = prefix}var fullName: String {return (prefix != nil ? prefix! + " " : "") + name}
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName 為 "USS Enterprise"

Starship 類把 fullName 作為只讀的計算屬性來實現。每一個 Starship 類的實例都有一個名為 name 的非可選屬性和一個名為 prefix 的可選屬性。 當 prefix 存在時,計算屬性 fullName 會將 prefix 插入到 name 之前,從而得到一個帶有 prefixfullName

三、方法要求

協議可以要求遵循協議的類型實現某些指定的實例方法或類方法,這些方法作為協議的一部分,像普通方法一樣放在協議的定義中,但是不需要大括號和方法體。可以在協議中定義具有可變參數的方法,和普通方法的定義方式相同。

協議可以要求遵循協議的類型實現某些指定的實例方法或類方法。這些方法作為協議的一部分,像普通方法一樣放在協議的定義中,但是不需要大括號和方法體。可以在協議中定義具有可變參數的方法,和普通方法的定義方式相同。但是,不支持為協議中的方法提供默認參數。

正如屬性要求中所述,在協議中定義類方法的時候,總是使用 static 關鍵字作為前綴。即使在類實現時,類方法要求使用 classstatic 作為關鍵字前綴,前面的規則仍然適用:

protocol SomeProtocol {static func someTypeMethod()
}

下面的例子定義了一個只含有一個實例方法的協議:

protocol RandomNumberGenerator {func random() -> Double
}

RandomNumberGenerator 協議要求遵循協議的類型必須擁有一個名為 random, 返回值類型為 Double 的實例方法。盡管這里并未指明,但是我們假設返回值是從 0.0 到(但不包括)1.0

RandomNumberGenerator 協議并不關心每一個隨機數是怎樣生成的,它只要求必須提供一個隨機數生成器。

如下所示,下邊是一個遵循并符合 RandomNumberGenerator 協議的類。該類實現了一個叫做 線性同余生成器(linear congruential generator) 的偽隨機數算法。

class LinearCongruentialGenerator: RandomNumberGenerator {var lastRandom = 42.0let m = 139968.0let a = 3877.0let c = 29573.0func random() -> Double {lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))return lastRandom / m}
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// 打印 “Here's a random number: 0.37464991998171”
print("And another one: \(generator.random())")
// 打印 “And another one: 0.729023776863283”

四、異變方法要求

有時需要在方法中改變(或異變)方法所屬的實例。例如,在值類型(即結構體和枚舉)的實例方法中,將 mutating 關鍵字作為方法的前綴,寫在 func 關鍵字之前,表示可以在該方法中修改它所屬的實例以及實例的任意屬性的值。

如果你在協議中定義了一個實例方法,該方法會改變遵循該協議的類型的實例,那么在定義協議時需要再方法前加 mutating 關鍵字。這使得結構體和枚舉能夠遵循此協議并滿足此方法要求。

注意

實現協議中的 mutating 方法時,若是類類型,則不用寫 mutating 關鍵字。而對于結構體和枚舉,則必須寫 mutating 關鍵字。

如下所示,Togglable 協議只定義了一個名為 toggle 的實例方法。顧名思義,toggle() 方法將改變實例屬性,從而切換遵循該協議類型的實例的狀態。

toggle() 方法在定義的時候,使用 mutating 關鍵字標記,這表明當它被調用時,該方法將會改變遵循協議的類型的實例:

protocol Togglable {mutating func toggle()
}

當使用枚舉或結構體來實現 Togglable 協議時,需要提供一個帶有 mutating 前綴的 toggle() 方法。

下面定義了一個名為 OnOffSwitch 的枚舉。這個枚舉在兩種狀態之間進行切換,用枚舉成員 OnOff 表示。枚舉的 toggle() 方法被標記為 mutating,以滿足 Togglable 協議的要求:

enum OnOffSwitch: Togglable {case off, onmutating func toggle() {switch self {case .off:self = .oncase .on:self = .off}}
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
// lightSwitch 現在的值為 .on

五、構造器要求

協議可以要求遵循協議的類型實現指定的構造器。你可以像編寫普通構造器那樣,在協議的定義里寫下構造器的聲明,但不需要寫花括號和構造器的實體:

protocol SomeProtocol {init(someParameter: Int)
}

1、協議構造器要求的類實現

你可以在遵循協議的類中實現構造器,無論是作為指定構造器,還是作為便利構造器。無論哪種情況,你都必須為構造器實現標上 required 修飾符:

class SomeClass: SomeProtocol {required init(someParameter: Int) {// 這里是構造器的實現部分}
}

使用 required 修飾符可以確保所有子類也必須提供此構造器實現,從而也能遵循協議。

注意

如果類已經被標記為 final,那么不需要在協議構造器的實現中使用 required 修飾符,因為 final 類不能有子類。關于 final 修飾符的更多內容,請參見 防止重寫。

如果一個子類重寫了父類的指定構造器,并且該構造器滿足了某個協議的要求,那么該構造器的實現需要同時標注 requiredoverride 修飾符:

protocol SomeProtocol {init()
}
class SomeSuperClass {init() {// 這里是構造器的實現部分}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {// 因為遵循協議,需要加上 required// 因為繼承自父類,需要加上 overriderequired override init() {// 這里是構造器的實現部分}
}

2.可失敗構造器要求

協議還可以為遵循協議的類型定義可失敗構造器要求,詳見 可失敗構造器。

遵循協議的類型可以通過可失敗構造器(init?)或非可失敗構造器(init)來滿足協議中定義的可失敗構造器要求。協議中定義的非可失敗構造器要求可以通過非可失敗構造器(init)或隱式解包可失敗構造器(init!)來滿足。

六、協議作為類型

盡管協議本身并未實現任何功能,但是協議可以被當做一個功能完備的類型來使用。協議作為類型使用,有時被稱作「存在類型」,這個名詞來自「存在著一個類型 T,該類型遵循協議 T」。

協議可以像其他普通類型一樣使用,使用場景如下:

  • 作為函數、方法或構造器中的參數類型或返回值類型
  • 作為常量、變量或屬性的類型
  • 作為數組、字典或其他容器中的元素類型

注意

協議是一種類型,因此協議類型的名稱應與其他類型(例如 IntDoubleString)的寫法相同,使用大寫字母開頭的駝峰式寫法,例如(FullyNamedRandomNumberGenerator)。

下面是將協議作為類型使用的例子:

class Dice {let sides: Intlet generator: RandomNumberGeneratorinit(sides: Int, generator: RandomNumberGenerator) {self.sides = sidesself.generator = generator}func roll() -> Int {return Int(generator.random() * Double(sides)) + 1}
}

例子中定義了一個 Dice 類,用來代表桌游中擁有 N 個面的骰子。Dice 的實例含有 sidesgenerator 兩個屬性,前者是整型,用來表示骰子有幾個面,后者為骰子提供一個隨機數生成器,從而生成隨機點數。

generator 屬性的類型為 RandomNumberGenerator,因此任何遵循了 RandomNumberGenerator 協議的類型的實例都可以賦值給 generator,除此之外并無其他要求。并且由于其類型是 RandomNumberGenerator,在 Dice 類中與 generator 交互的代碼,必須適用于所有 generator 實例都遵循的方法。這句話的意思是不能使用由 generator 底層類型提供的任何方法或屬性。但是你可以通過向下轉型,從協議類型轉換成底層實現類型,比如從父類向下轉型為子類。請參考 向下轉型。

Dice 類還有一個構造器,用來設置初始狀態。構造器有一個名為 generator,類型為 RandomNumberGenerator 的形參。在調用構造方法創建 Dice 的實例時,可以傳入任何遵循 RandomNumberGenerator 協議的實例給 generator

Dice 類提供了一個名為 roll 的實例方法,用來模擬骰子的面值。它先調用 generatorrandom() 方法來生成一個 [0.0,1.0) 區間內的隨機數,然后使用這個隨機數生成正確的骰子面值。因為 generator 遵循了 RandomNumberGenerator 協議,可以確保它有個 random() 方法可供調用。

下面的例子展示了如何使用 LinearCongruentialGenerator 的實例作為隨機數生成器來創建一個六面骰子:

var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {print("Random dice roll is \(d6.roll())")
}
// Random dice roll is 3
// Random dice roll is 5
// Random dice roll is 4
// Random dice roll is 5
// Random dice roll is 4

七、委托

委托是一種設計模式,它允許類或結構體將一些需要它們負責的功能委托給其他類型的實例。委托模式的實現很簡單:定義協議來封裝那些需要被委托的功能,這樣就能確保遵循協議的類型能提供這些功能。委托模式可以用來響應特定的動作,或者接收外部數據源提供的數據,而無需關心外部數據源的類型。

下面的例子定義了兩個基于骰子游戲的協議:

protocol DiceGame {var dice: Dice { get }func play()
}
protocol DiceGameDelegate {func gameDidStart(_ game: DiceGame)func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)func gameDidEnd(_ game: DiceGame)
}

DiceGame 協議可以被任意涉及骰子的游戲遵循。

DiceGameDelegate 協議可以被任意類型遵循,用來追蹤 DiceGame 的游戲過程。為了防止強引用導致的循環引用問題,可以把協議聲明為弱引用,更多相關的知識請看 類實例之間的循環強引用,當協議標記為類專屬可以使 SnakesAndLadders 類在聲明協議時強制要使用弱引用。若要聲明類專屬的協議就必須繼承于 AnyObject ,更多請看 類專屬的協議。

如下所示,SnakesAndLadders 是 控制流 章節引入的蛇梯棋游戲的新版本。新版本使用 Dice 實例作為骰子,并且實現了 DiceGameDiceGameDelegate 協議,后者用來記錄游戲的過程:

class SnakesAndLadders: DiceGame {let finalSquare = 25let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())var square = 0var board: [Int]init() {board = Array(repeating: 0, count: finalSquare + 1)board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08}var delegate: DiceGameDelegate?func play() {square = 0delegate?.gameDidStart(self)gameLoop: while square != finalSquare {let diceRoll = dice.roll()delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)switch square + diceRoll {case finalSquare:break gameLoopcase let newSquare where newSquare > finalSquare:continue gameLoopdefault:square += diceRollsquare += board[square]}}delegate?.gameDidEnd(self)}
}

這個版本的游戲封裝到了 SnakesAndLadders 類中,該類遵循了 DiceGame 協議,并且提供了相應的可讀的 dice 屬性和 play() 方法。( dice 屬性在構造之后就不再改變,且協議只要求 dice 為可讀的,因此將 dice 聲明為常量屬性。)

游戲使用 SnakesAndLadders 類的 init() 構造器來初始化游戲。所有的游戲邏輯被轉移到了協議中的 play() 方法,play() 方法使用協議要求的 dice 屬性提供骰子搖出的值。

注意,delegate 并不是游戲的必備條件,因此 delegate 被定義為 DiceGameDelegate 類型的可選屬性。因為 delegate 是可選值,因此會被自動賦予初始值 nil。隨后,可以在游戲中為 delegate 設置適當的值。因為 DiceGameDelegate 協議是類專屬的,可以將 delegate 聲明為 weak,從而避免循環引用。

DicegameDelegate 協議提供了三個方法用來追蹤游戲過程。這三個方法被放置于游戲的邏輯中,即 play() 方法內。分別在游戲開始時,新一輪開始時,以及游戲結束時被調用。

因為 delegate 是一個 DiceGameDelegate 類型的可選屬性,因此在 play() 方法中通過可選鏈式調用來調用它的方法。若 delegate 屬性為 nil,則調用方法會優雅地失敗,并不會產生錯誤。若 delegate 不為 nil,則方法能夠被調用,并傳遞 SnakesAndLadders 實例作為參數。

如下示例定義了 DiceGameTracker 類,它遵循了 DiceGameDelegate 協議:

class DiceGameTracker: DiceGameDelegate {var numberOfTurns = 0func gameDidStart(_ game: DiceGame) {numberOfTurns = 0if game is SnakesAndLadders {print("Started a new game of Snakes and Ladders")}print("The game is using a \(game.dice.sides)-sided dice")}func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {numberOfTurns += 1print("Rolled a \(diceRoll)")}func gameDidEnd(_ game: DiceGame) {print("The game lasted for \(numberOfTurns) turns")}
}

DiceGameTracker 實現了 DiceGameDelegate 協議要求的三個方法,用來記錄游戲已經進行的輪數。當游戲開始時,numberOfTurns 屬性被賦值為 0,然后在每新一輪中遞增,游戲結束后,打印游戲的總輪數。

gameDidStart(_:) 方法從 game 參數獲取游戲信息并打印。game 參數是 DiceGame 類型而不是 SnakeAndLadders 類型,所以在 gameDidStart(_:) 方法中只能訪問 DiceGame 協議中的內容。當然了,SnakeAndLadders 的方法也可以在類型轉換之后調用。在上例代碼中,通過 is 操作符檢查 game 是否為 SnakesAndLadders 類型的實例,如果是,則打印出相應的消息。

無論當前進行的是何種游戲,由于 game 遵循 DiceGame 協議,可以確保 game 含有 dice 屬性。因此在 gameDidStart(_:) 方法中可以通過傳入的 game 參數來訪問 dice 屬性,進而打印出 dicesides 屬性的值。

DiceGameTracker 的運行情況如下所示:

let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
// Started a new game of Snakes and Ladders
// The game is using a 6-sided dice
// Rolled a 3
// Rolled a 5
// Rolled a 4
// Rolled a 5
// The game lasted for 4 turns

八、在擴展里添加協議遵循

即便無法修改源代碼,依然可以通過擴展令已有類型遵循并符合協議。擴展可以為已有類型添加屬性、方法、下標以及構造器,因此可以符合協議中的相應要求。詳情請在 擴展 章節中查看。

注意

通過擴展令已有類型遵循并符合協議時,該類型的所有實例也會隨之獲得協議中定義的各項功能。

例如下面這個 TextRepresentable 協議,任何想要通過文本表示一些內容的類型都可以實現該協議。這些想要表示的內容可以是實例本身的描述,也可以是實例當前狀態的文本描述:

protocol TextRepresentable {var textualDescription: String { get }
}

可以通過擴展,令先前提到的 Dice 類可以擴展來采納和遵循 TextRepresentable 協議:

extension Dice: TextRepresentable {var textualDescription: String {return "A \(sides)-sided dice"}
}

通過擴展遵循并采納協議,和在原始定義中遵循并符合協議的效果完全相同。協議名稱寫在類型名之后,以冒號隔開,然后在擴展的大括號內實現協議要求的內容。

現在所有 Dice 的實例都可以看做 TextRepresentable 類型:

let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
// 打印 “A 12-sided dice”

同樣,SnakesAndLadders 類也可以通過擴展來采納和遵循 TextRepresentable 協議:

extension SnakesAndLadders: TextRepresentable {var textualDescription: String {return "A game of Snakes and Ladders with \(finalSquare) squares"}
}
print(game.textualDescription)
// 打印 “A game of Snakes and Ladders with 25 squares”

九、有條件地遵循協議

泛型類型可能只在某些情況下滿足一個協議的要求,比如當類型的泛型形式參數遵循對應協議時。你可以通過在擴展類型時列出限制讓泛型類型有條件地遵循某協議。在你采納協議的名字后面寫泛型 where 分句。更多關于泛型 where 分句,見 泛型 Where 分句。

下面的擴展讓 Array 類型只要在存儲遵循 TextRepresentable 協議的元素時就遵循 TextRepresentable 協議。

extension Array: TextRepresentable where Element: TextRepresentable {var textualDescription: String {let itemsAsText = self.map { $0.textualDescription }return "[" + itemsAsText.joined(separator: ", ") + "]"}
}
let myDice = [d6, d12]
print(myDice.textualDescription)
// 打印 "[A 6-sided dice, A 12-sided dice]"

十、在擴展里聲明采納協議

當一個類型已經遵循了某個協議中的所有要求,卻還沒有聲明采納該協議時,可以通過空的擴展來讓它采納該協議:

struct Hamster {var name: Stringvar textualDescription: String {return "A hamster named \(name)"}
}
extension Hamster: TextRepresentable {}

從現在起,Hamster 的實例可以作為 TextRepresentable 類型使用:

let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// 打印 “A hamster named Simon”

注意

即使滿足了協議的所有要求,類型也不會自動遵循協議,必須顯式地遵循協議

十一、使用合成實現來采納協議

Swift 可以自動提供一些簡單場景下遵循 EquatableHashableComparable 協議的實現。在使用這些合成實現之后,無需再編寫重復的代碼來實現這些協議所要求的方法。

Swift 為以下幾種自定義類型提供了 Equatable 協議的合成實現:

  • 遵循 Equatable 協議且只有存儲屬性的結構體。
  • 遵循 Equatable 協議且只有關聯類型的枚舉
  • 沒有任何關聯類型的枚舉

在包含類型原始聲明的文件中聲明對 Equatable 協議的遵循,可以得到 == 操作符的合成實現,且無需自己編寫任何關于 == 的實現代碼。Equatable 協議同時包含 != 操作符的默認實現。

下面的例子中定義了一個 Vector3D 結構體來表示一個類似 Vector2D 的三維向量 (x, y, z)。由于 xyz 都是滿足 Equatable 的類型,Vector3D 可以得到連等判斷的合成實現。

struct Vector3D: Equatable {var x = 0.0, y = 0.0, z = 0.0
}
let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
if twoThreeFour == anotherTwoThreeFour {print("These two vectors are also equivalent.")
}
// 打印 "These two vectors are also equivalent."

Swift 為以下幾種自定義類型提供了 Hashable 協議的合成實現:

  • 遵循 Hashable 協議且只有存儲屬性的結構體。
  • 遵循 Hashable 協議且只有關聯類型的枚舉
  • 沒有任何關聯類型的枚舉

在包含類型原始聲明的文件中聲明對 Hashable 協議的遵循,可以得到 hash(into:) 的合成實現,且無需自己編寫任何關于 hash(into:) 的實現代碼。

Swift 為沒有原始值的枚舉類型提供了 Comparable 協議的合成實現。如果枚舉類型包含關聯類型,那這些關聯類型也必須同時遵循 Comparable 協議。在包含原始枚舉類型聲明的文件中聲明其對 Comparable 協議的遵循,可以得到 < 操作符的合成實現,且無需自己編寫任何關于 < 的實現代碼。Comparable 協議同時包含 <=>>= 操作符的默認實現。

下面的例子中定義了 SkillLevel 枚舉類型,其中定義了初學者(beginner)、中級(intermediate)和專家(expert)三種等級,專家等級會由額外的星級(stars)來進行排名。

enum SkillLevel: Comparable {case beginnercase intermediatecase expert(stars: Int)
}
var levels = [SkillLevel.intermediate, SkillLevel.beginner,SkillLevel.expert(stars: 5), SkillLevel.expert(stars: 3)]
for level in levels.sorted() {print(level)
}
// 打印 "beginner"
// 打印 "intermediate"
// 打印 "expert(stars: 3)"
// 打印 "expert(stars: 5)"

十二、協議類型的集合

協議類型可以在數組或者字典這樣的集合中使用,在 協議類型 提到了這樣的用法。下面的例子創建了一個元素類型為 TextRepresentable 的數組:

let things: [TextRepresentable] = [game, d12, simonTheHamster]

如下所示,可以遍歷 things 數組,并打印每個元素的文本表示:

for thing in things {print(thing.textualDescription)
}
// A game of Snakes and Ladders with 25 squares
// A 12-sided dice
// A hamster named Simon

注意 thing 常量是 TextRepresentable 類型而不是 DiceDiceGameHamster 等類型,即使實例在幕后確實是這些類型中的一種。由于 thingTextRepresentable 類型,任何 TextRepresentable 的實例都有一個 textualDescription 屬性,所以在每次循環中可以安全地訪問 thing.textualDescription

十三、協議的繼承

協議能夠繼承一個或多個其他協議,可以在繼承的協議的基礎上增加新的要求。協議的繼承語法與類的繼承相似,多個被繼承的協議間用逗號分隔:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {// 這里是協議的定義部分
}

如下所示,PrettyTextRepresentable 協議繼承了 TextRepresentable 協議:

protocol PrettyTextRepresentable: TextRepresentable {var prettyTextualDescription: String { get }
}

例子中定義了一個新的協議 PrettyTextRepresentable,它繼承自 TextRepresentable 協議。任何遵循 PrettyTextRepresentable 協議的類型在滿足該協議的要求時,也必須滿足 TextRepresentable 協議的要求。在這個例子中,PrettyTextRepresentable 協議額外要求遵循協議的類型提供一個返回值為 String 類型的 prettyTextualDescription 屬性。

如下所示,擴展 SnakesAndLadders,使其遵循并符合 PrettyTextRepresentable 協議:

extension SnakesAndLadders: PrettyTextRepresentable {var prettyTextualDescription: String {var output = textualDescription + ":\n"for index in 1...finalSquare {switch board[index] {case let ladder where ladder > 0:output += "▲ "case let snake where snake < 0:output += "▼ "default:output += "○ "}}return output}
}

上述擴展令 SnakesAndLadders 遵循了 PrettyTextRepresentable 協議,并提供了協議要求的 prettyTextualDescription 屬性。每個 PrettyTextRepresentable 類型同時也是 TextRepresentable 類型,所以在 prettyTextualDescription 的實現中,可以訪問 textualDescription 屬性。然后,拼接上了冒號和換行符。接著,遍歷數組中的元素,拼接一個幾何圖形來表示每個棋盤方格的內容:

  • 當從數組中取出的元素的值大于 0 時,用 ▲ 表示。
  • 當從數組中取出的元素的值小于 0 時,用 ▼ 表示。
  • 當從數組中取出的元素的值等于 0 時,用 ○ 表示。

任意 SankesAndLadders 的實例都可以使用 prettyTextualDescription 屬性來打印一個漂亮的文本描述:

print(game.prettyTextualDescription)
// A game of Snakes and Ladders with 25 squares:
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○

十四、類專屬的協議

你通過添加 AnyObject 關鍵字到協議的繼承列表,就可以限制協議只能被類類型采納(以及非結構體或者非枚舉的類型)。

protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {// 這里是類專屬協議的定義部分
}

在以上例子中,協議 SomeClassOnlyProtocol 只能被類類型采納。如果嘗試讓結構體或枚舉類型采納 SomeClassOnlyProtocol,則會導致編譯時錯誤。

注意

當協議定義的要求需要遵循協議的類型必須是引用語義而非值語義時,應該采用類類型專屬協議。關于引用語義和值語義的更多內容,請查看
結構體和枚舉是值類型 和 類是引用類型。

十五、協議合成

要求一個類型同時遵循多個協議是很有用的。你可以使用協議組合來復合多個協議到一個要求里。協議組合行為就和你定義的臨時局部協議一樣擁有構成中所有協議的需求。協議組合不定義任何新的協議類型。

協議組合使用 SomeProtocol & AnotherProtocol 的形式。你可以列舉任意數量的協議,用和符號(&)分開。除了協議列表,協議組合也能包含類類型,這允許你標明一個需要的父類。

下面的例子中,將 NamedAged 兩個協議按照上述語法組合成一個協議,作為函數參數的類型:

protocol Named {var name: String { get }
}
protocol Aged {var age: Int { get }
}
struct Person: Named, Aged {var name: Stringvar age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// 打印 “Happy birthday Malcolm - you're 21!”

Named 協議包含 String 類型的 name 屬性。Aged 協議包含 Int 類型的 age 屬性。Person 結構體采納了這兩個協議。

wishHappyBirthday(to:) 函數的參數 celebrator 的類型為 Named & Aged, 這意味著“任何同時遵循 NamedAged 的協議”。它不關心參數的具體類型,只要參數遵循這兩個協議即可。

上面的例子創建了一個名為 birthdayPersonPerson 的實例,作為參數傳遞給了 wishHappyBirthday(to:) 函數。因為 Person 同時遵循這兩個協議,所以這個參數合法,函數將打印生日問候語。

這里有一個例子:將 Location 類和前面的 Named 協議進行組合:

class Location {var latitude: Doublevar longitude: Doubleinit(latitude: Double, longitude: Double) {self.latitude = latitudeself.longitude = longitude}
}
class City: Location, Named {var name: Stringinit(name: String, latitude: Double, longitude: Double) {self.name = namesuper.init(latitude: latitude, longitude: longitude)}
}
func beginConcert(in location: Location & Named) {print("Hello, \(location.name)!")
}
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// 打印 "Hello, Seattle!"

beginConcert(in:) 函數接受一個類型為 Location & Named 的參數,這意味著“任何 Location 的子類,并且遵循 Named 協議”。在這個例子中,City 就滿足這樣的條件。

birthdayPerson 傳入 beginConcert(in:) 函數是不合法的,因為 Person 不是 Location 的子類。同理,如果你新建一個類繼承于 Location,但是沒有遵循 Named 協議,而用這個類的實例去調用 beginConcert(in:) 函數也是非法的。

十六、檢查協議一致性

你可以使用 類型轉換 中描述的 isas 操作符來檢查協議一致性,即是否遵循某協議,并且可以轉換到指定的協議類型。檢查和轉換協議的語法與檢查和轉換類型是完全一樣的:

  • is 用來檢查實例是否遵循某個協議,若遵循則返回 true,否則返回 false
  • as? 返回一個可選值,當實例遵循某個協議時,返回類型為協議類型的可選值,否則返回 nil
  • as! 將實例強制向下轉換到某個協議類型,如果強轉失敗,將觸發運行時錯誤。

下面的例子定義了一個 HasArea 協議,該協議定義了一個 Double 類型的可讀屬性 area

protocol HasArea {var area: Double { get }
}

如下所示,Circle 類和 Country 類都遵循了 HasArea 協議:

class Circle: HasArea {let pi = 3.1415927var radius: Doublevar area: Double { return pi * radius * radius }init(radius: Double) { self.radius = radius }
}
class Country: HasArea {var area: Doubleinit(area: Double) { self.area = area }
}

Circle 類把 area 屬性實現為基于存儲型屬性 radius 的計算型屬性。Country 類則把 area 屬性實現為存儲型屬性。這兩個類都正確地遵循了 HasArea 協議。

如下所示,Animal 是一個未遵循 HasArea 協議的類:

class Animal {var legs: Intinit(legs: Int) { self.legs = legs }
}

CircleCountryAnimal 并沒有一個共同的基類,盡管如此,它們都是類,它們的實例都可以作為 AnyObject 類型的值,存儲在同一個數組中:

let objects: [AnyObject] = [Circle(radius: 2.0),Country(area: 243_610),Animal(legs: 4)
]

objects 數組使用字面量初始化,數組包含一個 radius2Circle 的實例,一個保存了英國國土面積的 Country 實例和一個 legs4Animal 實例。

如下所示,objects 數組可以被迭代,并對迭代出的每一個元素進行檢查,看它是否遵循 HasArea 協議:

for object in objects {if let objectWithArea = object as? HasArea {print("Area is \(objectWithArea.area)")} else {print("Something that doesn't have an area")}
}
// Area is 12.5663708
// Area is 243610.0
// Something that doesn't have an area

當迭代出的元素遵循 HasArea 協議時,將 as? 操作符返回的可選值通過可選綁定,綁定到 objectWithArea 常量上。objectWithAreaHasArea 協議類型的實例,因此 area 屬性可以被訪問和打印。

objects 數組中的元素的類型并不會因為強轉而丟失類型信息,它們仍然是 CircleCountryAnimal 類型。然而,當它們被賦值給 objectWithArea 常量時,只被視為 HasArea 類型,因此只有 area 屬性能夠被訪問。

十七、可選的協議要求

協議可以定義可選要求,遵循協議的類型可以選擇是否實現這些要求。在協議中使用 optional 關鍵字作為前綴來定義可選要求。可選要求用在你需要和 Objective-C 打交道的代碼中。協議和可選要求都必須帶上 @objc 屬性。標記 @objc 特性的協議只能被繼承自 Objective-C 類的類或者 @objc 類遵循,其他類以及結構體和枚舉均不能遵循這種協議。

使用可選要求時(例如,可選的方法或者屬性),它們的類型會自動變成可選的。比如,一個類型為 (Int) -> String 的方法會變成 ((Int) -> String)?。需要注意的是整個函數類型是可選的,而不是函數的返回值。

協議中的可選要求可通過可選鏈式調用來使用,因為遵循協議的類型可能沒有實現這些可選要求。類似 someOptionalMethod?(someArgument) 這樣,你可以在可選方法名稱后加上 ? 來調用可選方法。詳細內容可在 可選鏈式調用 章節中查看。

下面的例子定義了一個名為 Counter 的用于整數計數的類,它使用外部的數據源來提供每次的增量。數據源由 CounterDataSource 協議定義,它包含兩個可選要求:

@objc protocol CounterDataSource {@objc optional func increment(forCount count: Int) -> Int@objc optional var fixedIncrement: Int { get }
}

CounterDataSource 協議定義了一個可選方法 increment(forCount:) 和一個可選屬性 fiexdIncrement,它們使用了不同的方法來從數據源中獲取適當的增量值。

注意

嚴格來講,CounterDataSource 協議中的方法和屬性都是可選的,因此遵循協議的類可以不實現這些要求,盡管技術上允許這樣做,不過最好不要這樣寫。

Counter 類含有 CounterDataSource? 類型的可選屬性 dataSource,如下所示:

class Counter {var count = 0var dataSource: CounterDataSource?func increment() {if let amount = dataSource?.increment?(forCount: count) {count += amount} else if let amount = dataSource?.fixedIncrement {count += amount}}
}

Counter 類使用變量屬性 count 來存儲當前值。該類還定義了一個 increment 方法,每次調用該方法的時候,將會增加 count 的值。

increment() 方法首先試圖使用 increment(forCount:) 方法來得到每次的增量。increment() 方法使用可選鏈式調用來嘗試調用 increment(forCount:),并將當前的 count 值作為參數傳入。

這里使用了兩層可選鏈式調用。首先,由于 dataSource 可能為 nil,因此在 dataSource 后邊加上了 ?,以此表明只在 dataSource 非空時才去調用 increment(forCount:) 方法。其次,即使 dataSource 存在,也無法保證其是否實現了 increment(forCount:) 方法,因為這個方法是可選的。因此,increment(forCount:) 方法同樣使用可選鏈式調用進行調用,只有在該方法被實現的情況下才能調用它,所以在 increment(forCount:) 方法后邊也加上了 ?

調用 increment(forCount:) 方法在上述兩種情形下都有可能失敗,所以返回值為 Int? 類型。雖然在 CounterDataSource 協議中,increment(forCount:) 的返回值類型是非可選 Int。另外,即使這里使用了兩層可選鏈式調用,最后的返回結果依舊是單層的可選類型。關于這一點的更多信息,請查閱 連接多層可選鏈式調用。

在調用 increment(forCount:) 方法后,Int? 型的返回值通過可選綁定解包并賦值給常量 amount。如果可選值確實包含一個數值,也就是說,數據源和方法都存在,數據源方法返回了一個有效值。之后便將解包后的 amount 加到 count 上,增量操作完成。

如果沒有從 increment(forCount:) 方法獲取到值,可能由于 dataSourcenil,或者它并沒有實現 increment(forCount:) 方法,那么 increment() 方法將試圖從數據源的 fixedIncrement 屬性中獲取增量。fixedIncrement 是一個可選屬性,因此屬性值是一個 Int? 值,即使該屬性在 CounterDataSource 協議中的類型是非可選的 Int

下面的例子展示了 CounterDataSource 的簡單實現。ThreeSource 類遵循了 CounterDataSource 協議,它實現了可選屬性 fixedIncrement,每次會返回 3

class ThreeSource: NSObject, CounterDataSource {let fixedIncrement = 3
}

上述代碼新建了一個 Counter 實例,并將它的數據源設置為一個 ThreeSource 的實例,然后調用 increment() 方法 4 次。按照預期預期一樣,每次調用都會將 count 的值增加 3.

下面是一個更為復雜的數據源 TowardsZeroSource,它將使得最后的值變為 0

class TowardsZeroSource: NSObject, CounterDataSource {func increment(forCount count: Int) -> Int {if count == 0 {return 0} else if count < 0 {return 1} else {return -1}}
}

TowardsZeroSource 實現了 CounterDataSource 協議中的 increment(forCount:) 方法,以 count 參數為依據,計算出每次的增量。如果 count 已經為 0,此方法將返回 0,以此表明之后不應再有增量操作發生。

你可以使用 TowardsZeroSource 實例將 Counter 實例來從 -4 增加到 0。一旦增加到 0,數值便不會再有變動:

counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {counter.increment()print(counter.count)
}
// -3
// -2
// -1
// 0
// 0

十八、協議擴展

協議可以通過擴展來為遵循協議的類型提供屬性、方法以及下標的實現。通過這種方式,你可以基于協議本身來實現這些功能,而無需在每個遵循協議的類型中都重復同樣的實現,也無需使用全局函數。

例如,可以擴展 RandomNumberGenerator 協議來提供 randomBool() 方法。該方法使用協議中定義的 random() 方法來返回一個隨機的 Bool 值:

extension RandomNumberGenerator {func randomBool() -> Bool {return random() > 0.5}
}

通過協議擴展,所有遵循協議的類型,都能自動獲得這個擴展所增加的方法實現而無需任何額外修改:

let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// 打印 “Here's a random number: 0.37464991998171”
print("And here's a random Boolean: \(generator.randomBool())")
// 打印 “And here's a random Boolean: true”

協議擴展可以為遵循協議的類型增加實現,但不能聲明該協議繼承自另一個協議。協議的繼承只能在協議聲明處進行指定。

1、提供默認實現

可以通過協議擴展來為協議要求的方法、計算屬性提供默認的實現。如果遵循協議的類型為這些要求提供了自己的實現,那么這些自定義實現將會替代擴展中的默認實現被使用。

注意

通過協議擴展為協議要求提供的默認實現和可選的協議要求不同。雖然在這兩種情況下,遵循協議的類型都無需自己實現這些要求,但是通過擴展提供的默認實現可以直接調用,而無需使用可選鏈式調用

例如,PrettyTextRepresentable 協議繼承自 TextRepresentable 協議,可以為其提供一個默認的 prettyTextualDescription 屬性來簡單地返回 textualDescription 屬性的值:

extension PrettyTextRepresentable  {var prettyTextualDescription: String {return textualDescription}
}

2、為協議擴展添加限制條件

在擴展協議的時候,可以指定一些限制條件,只有遵循協議的類型滿足這些限制條件時,才能獲得協議擴展提供的默認實現。這些限制條件寫在協議名之后,使用 where 子句來描述,正如 泛型 Where 子句 中所描述的。

例如,你可以擴展 Collection 協議,適用于集合中的元素遵循了 Equatable 協議的情況。通過限制集合元素遵循 Equatable 協議, 作為標準庫的一部分, 你可以使用 ==!= 操作符來檢查兩個元素的等價性和非等價性。

extension Collection where Element: Equatable {func allEqual() -> Bool {for element in self {if element != self.first {return false}}return true}
}

如果集合中的所有元素都一致,allEqual() 方法才返回 true

看看兩個整數數組,一個數組的所有元素都是一樣的,另一個不一樣:

let equalNumbers = [100, 100, 100, 100, 100]
let differentNumbers = [100, 100, 200, 100, 200]

由于數組遵循 Collection 而且整數遵循 EquatableequalNumbersdifferentNumbers 都可以使用 allEqual() 方法。

print(equalNumbers.allEqual())
// 打印 "true"
print(differentNumbers.allEqual())
// 打印 "false"

注意

如果一個遵循的類型滿足了為同一方法或屬性提供實現的多個限制型擴展的要求, Swift 會使用最匹配限制的實現。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/22372.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/22372.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/22372.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Docker橋接網絡分析

前言 《虛擬局域網(VLAN)》一文中描述了虛擬網卡、虛擬網橋的作用&#xff0c;以及通過iptables實現了vlan聯網&#xff0c;其實學習到這里自然就會聯想到目前主流的容器技術&#xff1a;Docker&#xff0c;因此接下來打算研究一下Docker的橋接網絡與此有何異同。 猜測 眾所周知…

第十一屆藍橋杯C++青少年組中/高級組選拔賽2019年真題解析

一、單選題 第1題 一個C語言的源程序中&#xff0c;有關主函數的說法正確的是&#xff08; &#xff09;. A:可以有多個主函數 B:必須有一個主函數 C:必須有主函數和其他函數 D:可以沒有主函數 答案&#xff1a; 第2題 在下面的條件語句中&#xff08;其中s1和s2代表C語言…

計算機基礎(8)——音頻數字化(模電與數電)

&#x1f497;計算機基礎系列文章&#x1f497; &#x1f449;&#x1f340;計算機基礎&#xff08;1&#xff09;——計算機的發展史&#x1f340;&#x1f449;&#x1f340;計算機基礎&#xff08;2&#xff09;——馮諾依曼體系結構&#x1f340;&#x1f449;&#x1f34…

如何在GlobalMapper中加載高清衛星影像?

GlobalMapper在GIS行業幾乎無人不知&#xff0c;無人不曉&#xff0c;但它可以直接加載衛星影像也許就不是每個人都知道的了。 這里就來分享一下如何在GlobalMapper中加載高清衛星影像&#xff0c;并可以在文末查看領取軟件安裝包和圖源的方法。 如何加載高清圖源 首先&…

45-1 waf繞過 - 文件上傳繞過WAF方法

環境準備: 43-5 waf繞過 - 安全狗簡介及安裝-CSDN博客然后安裝dvwa靶場:構建完善的安全滲透測試環境:推薦工具、資源和下載鏈接_滲透測試靶機下載-CSDN博客打開dvwa靶場,先將靶場的安全等級調低,然后切換到文件上傳 一、符號變異 在PHP中,由于其弱類型特性,有時候僅有一…

4月份新出!外網爆火的大模型黑書!內行人都在學~

今天給大家推薦一本4月份才新出的大型語言模型&#xff08;LLM&#xff09;的權威教程《基于GPT-3、ChatGPT、GPT-4等Transformer架構的自然語言處理》&#xff01;Google工程總監Antonio Gulli作序&#xff0c;一堆大佬推薦&#xff01;這含金量不用多說&#xff0c;在這里給大…

Docker容器搭建ELK日志分析系統

Docker容器搭建ELK日志分析系統 文章目錄 Docker容器搭建ELK日志分析系統資源列表基礎環境一、創建容器網絡二、創建容器掛載目錄三、構建systemctl鏡像三、構建Elasticsearch鏡像3.1、構建Elasticsearch3.2、構建鏡像3.3、啟動容器3.4、進入容器3.5、查看節點信息 四、構建Log…

NLP基礎——語言模型(動手學深度學習)

語言模型 聯合概率 給定文本序列 x 1 , ? , x t x_1,\cdots,x_t x1?,?,xt?&#xff0c;語言模型的目標是估計聯合概率 P ( x 1 , ? , x t ) P(x_1,\cdots,x_t) P(x1?,?,xt?). 這里的 x t x_t xt? 可以認為是文本序列在時間步 t t t 處的觀測或標簽&#xff0c;而…

亞信安慧AntDB:卓越的拓展性和靈活性

在當今這個信息爆炸的時代&#xff0c;企業對數據處理的需求不斷增長&#xff0c;傳統的數據庫系統往往難以應對海量數據的存儲和處理挑戰。然而&#xff0c;隨著亞信安慧AntDB的出現&#xff0c;解決這一難題的曙光終于出現在眼前。AntDB不僅僅具備了高吞吐、高并發、高性能的…

Linux系統之mv命令的基本使用

Linux系統之mv命令的基本使用 一、mv命令介紹1. mv命令簡介2. mv命令的使用結果 二、mv命令的使用幫助1. 在命令行的幫助信息2. mv常用選項 三、mv命令的基本使用1. 創建源目錄和目標目錄2. 新建測試文件3. 將源目錄文件復制到目標目錄4. 將文件進行改名5. 將目錄的所有文件轉移…

前端面試寶典總結4-手搓代碼JavaScript(數據處理)

前端面試寶典總結4之手寫代碼JavaScript&#xff08;數據處理&#xff09; 本文章 對各大學習技術論壇知識點&#xff0c;進行總結、歸納自用學習&#xff0c;共勉&#x1f64f; 上一篇&#x1f449;: 前端面試寶典總結3-JavaScript&#xff08;2&#xff09; 文章目錄 前端…

python長方形周長面積 2024年3月青少年編程電子學會python編程等級考試二級真題解析

目錄 python長方形周長面積 一、題目要求 1、編程實現 2、輸入輸出 二、算法分析 三、程序代碼 四、程序說明 五、運行結果 六、考點分析 七、 推薦資料 1、藍橋杯比賽 2、考級資料 3、其它資料 python長方形周長面積 2024年3月 python編程等級考試級編程題 一、…

matlab模擬太陽耀斑噴發

代碼 function simulate_solar_flare% 參數設置gridSize 100; % 網格大小timeSteps 200; % 時間步數dt 0.1; % 時間步長% 初始化網格[X, Y] meshgrid(linspace(-5, 5, gridSize));Z zeros(size(X));% 設置耀斑初始位置和強度flareCenter [0, 0]; % 耀斑中心位置flareRad…

【實用技巧】Unity中的Image組件

Unity中的Image組件是UI系統的核心部分&#xff0c;用于顯示圖像和紋理。以下是一些關于Unity Image組件的實用技巧&#xff1a; 使用Sprite作為Image源&#xff1a; 將Sprite直接拖拽到Image組件的Source Image字段中&#xff0c;可以快速設置顯示的圖像。 調整顏色和透明度&a…

9 -力扣高頻 SQL 50 題(基礎版)

9 - 上升的溫度 -- 找出與之前&#xff08;昨天的&#xff09;日期相比溫度更高的所有日期的 id -- DATEDIFF(2007-12-31,2007-12-30); # 1 -- DATEDIFF(2010-12-30,2010-12-31); # -1select w1.id from Weather w1, Weather w2 wheredatediff(w1.recordDate,w2.recordDat…

SolidWorks功能強大的三維設計軟件下載安裝,SolidWorks最新資源獲取!

SolidWorks&#xff0c;它憑借出色的三維建模能力&#xff0c;使得設計師們能夠輕松構建出復雜且精細的機械模型&#xff0c;大大提升了設計效率和質量。 在機械設計領域&#xff0c;SolidWorks憑借其豐富的工具和特性&#xff0c;讓設計師們能夠隨心所欲地揮灑創意。無論是零…

Flutter 中的 LayoutBuilder 小部件:全面指南

Flutter 中的 LayoutBuilder 小部件&#xff1a;全面指南 Flutter 是一個功能豐富的 UI 框架&#xff0c;它允許開發者使用 Dart 語言來構建高性能、美觀的跨平臺應用。在 Flutter 的布局系統中&#xff0c;LayoutBuilder 是一個強大的組件&#xff0c;它可以根據父容器的約束…

家政預約小程序12用戶登錄

目錄 1 創建全局變量2 創建頁面3 搭建頁面4 實現登錄邏輯總結 在小程序中&#xff0c;登錄是一個常見的場景。比如我們在小程序預約或者購買時&#xff0c;通常要求用戶先登錄后購買。如果使用傳統方案&#xff0c;登錄這個動作其實最終的目的是為了獲取用戶的openid。而使用低…

Python學習圣經:從0到1,精通Python使用

尼恩&#xff1a;LLM大模型學習圣經PDF的起源 在40歲老架構師 尼恩的讀者交流群(50)中&#xff0c;經常性的指導小伙伴們改造簡歷。 經過尼恩的改造之后&#xff0c;很多小伙伴拿到了一線互聯網企業如得物、阿里、滴滴、極兔、有贊、希音、百度、網易、美團的面試機會&#x…

【智能體】文心智能體大賽第二季持續進行中,一起在智能體的海洋里發揮你的創意吧

目錄 背景作文小助手AI迅哥問答程序員黃歷助手比賽時間第二期賽題豐厚獎品評選說明獲獎智能體推薦文章 背景 AI應用&#xff08;智能體&#xff09;&#xff0c;持續火熱&#xff0c;一句話就能創建一個有趣、好玩的應用。 可以說一分鐘內就能創建一個有創意的智能體。 看大多…