Swift學(xué)習(xí):協(xié)議

本篇將詳細(xì)總結(jié)介紹Swift協(xié)議的用法;
協(xié)議是定義一些規(guī)范(屬性、功能方法),然后由類、結(jié)構(gòu)體或者枚舉遵循并實(shí)現(xiàn)這些規(guī)范,這一過(guò)程被稱為遵循了協(xié)議。

主要內(nèi)容:
1.協(xié)議的基本語(yǔ)法
2.定義協(xié)議與實(shí)現(xiàn)協(xié)議
3.協(xié)議與構(gòu)造器
4.協(xié)議作為類型
5.協(xié)議實(shí)現(xiàn)委托代理模式
6.通過(guò)擴(kuò)展遵循協(xié)議
7.協(xié)議類型的集合
8.協(xié)議繼承協(xié)議
9.類類型專屬協(xié)議
10.協(xié)議合成
11.檢查協(xié)議的一致性
12.協(xié)議的可選性
13.協(xié)議擴(kuò)展

一、協(xié)議的基本語(yǔ)法

下面是協(xié)議的一些基本語(yǔ)法:
1.定義一個(gè)協(xié)議

protocol SomeProtocol {
    //這里是協(xié)議的定義部分
}
protocol AnotherProtocol{
    //這里是協(xié)議的定義部分
}

2.自定義類型遵循協(xié)議使用冒號(hào),遵循多個(gè)協(xié)議時(shí),各協(xié)議間使用逗號(hào)分隔

struct SomeStructure: SomeProtocol, AnotherProtocol {
    //這里是結(jié)構(gòu)體的定義部分
}

3.擁有父類的類在遵循協(xié)議時(shí),需要將父類名放在協(xié)議名之前,以逗號(hào)分隔

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

二、定義協(xié)議與實(shí)現(xiàn)協(xié)議

協(xié)議可以要求遵循協(xié)議的類型提供特定的屬性、方法,構(gòu)造器。如果協(xié)議中的屬性和方法沒有實(shí)現(xiàn),就會(huì)報(bào)錯(cuò);除此之外,我們還需要注意一些具體的使用規(guī)則如下:
屬性要求:
1.協(xié)議可以定義實(shí)例屬性和類型屬性(使用static);
2.協(xié)議不指定屬性是存儲(chǔ)屬性還是計(jì)算型屬性,只指定屬性名稱和類型以及讀寫性;
3.協(xié)議指定屬性的讀取類型,使用的get和set,中間不能使用逗號(hào);
4.協(xié)議總是使用var關(guān)鍵字來(lái)聲明變量屬性;
5.不能給協(xié)議屬性設(shè)置默認(rèn)值,因?yàn)槟J(rèn)值被看做是一種實(shí)現(xiàn);

方法要求:
1.協(xié)議可以定義實(shí)例方法和類方法(使用static);
2.協(xié)議定義函數(shù)時(shí)不能添加函數(shù)的實(shí)現(xiàn),同時(shí),傳入的參數(shù)也不能使用默認(rèn)參數(shù);
3.如果協(xié)議定義的實(shí)例方法會(huì)改變實(shí)例本身,需要在定義的方法名前使用mutating;這使得結(jié)構(gòu)體和枚舉能夠遵循此協(xié)議并滿足此方法要求。

下面具體演示一個(gè)協(xié)議的使用:

protocol  PersonProtocol{
    //1.定義屬性
    static var personCount: Int {get}
    var name:String{
        get
        set
    }
    var nickName:String{get set} //要求可讀可寫,則該屬性不能是常量屬性或者只讀的計(jì)算型屬性
    var birthPlace:String{get}   //只要求可讀,若代碼需要,實(shí)現(xiàn)時(shí)也是可寫的
    var age:Int{get}
    
    //2.定義函數(shù)
    static func play()
    func eat(food:String)
    //func fed(food:string = "defaultfood”) 錯(cuò)誤,不能使用默認(rèn)參數(shù)
    mutating func changeNickName(newName:String)
}


struct Student:PersonProtocol{
    static var personCount = 0        //類屬性
    var name: String = ""
    var nickName: String = ""          //這種形式的聲明就代表可讀可寫
    let birthPlace: String = "beijing" //將只讀屬性設(shè)置為let,在合適位置給其設(shè)置默認(rèn)值就好了
    var age:Int = 10
    //其實(shí),只讀類型的屬性也可以設(shè)置為var,這相當(dāng)于是對(duì)其進(jìn)行擴(kuò)展,不僅遵循了原來(lái)的get,還增加了set
    
    static func play() {
        //類方法
    }
    
