iOS開發(fā)---圖解KVC

什么是KVC?

KVC(Key-value coding)鍵值編碼,單看這個名字可能不太好理解。其實(shí)是指iOS的開發(fā)中,可以允許開發(fā)者通過Key名直接訪問對象的屬性,或者給對象的屬性賦值。這樣就可以在運(yùn)行時(shí)動態(tài)地訪問和修改對象的屬性。而不是在編譯時(shí)確定,很多高級的iOS開發(fā)技巧都是基于KVC實(shí)現(xiàn)的。目前網(wǎng)上關(guān)于KVC的文章在非常多,有的只是簡單地說了下用法,我會運(yùn)用圖解的方式寫下這遍文章就是為了讓大家更好的理解。

KVC方法全覽

KVC提供了一種間接訪問其屬性方法或成員變量的機(jī)制,可以通過字符串來訪問對應(yīng)的屬性方法或成員變量。

KVC方法全覽

KVC基礎(chǔ)操作

KVC取值

取值方法
  1. 通過key
- (nullable id)valueForKey:(NSString *)key;                          //直接通過Key來取值
  1. 通過keyPath
- (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通過KeyPath來取值
基于getter取值底層實(shí)現(xiàn)

當(dāng)調(diào)用valueForKey的代碼時(shí),其搜索方式如下:

你需要先看一下這張流程圖,大致知道如何運(yùn)轉(zhuǎn)的,之后再看文字描述,仔細(xì)了解其機(jī)制

基于getter取值
  1. 通過getter方法搜索實(shí)例,按照get<Key>, <key>, is<Key>, _<key>的順序查找getter`方法。如果發(fā)現(xiàn)符合的方法,就調(diào)用對應(yīng)的方法并拿著結(jié)果跳轉(zhuǎn)到第五步。否則,就繼續(xù)到下一步。

  2. 如果沒有找到簡單的getter方法,則搜索其匹配模式的方法countOf<Key>、objectIn<Key>AtIndex:、<key>AtIndexes:。

    如果找到其中的第一個和其他兩個中的一個,則就會返回一個可以響應(yīng)NSArray所有方法的代理集合(它是NSKeyValueArray,是NSArray的子類)。或者說給這個代理集合發(fā)送屬于NSArray的方法,就會以countOf<Key>,objectIn<Key>AtIndex<Key>AtIndexes這幾個方法組合的形式調(diào)用。否則,繼續(xù)到第三步。

    代理對象隨后將NSArray接收到的countOf<Key>objectIn<Key>AtIndex:、<key>AtIndexes:的消息給符合KVC規(guī)則的調(diào)用方。

    當(dāng)代理對象和KVC調(diào)用方通過上面方法一起工作時(shí),就會允許其行為類似于NSArray一樣。

  3. 如果沒有找到NSArray簡單存取方法,或者NSArray存取方法組。那么會同時(shí)查找countOf<Key>enumeratorOf<Key>、memberOf<Key>:命名的方法。

    如果找到三個方法,則創(chuàng)建一個集合代理對象,該對象響應(yīng)所有NSSet方法并返回。否則,繼續(xù)執(zhí)行第四步。

    給這個代理對象發(fā)NSSet的消息,就會以countOf<Key>,enumeratorOf<Key>,memberOf<Key>組合的形式調(diào)用。

  4. 如果沒有發(fā)現(xiàn)簡單getter方法,或集合存取方法組,以及接收類方法accessInstanceVariablesDirectly是返回YES的。搜索一個名為_<key>、_is<Key>、<key>、is<Key>的實(shí)例,根據(jù)他們的順序。

    如果發(fā)現(xiàn)對應(yīng)的實(shí)例,則立刻獲得實(shí)例可用的值并跳轉(zhuǎn)到第五步,如果重寫了類方法+ (BOOL)accessInstanceVariablesDirectly返回NO的話,那么會直接調(diào)用valueForUndefinedKey:。

  5. 如果取回的是一個對象指針,則直接返回這個結(jié)果。
    如果取回的是一個基礎(chǔ)數(shù)據(jù)類型,但是這個基礎(chǔ)數(shù)據(jù)類型是被NSNumber支持的,則存儲為NSNumber并返回。
    如果取回的是一個不支持NSNumber的基礎(chǔ)數(shù)據(jù)類型,則通過NSValue進(jìn)行存儲并返回。

  6. 如果所有情況都失敗,則調(diào)用valueForUndefinedKey:方法并拋出異常,這是默認(rèn)行為。但是子類可以重寫此方法。

KVC設(shè)值

賦值方法
  1. 通過key
  • 直接將屬性名當(dāng)做key,并設(shè)置value,即可對屬性進(jìn)行賦值。

    - (void)setValue:(nullable id)value forKey:(NSString *)key;          //通過Key來設(shè)值
    
  1. 通過keyPath
  • 除了對當(dāng)前對象的屬性進(jìn)行賦值外,還可以對其更“深層”的對象進(jìn)行賦值。KVC進(jìn)行多級訪問時(shí),直接類似于屬性調(diào)用一樣用點(diǎn)語法進(jìn)行訪問即可。例如Person屬性中有name屬性,我就可以通過Person.name進(jìn)行賦值

    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通過KeyPath來設(shè)值
    
基于setter賦值底層實(shí)現(xiàn)

這是setValue:forKey:的默認(rèn)實(shí)現(xiàn),給定輸入?yún)?shù)valuekey。試圖在接收調(diào)用對象的內(nèi)部,設(shè)置屬性名為keyvalue,通過下面的步驟

你需要先看一下這張流程圖,大致知道如何運(yùn)轉(zhuǎn)的,之后再看文字描述,仔細(xì)了解其機(jī)制

基于setter搜素
  1. 查找set<Key>:_set<Key>命名的setter,按照這個順序,如果找到的話,代碼通過setter方法完成設(shè)置。
  2. 如果沒有找到setter方法,KVC機(jī)制會檢查+ (BOOL)accessInstanceVariablesDirectly的返回值,如果accessInstanceVariablesDirectly類屬性返回YES,則查找一個命名規(guī)則為_<key>、_is<Key>、<key>、is<Key>的實(shí)例變量。根據(jù)這個順序,如果發(fā)現(xiàn)則將value賦值給實(shí)例變量,如果返回值為NO,KVC會執(zhí)行setValue:forUndefinedKey:方法。
  3. 如果沒有發(fā)現(xiàn)setter或?qū)嵗兞?,則調(diào)用setValue:forUndefinedKey:方法,并默認(rèn)提出一個異常,但是一個NSObject的子類可以提出合適的行為。

KVC批量操作

  • 在對象調(diào)用setValuesForKeysWithDictionary:方法時(shí),可以傳入一個包含key、value的字典進(jìn)去,KVC可以將所有數(shù)據(jù)按照屬性名和字典的key進(jìn)行匹配,并將valueUser對象的屬性賦值。

    //創(chuàng)建一個model模型,里面的字符串名稱必須和key的名稱對應(yīng),不然該方法會崩潰
    @interface PersonModel : NSObject
    @property (nonatomic, copy) NSString *key1;
    @property (nonatomic, copy) NSString *key2;
    @property (nonatomic, copy) NSString *id;
    @property (nonatomic, copy) NSString *key3;
    @property (nonatomic, copy) NSString *other;
    @end
      
    PersonModel *person = [[PersonModel alloc] init];
    //1.這是直接賦值,數(shù)據(jù)量小會很簡單,但是數(shù)據(jù)量一多就很麻煩,就像我們進(jìn)行網(wǎng)絡(luò)請求時(shí)
    person.key1 = dictionary[@"key1"];
    person.key2 = dictionary[@"key2"];
    person.key3 = dictionary[@"key3"];
    
    //2.通過下面該方法可以批量賦值
    //2.1如果model里面的string不存在于dictionary中,輸出結(jié)果為null;
    [person setValuesForKeysWithDictionary:dictionary];
    NSLog(@"\n%@\n%@\n%@\n%@\n", person.key1,person.key2,person.key3,person.other);
    
    //輸出結(jié)果
    test1
    test2
    test3
    (null)
    
    //2.2如果dictionary中有的元素,moedl中沒有運(yùn)行會直接出錯,那么我們應(yīng)該怎么解決?
    //我們需要實(shí)現(xiàn)setValue:forUndefinedKey:這個方法能過濾掉不存在的鍵值
    -(void)setValue:(id)value forUndefinedKey:(NSString *)key{
      //這里我們不需要寫任何內(nèi)容
    }
    person.key1 = dictionary[@"key1"];
    person.key2 = dictionary[@"key2"];
    person.key3 = dictionary[@"key3"];
    [person setValuesForKeysWithDictionary:dictionary];
    NSLog(@"\n%@\n%@\n%@\n", person.key1,person.key2,person.key3);
    
    //輸出結(jié)果
    test1
    test2
    test3
    
    //2.3如果dictionar中的key與model中的變量名字不同,怎么賦值?
    //還是從setValue:forUndefinedKey:這個方法入手
    -(void)setValue:(id)value forUndefinedKey:(NSString *)key{
      if ([key isEqualToString:@"key2"]) {
        self.id = value;
    }
    person.key1 = dictionary[@"key1"];
    person.id = dictionary[@"key2"];
    person.key3 = dictionary[@"key3"];
    [person setValuesForKeysWithDictionary:dictionary];
    NSLog(@"\n%@\n%@\n%@\n", person.key1,person.id,person.key3);
      
    //輸出結(jié)果
    test1
    test2
    test3
    

KVC集合屬性操作

KVC提供的valueForKeyPath:方法非常強(qiáng)大,可以通過該方法對集合對象進(jìn)行“深入”操作,在其keyPath中嵌套集合運(yùn)算符,例如求一個數(shù)組中對象某個屬性的count。(集合對象主要指NSArrayNSSet,但不包括NSDictionary)

集合運(yùn)算符格式

上面表達(dá)式主要分為三部分,left部分是要操作的集合對象,如果調(diào)用KVC的對象本來就是集合對象,則left可以為空。中間部分是表達(dá)式,表達(dá)式一般以@符號開頭。后面是進(jìn)行運(yùn)算的屬性。

  • 為了驗(yàn)證操作符,我們需要先建立一個Model類
@interface Transaction : NSObject
@property (nonatomic, strong) NSString *payee;
@property (nonatomic, strong) NSNumber *amount;
@property (nonatomic, strong) NSDate *date;
@end

@interface BankAccount : NSObject
@property (nonatomic, strong) NSArray *transcationArray;
@end

集合操作符

處理集合包含的對象,并根據(jù)操作符的不同返回不同的類型,返回值以NSNumber為主。

//@avg用來計(jì)算集合中right keyPath指定的屬性的平均值
NSNumber *transactionAverage = [bankAccount.transcationArray valueForKeyPath:@"@avg.amount"];
NSLog(@"@avg = %@", transactionAverage);

//@count用來計(jì)算集合的總數(shù)
NSNumber *numberOfTransactions = [bankAccount.transcationArray valueForKeyPath:@"@count"];
NSLog(@"@count = %@", numberOfTransactions);
//備注:@count操作符比較特殊,它不需要寫right keyPath,即使寫了也會被忽略。

//@sum用來計(jì)算集合中right keyPath指定的屬性的總和。
NSNumber *amountSum = [bankAccount.transcationArray valueForKeyPath:@"@sum.amount"];
NSLog(@"@sum = %@", amountSum);

//@max用來查找集合中right keyPath指定的屬性的最大值
NSNumber *amountMax = [bankAccount.transcationArray valueForKeyPath:@"@max.amount"];
NSLog(@"@max = %@", amountMax);

//@min用來查找集合中right keyPath指定的屬性的最小值。
NSNumber *amountMin = [bankAccount.transcationArray valueForKeyPath:@"@min.amount"];
NSLog(@"@min = %@", amountMin);

數(shù)組操作符

根據(jù)操作符的條件,將符合條件的對象包含在數(shù)組中返回。

//@unionOfObjects將集合對象中,所有payee對象放在一個數(shù)組中并返回
NSArray *payees = [bankAccount.transcationArray valueForKeyPath:@"@unionOfObjects.payee"];
NSLog(@"@unionOfObjects = %@", payees);

//@distinctUnionOfObjects將集合對象中,所有payee對象放在一個數(shù)組中,并將數(shù)組進(jìn)行去重后返回。
NSArray *distinctPayees = [bankAccount.transcationArray valueForKeyPath:@"@distinctUnionOfObjects.payee"];
NSLog(@"@distinctUnionOfObjects = %@", distinctPayees);
//注意:以上兩個方法中,如果操作的屬性為nil,在添加到數(shù)組中時(shí)會導(dǎo)致Crash。

嵌套操作符

處理集合對象中嵌套其他集合對象的情況,返回結(jié)果也是一個集合對象。

//@distinctUnionOfArrays是用來操作集合內(nèi)部的集合對象,將所有right keyPath對應(yīng)的對象放在一個數(shù)組中,并進(jìn)行排重。
NSArray *collectedPayees = [allArray valueForKeyPath:@"@unionOfArrays.payee"];
NSLog(@"@unionOfArrays = %@", collectedPayees);

//@distinctUnionOfSets是用來操作集合內(nèi)部的集合對象,將所有right keyPath對應(yīng)的對象放在一個set中,并進(jìn)行排重。
NSArray *collectedDistinctPayees = [allArray valueForKeyPath:@"@distinctUnionOfArrays.payee"];
NSLog(@"@distinctUnionOfArrays = %@", collectedDistinctPayees);

KVC與容器類

對象的屬性可以是一對一的,也可以是一對多的。一對多的屬性要么是有序的(數(shù)組),要么是無序的(集合)。

??:根據(jù)KVO的實(shí)現(xiàn)原理,是在運(yùn)行時(shí)生成新的子類并重寫其setter方法,在其內(nèi)容發(fā)生改變時(shí)發(fā)送消息。但這只是對屬性直接進(jìn)行賦值會觸發(fā),如果屬性是容器對象,對容器對象進(jìn)行addremove操作,則不會調(diào)用KVO的方法??梢酝ㄟ^KVC對應(yīng)的API來配合使用,使容器對象內(nèi)部發(fā)生改變時(shí)也能觸發(fā)KVO。

在進(jìn)行容器對象操作時(shí),先通過key或者keyPath獲取集合對象,然后再對容器對象進(jìn)行addremove等操作時(shí),就會觸發(fā)KVO的消息通知了。

KVC與有序容器(NSMutableArray)

取值方法
  1. 通過key
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//該方法返回一個可變有序數(shù)組
  1. 通過keyPath
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
//該方法返回一個可變有序數(shù)組
NSMutableArray取值底層實(shí)現(xiàn)

當(dāng)調(diào)用mutableArrayValueForKey的代碼時(shí),其搜索方式如下:

你需要先看一下這張流程圖,大致知道如何運(yùn)轉(zhuǎn)的,之后再看文字描述,仔細(xì)了解其機(jī)制

NSMutableArray取值底層實(shí)現(xiàn)
  1. 搜索insertObject:in<Key>AtIndex: , removeObjectFrom<Key>AtIndex: 或者 insert<Key>AdIndexes , remove<Key>AtIndexes 格式的方法
    如果至少找到一個insert方法和一個remove方法,那么同樣返回一個可以響應(yīng)NSMutableArray所有方法代理集合(類名是NSKeyValueFastMutableArray),那么給這個代理集合發(fā)送NSMutableArray的方法,以insertObject:in<Key>AtIndex: , removeObjectFrom<Key>AtIndex: 或者 insert<Key>AdIndexes , remove<Key>AtIndexes組合的形式調(diào)用。

    當(dāng)對象接收一個mutableArrayValueForKey:消息并實(shí)現(xiàn)可選替換方法,例如replaceObjectIn<Key>AtIndex:withObject:replace<Key>AtIndexes:with<Key>:方法,代理對象會在適當(dāng)?shù)那闆r下使用它們,以獲得最佳性能。

  2. 如果上步的方法沒有找到,則搜索set<Key>: 格式的方法,如果找到,那么發(fā)送給代理集合的NSMutableArray最終都會調(diào)用set<Key>:方法。

    也就是說,mutableArrayValueForKey:取出的代理集合修改后,用set<Key>: 重新賦值回去去。這樣做效率會低很多。所以推薦實(shí)現(xiàn)上面的方法。

  3. 如果上一步的方法還還沒有找到,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認(rèn)行為),會按_<key>,<key>,的順序搜索成員變量名,如果找到,那么發(fā)送的NSMutableArray消息方法直接交給這個成員變量處理。

  4. 如果還是找不到,則調(diào)用valueForUndefinedKey:

KVC與無序容器(NSMutableSet)

取值方法
  1. 通過key
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
//方法返回一個可變的無序數(shù)組
  1. 通過keyPath
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
//方法返回一個可變的無序數(shù)組
NSMutableSet取值底層實(shí)現(xiàn)

當(dāng)調(diào)用NSMutableSet的代碼時(shí),其搜索方式如下:

你需要先看一下這張流程圖,大致知道如何運(yùn)轉(zhuǎn)的,之后再看文字描述,仔細(xì)了解其機(jī)制

NSMutableSet取值底層實(shí)現(xiàn)
  1. 搜索addObject<Key>Object: , remove<Key>Object: 或者 add<Key> , remove<Key> 格式的方法
    如果至少找到一個insert方法和一個remove方法,那么同樣返回一個可以響應(yīng)NSMutableSet所有方法代理集合(類名是NSKeyValueFastMutableSet2),那么給這個代理集合發(fā)送NSMutableSet的方法,以addObject<Key>Object: , remove<Key>Object: 或者 add<Key> , remove<Key>組合的形式調(diào)用。還有兩個可選實(shí)現(xiàn)的接口:intersect<Key> , set<Key>: 。
  2. 如果receiverManagedObject,那么就不會繼續(xù)搜索。
  3. 如果上一步的方法沒有找到,則搜索set<Key>: 格式的方法,如果找到,那么發(fā)送給代理集合的NSMutableSet最終都會調(diào)用set<Key>:方法。 也就是說,mutableSetValueForKey取出的代理集合修改后,用set<Key>: 重新賦值回去去。這樣做效率會低很多。所以推薦實(shí)現(xiàn)上面的方法。
  4. 如果上一步的方法還沒有找到,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認(rèn)行為),會按_<key>,<key>的順序搜索成員變量名,如果找到,那么發(fā)送的NSMutableSet消息方法直接交給這個成員變量處理。
  5. 如果還是找不到,調(diào)用valueForUndefinedKey:
    可見,除了檢查receiverManagedObject以外,其搜索順序和mutableArrayValueForKey基本一至

KVC異常處理

  1. key或者keyPath發(fā)生錯誤

當(dāng)根據(jù)KVC搜索規(guī)則,沒有搜索到對應(yīng)的key或者keyPath,則會調(diào)用對應(yīng)的異常方法。異常方法的默認(rèn)實(shí)現(xiàn),在異常發(fā)生時(shí)會拋出一個NSUndefinedKeyException的異常,并且應(yīng)用程序Crash

我們可以重寫下面兩個方法:

- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
  1. 傳參為nil

通常情況下,KVC不允許你要在調(diào)用setValue:屬性值 forKey:(或者keyPath)時(shí)對非對象傳遞一個nil的值。因?yàn)橹殿愋褪遣荒転?code>nil的。如果你不小心傳了,KVC會調(diào)用setNilValueForKey:方法。這個方法默認(rèn)是拋出異常,所以一般而言最好還是重寫這個方法。

我們可以重寫這個方法:

-(void)setNilValueForKey:(NSString *)key{
    NSLog(@"不能將%@設(shè)成nil",key);
}

KVC處理非對象

KVC是支持基礎(chǔ)數(shù)據(jù)類型和結(jié)構(gòu)體的,可以在settergetter的時(shí)候,通過NSValueNSNumber來轉(zhuǎn)換為OC對象。該方法valueForKey:總是返回一個id對象,如果原本的變量類型是值類型或者結(jié)構(gòu)體,返回值會封裝成NSNumber或者NSValue對象。這兩個類會處理從數(shù)字,布爾值到指針和結(jié)構(gòu)體任何類型。然后開發(fā)者需要手動轉(zhuǎn)換成原來的類型。盡管valueForKey:會自動將值類型封裝成對象,但是setValue:forKey:卻不行。你必須手動將值類型轉(zhuǎn)換成NSNumber或者NSValue類型,才能傳遞過去。

  • 可以調(diào)用initWithBool:方法對基礎(chǔ)數(shù)據(jù)類型進(jìn)行包裝
@property (nonatomic, assign, readonly) BOOL boolValue;
- (NSNumber *)initWithBool:(BOOL)value 

KVC屬性驗(yàn)證

KVC提供了屬性值,用來驗(yàn)證key對應(yīng)的Value是否可用的方法

  • 在調(diào)用KVC時(shí)可以先進(jìn)行驗(yàn)證,驗(yàn)證通過下面兩個方法進(jìn)行,支持keykeyPath兩種方式。驗(yàn)證方法默認(rèn)實(shí)現(xiàn)返回YES,可以通過重寫對應(yīng)的方法修改驗(yàn)證邏輯。

    驗(yàn)證方法需要我們手動調(diào)用,并不會在進(jìn)行KVC的過程中自動調(diào)用。

- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;

這個方法的默認(rèn)實(shí)現(xiàn)是去探索類里面是否有一個這樣的方法:-(BOOL)validate<Key>:error:如果有這個方法,就調(diào)用這個方法來返回,沒有的話就直接返回YES

@implementation Address
-(BOOL)validateCountry:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{  //在implementation里面加這個方法,它會驗(yàn)證是否設(shè)了非法的value
    NSString* country = *value;
    country = country.capitalizedString;
    if ([country isEqualToString:@"Japan"]) {
        return NO;                                                                             //如果國家是日本,就返回NO,這里省略了錯誤提示,
    }
    return YES;
}
@end
NSError* error;
id value = @"japan";
NSString* key = @"country";
BOOL result = [add validateValue:&value forKey:key error:&error]; //如果沒有重寫-(BOOL)-validate<Key>:error:,默認(rèn)返回Yes
if (result) {
    NSLog(@"鍵值匹配");
    [add setValue:value forKey:key];
}
else{
    NSLog(@"鍵值不匹配"); //不能設(shè)為日本,其他國家都行
}
NSString* country = [add valueForKey:@"country"];
NSLog(@"country:%@",country);
//打印結(jié)果 
KVCDemo[867:58871] 鍵值不匹配
KVCDemo[867:58871] country:China

KVC適用場景

動態(tài)的取值和設(shè)值

利用KVC動態(tài)的取值和設(shè)值是最基本的用途了。相信每一個iOS開發(fā)者都能熟練掌握

Model和字典轉(zhuǎn)換

在上面KVC批量操作已闡述

用KVC來訪問和修改私有變量

根據(jù)上面的實(shí)現(xiàn)原理我們知道,KVC本質(zhì)上是操作方法列表以及在內(nèi)存中查找實(shí)例變量。我們可以利用這個特性訪問類的私有變量,例如下面在.m中定義的私有成員變量和屬性,都可以通過KVC的方式訪問。

這個操作對readonly的屬性,@protected的成員變量,都可以正常訪問。如果不想讓外界訪問類的成員變量,則可以將accessInstanceVariablesDirectly屬性賦值為NO。

修改一些控件的內(nèi)部屬性

這也是iOS開發(fā)中必不可少的小技巧。眾所周知很多UI控件都由很多內(nèi)部UI控件組合而成的,但是Apple度沒有提供這訪問這些控件的API,這樣我們就無法正常地訪問和修改這些控件的樣式。而KVC在大多數(shù)情況可下可以解決這個問題。

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

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