    func eat(food: String) {
        //普通實(shí)例方法
    }
    
    mutating func changeNickName(newName: String) {
        //實(shí)例方法中修改了實(shí)例屬性
        self.nickName = newName
    }
}

//測(cè)試代碼:
var student:Student = Student()
student.age = 18
var stu:PersonProtocol = student //這里協(xié)議也當(dāng)做了一種類型來(lái)使用,但是具體的實(shí)現(xiàn)還是是Dog完成的
//stu.age = 10 //這里報(bào)錯(cuò),因?yàn)閰f(xié)議中的age是只讀的

注意:實(shí)現(xiàn)協(xié)議中的 mutating 方法時(shí),若是類類型,則不用寫 mutating 關(guān)鍵字。而對(duì)于結(jié)構(gòu)體和枚舉,則必須寫 mutating 關(guān)鍵字。

三、協(xié)議與構(gòu)造器

這里主要總結(jié)協(xié)議在定義構(gòu)造器時(shí)候的一些要求,主要有如下幾個(gè)方面:
1.協(xié)議中可設(shè)置指定或者便利構(gòu)造器,實(shí)現(xiàn)時(shí)都需要添加required修飾符,因?yàn)檫@樣可以確保所有子類也必須提供此構(gòu)造器,從而符合協(xié)議,但是如果為final類,就不需要;
2.如果一個(gè)子類重寫了父類的指定構(gòu)造器,并且該構(gòu)造器滿足了某個(gè)協(xié)議的要求,那么該構(gòu)造器的實(shí)現(xiàn)需要同時(shí)標(biāo)注 required 和 override 修飾符;
3.協(xié)議中可定義可失敗構(gòu)造器(init?)、非可失敗構(gòu)造器(init)、隱式解包可失敗構(gòu)造器(init!);
下面是協(xié)議與構(gòu)造器使用的相關(guān)示例:

protocol Protocol {
    init()
}
class SomeSuperClass {
    init() {
        // 這里是構(gòu)造器的實(shí)現(xiàn)部分
    }
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
    // 因?yàn)樽裱瓍f(xié)議,需要加上 required
    // 因?yàn)槔^承自父類,需要加上 override
    required override init() {
        // 這里是構(gòu)造器的實(shí)現(xiàn)部分
    }
}

四、協(xié)議作為類型

協(xié)議雖本身并未實(shí)現(xiàn)任何功能,但是仍然可以像其他普通類型一樣使用,如Int、Double等。協(xié)議作為類型使用的場(chǎng)景如下:

  • 作為函數(shù)、方法或構(gòu)造器中的參數(shù)類型或返回值類型
  • 作為常量、變量或?qū)傩缘念愋?/li>
  • 作為數(shù)組、字典或其他容器中的元素類型
    下面演示協(xié)議類型的使用:
//協(xié)議:定義了生成隨機(jī)數(shù)方法
protocol RandomNumberGenerator {
    func random() -> Double
}
//實(shí)現(xiàn)了RandomNumberGenerator協(xié)議的類
class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        //使用truncatingRemainder方法進(jìn)行浮點(diǎn)數(shù)取余
        lastRandom = (lastRandom * a + c).truncatingRemainder(dividingBy: m)
        return lastRandom / m
    }
}
//Dice的generator屬性,其類型是RandomNumberGenerator協(xié)議類型
class Dice {
    let sides: Int
    let generator: RandomNumberGenerator    //協(xié)議作為屬性
    
    //協(xié)議作為參數(shù)類型
    init(sides: Int, generator: RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
    func roll() -> Int {
        return Int(generator.random() * Double(sides)) + 1
    }
}
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

五、協(xié)議實(shí)現(xiàn)委托代理模式

委托是一種設(shè)計(jì)模式,它允許類或結(jié)構(gòu)體將一些需要它們負(fù)責(zé)的功能委托給其他類型的實(shí)例。

委托模式的作用:
用來(lái)響應(yīng)特定的動(dòng)作,或者接收外部數(shù)據(jù)源提供的數(shù)據(jù),而無(wú)需關(guān)心外部數(shù)據(jù)源的類型。
委托模式的原理:
定義協(xié)議來(lái)封裝那些需要被委托的功能,這樣就能確保遵循協(xié)議的類型能提供這些功能。

下面例子演示了通過(guò)協(xié)議實(shí)現(xiàn)代理模式:

//播放音樂的協(xié)議
protocol PlayMusicTools{
    func playMusic();
}

//實(shí)現(xiàn)協(xié)議的類
class QQMusisApp:PlayMusicTools{
    func playMusic() {
        print(“播放一首美妙的音樂")
    }
}

class Person{
    var delegate:PlayMusicTools?
    func listenMusic(){
        self.delegate?.playMusic()
    }
}

//人想聽音樂但是又不能自己播放,就調(diào)用了代理的方法
let person:Person = Person()
person.delegate = QQMusisApp()
person.listenMusic()

六、通過(guò)擴(kuò)展遵循協(xié)議

我們知道,擴(kuò)展可以為已有類型添加屬性、方法、下標(biāo)以及構(gòu)造器。同樣道理,我們也可以通過(guò)擴(kuò)展為已有類型實(shí)現(xiàn)需要遵循的協(xié)議,通過(guò)這種方法與在原始定義中遵循并實(shí)現(xiàn)協(xié)議效果完全相同。

6.1.通過(guò)擴(kuò)展實(shí)現(xiàn)協(xié)議

//協(xié)議:定義一個(gè)可以打印UIView屬性fame的方法
protocol ViewProperty{
    func printFrame()
}

//通過(guò)擴(kuò)展為UIView實(shí)現(xiàn)了ViewProperty協(xié)議
extension UIView:ViewProperty{
    func printFrame() {
        print(self.frame)
    }
}

let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.printFrame()

6.2.已經(jīng)符合協(xié)議的類

如果一個(gè)類型已經(jīng)符合了某個(gè)協(xié)議的所有要求,卻還沒有聲明遵循這個(gè)協(xié)議,那么可以通過(guò)空擴(kuò)展來(lái)遵循協(xié)議。

class CustomObject{
    func printFrame() {
        print("this is not a view,cant print frame")
    }
}
//空擴(kuò)展表示遵循協(xié)議
extension CustomObject:ViewProperty{}
//遵循了協(xié)議之后,就可以使用協(xié)議作為類型
let customOject:ViewProperty = CustomObject()
customOject.printFrame()

七、協(xié)議類型的集合

協(xié)議類型可以在數(shù)組或者字典這樣的集合中使用;如下,等號(hào)左邊的數(shù)組表示遵循了ViewProperty協(xié)議的對(duì)象構(gòu)成的數(shù)組。

let things:[ViewProperty] = [view,customOject]
for thing in things{
    thing.printFrame()
}
//打印結(jié)果:
//(0.0, 0.0, 100.0, 100.0)
//this is not a view,cant print frame

八、協(xié)議繼承協(xié)議

協(xié)議繼承協(xié)議具有以下特點(diǎn):
1.協(xié)議能夠繼承一個(gè)或多個(gè)其他協(xié)議,可以在繼承的協(xié)議的基礎(chǔ)上增加新的要求。
2.協(xié)議的繼承語(yǔ)法與類的繼承相似,多個(gè)被繼承的協(xié)議間用逗號(hào)分隔:
3.所有遵循新協(xié)議的類型,也同時(shí)滿足新協(xié)議所繼承的父協(xié)議
協(xié)議繼承協(xié)議的格式如下:

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

九、類類型專屬協(xié)議

協(xié)議的繼承列表中,通過(guò)添加 class 關(guān)鍵字來(lái)限制協(xié)議只能被類類型遵循,而結(jié)構(gòu)體或枚舉不能遵循該協(xié)議。class 關(guān)鍵字必須第一個(gè)出現(xiàn)在協(xié)議的繼承列表中,在其他繼承的協(xié)議之前。

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    // 這里是類類型專屬協(xié)議的定義部分
    //class 關(guān)鍵字必須第一個(gè)出現(xiàn)在協(xié)議的繼承列表中,在其他繼承的協(xié)議之前
}

十、協(xié)議合成

有時(shí)候需要同時(shí)遵循多個(gè)協(xié)議,你可以將多個(gè)協(xié)議采用SomeProtocol & AnotherProtocol這樣的格式進(jìn)行組合,稱為協(xié)議合成(protocol composition);你可以羅列任意多個(gè)你想要遵循的協(xié)議,以與符號(hào)(&)分隔。
下面的例子中,將 Named 和 Aged 兩個(gè)協(xié)議按照上述語(yǔ)法組合成一個(gè)協(xié)議,作為函數(shù)參數(shù)的類型:

protocol Named {
    var name: String { get }
}

protocol Aged {
    var age: Int { get }
}

//Person遵循兩個(gè)協(xié)議
struct Person: Named, Aged {
    var name: String
    var age: Int
}

//函數(shù)參數(shù)celebrator的類型為 Name & Aged;
//這意味著它不關(guān)心參數(shù)的具體類型,只要參數(shù)符合這兩個(gè)協(xié)議即可;
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!”

注意:協(xié)議合成并不會(huì)生成新的、永久的協(xié)議類型,而是將多個(gè)協(xié)議中的要求合成到一個(gè)只在局部作用域有效的臨時(shí)協(xié)議中

十一、檢查協(xié)議的一致性

類型轉(zhuǎn)換中描述的is和as操作符同樣可以用來(lái)檢查協(xié)議一致性,即是否符合某協(xié)議,并且可以轉(zhuǎn)換到指定的協(xié)議類型。檢查和轉(zhuǎn)換到某個(gè)協(xié)議類型在語(yǔ)法上和類型的檢查和轉(zhuǎn)換完全相同:
is 用來(lái)檢查實(shí)例是否符合某個(gè)協(xié)議,若符合則返回 true,否則返回 false。
as? 返回一個(gè)可選值,當(dāng)實(shí)例符合某個(gè)協(xié)議時(shí),返回類型為協(xié)議類型的可選值,否則返回 nil。
as! 將實(shí)例強(qiáng)制向下轉(zhuǎn)換到某個(gè)協(xié)議類型,如果強(qiáng)轉(zhuǎn)失敗,會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤。

//定義協(xié)議:定義一個(gè)Double類型的可讀屬性area
protocol HasArea {
    var area: Double { get }
}

//Circle類和Country類遵循協(xié)議HasArea
class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area: Double { return pi * radius * radius } //計(jì)算型
    init(radius: Double) { self.radius = radius }
}

class Country: HasArea {
    var area: Double //存儲(chǔ)型
    init(area: Double) { self.area = area }
}

//Animal未遵循協(xié)議
class Animal {
    var legs: Int
    init(legs: Int) { self.legs = legs }
}

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

//迭代測(cè)試,并檢測(cè)協(xié)議
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”

十二、協(xié)議的可選性

協(xié)議可以定義可選要求,即遵循協(xié)議的類型可以選擇是否實(shí)現(xiàn)這些要求。
1.在協(xié)議中使用optional關(guān)鍵字作為前綴來(lái)定義可選要求。
2.可選要求用在你需要和Objective-C打交道的代碼中。協(xié)議和可選要求都必須帶上@objc屬性。
3.標(biāo)記@objc特性的協(xié)議只能被繼承自O(shè)bjective-C類的類或者@objc類遵循,其他類以及結(jié)構(gòu)體和枚舉均不能遵循這種協(xié)議。
4.協(xié)議中的可選要求可通過(guò)可選鏈?zhǔn)秸{(diào)用來(lái)使用,因?yàn)樽裱瓍f(xié)議的類型可能沒有實(shí)現(xiàn)這些可選要求
下面的例子定義了一個(gè)名為Counter的用于整數(shù)計(jì)數(shù)的類,它使用外部的數(shù)據(jù)源來(lái)提供每次的增量。數(shù)據(jù)源由CounterDataSource 協(xié)議定義,包含兩個(gè)可選要求:

//1.協(xié)議CounterDataSource包含兩個(gè)可選要求
@objc protocol CounterDataSource {
    @objc optional func incrementForCount(count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}

//2.Counter類含有CounterDataSource?類型的可選屬性dataSource
class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    
    func increment() {
        if let amount = dataSource?.incrementForCount?(count: count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
             //通過(guò)可選鏈調(diào)用,每次使用的是fixedIncrement的3
            count += amount
        }
    }
}

//3.ThreeSource類遵循了CounterDataSource協(xié)議
//它實(shí)現(xiàn)了可選屬性fixedIncrement,而并未實(shí)現(xiàn)incrementForCount方法
class ThreeSource: NSObject, CounterDataSource {
    let fixedIncrement = 3
}

//4.使用ThreeSource實(shí)例作為Counter實(shí)例的數(shù)據(jù)源對(duì)象
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
    counter.increment()
    print(counter.count)
}

//測(cè)試結(jié)果:
//3
//6
//9
//12

注意:嚴(yán)格來(lái)講,CounterDataSource 協(xié)議中的方法和屬性都是可選的,因此遵循協(xié)議的類可以不實(shí)現(xiàn)這些要求,盡管技術(shù)上允許這樣做,不過(guò)最好不要這樣寫。

十三、協(xié)議擴(kuò)展

協(xié)議可以通過(guò)擴(kuò)展來(lái)為遵循協(xié)議的類型提供屬性、方法以及下標(biāo)的實(shí)現(xiàn)。通過(guò)這種方式,你可以基于協(xié)議本身來(lái)實(shí)現(xiàn)這些功能,而無(wú)需在每個(gè)遵循協(xié)議的類型中都重復(fù)同樣的實(shí)現(xiàn),也無(wú)需使用全局函數(shù)。
下面的代碼演示了協(xié)議擴(kuò)展的用法:

//協(xié)議:定義random函數(shù)生成隨機(jī)數(shù)方法
protocol RandomNumProtocol {
    func random() -> Double
}
//擴(kuò)展RandomNumProtocol協(xié)議,增加了randomBool方法
//注意:通過(guò)協(xié)議擴(kuò)展,所有遵循協(xié)議的類型都能自動(dòng)獲得這個(gè)擴(kuò)展所增加的方法實(shí)現(xiàn),無(wú)需任何額外修改
extension RandomNumProtocol {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}
//遵循協(xié)議的類:一個(gè)實(shí)現(xiàn)了RandomNumProtocol協(xié)議的類
//RandomNum類只實(shí)現(xiàn)了協(xié)議方法random(),但是同樣可以使用協(xié)議擴(kuò)展里的方法randomBool()
class RandomNum: RandomNumProtocol {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        //使用truncatingRemainder方法進(jìn)行浮點(diǎn)數(shù)取余
        lastRandom = (lastRandom * a + c).truncatingRemainder(dividingBy: m)
        return lastRandom / m
    }
}
//測(cè)試代碼:
let generator = RandomNum()
print("Here's a random number: \(generator.random())")
//打印:Here's a random number: 0.37464991998171
print("And here's a random Boolean: \(generator.randomBool())")
//打?。篈nd here's a random Boolean: true

13.1.提供默認(rèn)實(shí)現(xiàn)

可以通過(guò)協(xié)議擴(kuò)展來(lái)為協(xié)議要求的屬性、方法以及下標(biāo)提供默認(rèn)的實(shí)現(xiàn)。但是,如果遵循協(xié)議的類型也為這些要求提供了自己的實(shí)現(xiàn),那么這些自定義實(shí)現(xiàn)將會(huì)替代擴(kuò)展中的默認(rèn)實(shí)現(xiàn)被使用。
注意:通過(guò)協(xié)議擴(kuò)展為協(xié)議要求提供的默認(rèn)實(shí)現(xiàn),這和可選的協(xié)議要求不同;雖然在這兩種情況下,遵循協(xié)議的類型都無(wú)需自己實(shí)現(xiàn)這些要求,但是通過(guò)擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)可以直接調(diào)用,而無(wú)需使用可選鏈?zhǔn)秸{(diào)用。

//協(xié)議:一個(gè)寵物協(xié)議,定義發(fā)出聲音的方法makeSound
protocol PetProtocol{
    func makeSound()
    
}
//擴(kuò)展協(xié)議:提供默認(rèn)方法實(shí)現(xiàn)
extension PetProtocol{
    func makeSound(){
        print("aaaaaa。。。。")
    }
}
class Cat:PetProtocol{
     //因?yàn)橛袇f(xié)議擴(kuò)展,已經(jīng)提供了默認(rèn)的方法實(shí)現(xiàn);所以這里只遵循了協(xié)議
}
class Dog:PetProtocol{
    func makeSound() {
        print("汪汪汪。。。。")
    }
}
//測(cè)試代碼:
let cat = Cat();
cat.makeSound();  //打印:aaaaaa。。。。
let dog = Dog();
dog.makeSound()   //打?。和敉敉簟?。。。

13.2.為協(xié)議擴(kuò)展添加限制條件

在擴(kuò)展協(xié)議的時(shí)候,可以指定一些限制條件,只有遵循協(xié)議的類型滿足這些限制條件時(shí),才能獲得協(xié)議擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)。這些限制條件寫在協(xié)議名之后,使用 where子句來(lái)描述
例如:你可以擴(kuò)展Collection協(xié)議,通過(guò)限制集合元素遵循Equatable 協(xié)議, 作為標(biāo)準(zhǔn)庫(kù)的一部分,你可以使用==和!=操作符來(lái)檢查兩個(gè)元素的等價(jià)性和非等價(jià)性。

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]
print(equalNumbers.allEqual())      //打印 "true"
let differentNumbers = [100, 100, 200, 100, 200]
print(differentNumbers.allEqual())  //打印 "false"

注意:如果多個(gè)協(xié)議擴(kuò)展都為同一個(gè)協(xié)議要求提供了默認(rèn)實(shí)現(xiàn),而遵循協(xié)議的類型又同時(shí)滿足這些協(xié)議擴(kuò)展的限制條件,那么將會(huì)使用限制條件最多的那個(gè)協(xié)議擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)。

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

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

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