KVC詳解

KVC(Key-value coding)鍵值編碼屑那,單看這個名字可能不太好理解落恼。其實翻譯一下就很簡單了降传,就是指iOS的開發(fā)中玉转,可以允許開發(fā)者通過Key名直接訪問對象的屬性佳谦,或者給對象的屬性賦值。而不需要調(diào)用明確的存取方法吩屹。這樣就可以在運行時動態(tài)地訪問和修改對象的屬性。而不是在編譯時確定拧抖,這也是iOS開發(fā)中的黑魔法之一煤搜。很多高級的iOS開發(fā)技巧都是基于KVC實現(xiàn)的。目前網(wǎng)上關(guān)于KVC的文章在非常多唧席,有的只是簡單地說了下用法擦盾,有的講得深入但是在使用場景和最佳實踐沒有說明嘲驾,我寫下這遍文章就是給大家詳解一個最完整最詳細(xì)的KVC。

KVC在iOS中的定義

無論是Swift還是Objective-C厌衙,KVC的定義都是對NSObject的擴展來實現(xiàn)的(Objective-C中有個顯式的NSKeyValueCoding類別名距淫,而Swift沒有,也不需要)婶希。所以對于所有繼承了NSObject的類型榕暇,也就是幾乎所有的Objective-C對象都能使用KVC(一些純Swift類和結(jié)構(gòu)體是不支持KVC的),下面是KVC最為重要的四個方法

- (nullable id)valueForKey:(NSString *)key;                          //直接通過Key來取值
- (void)setValue:(nullable id)value forKey:(NSString *)key;          //通過Key來設(shè)值
- (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通過KeyPath來取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通過KeyPath來設(shè)值

當(dāng)然NSKeyValueCoding類別中還有其他的一些方法喻杈,下面列舉一些

+ (BOOL)accessInstanceVariablesDirectly;
//默認(rèn)返回YES彤枢,表示如果沒有找到Set<Key>方法的話,會按照_key筒饰,_iskey缴啡,key,iskey的順序搜索成員瓷们,設(shè)置成NO就不這樣搜索

- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//KVC提供屬性值正確性?驗證的API业栅,它可以用來檢查set的值是否正確、為不正確的值做一個替換值或者拒絕設(shè)置新值并返回錯誤原因谬晕。

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//這是集合操作的API碘裕,里面還有一系列這樣的API,如果屬性是一個NSMutableArray攒钳,那么可以用這個方法來返回帮孔。

- (nullable id)valueForUndefinedKey:(NSString *)key;
//如果Key不存在,且沒有KVC無法搜索到任何和Key有關(guān)的字段或者屬性不撑,則會調(diào)用這個方法文兢,默認(rèn)是拋出異常。

- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//和上一個方法一樣焕檬,但這個方法是設(shè)值姆坚。

- (void)setNilValueForKey:(NSString *)key;
//如果你在SetValue方法時面給Value傳nil,則會調(diào)用這個方法

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//輸入一組key,返回該組key對應(yīng)的Value实愚,再轉(zhuǎn)成字典返回旷偿,用于將Model轉(zhuǎn)到字典。

上面的這些方法在碰到特殊情況或者有特殊需求還是會用到的爆侣,所以也是可以c了解一下萍程。后面的代碼示例會有講到其中的一些方法。

同時蘋果對一些容器類比如NSArray或者NSSet等兔仰,KVC有著特殊的實現(xiàn)茫负。建議有基礎(chǔ)的或者英文好的開發(fā)者直接去看蘋果的官方文檔,相信你會對KVC的理解更上一個臺階乎赴。

可能有些讀者不知道怎么查官方文檔忍法,在這里說明一下潮尝。打開Xcode,查看最上面的菜單饿序,點最后一個Help -> Documentation and API Reference,然后就可以打開官方文檔了勉失。

KVC是怎么尋找Key的

KVC是怎么使用的,我相信絕大多數(shù)的開發(fā)者都很清楚原探,我在這里就不再寫簡單的使用KVC來設(shè)值和取值的代碼了乱凿,首?先我們來探討KVC在內(nèi)部是按什么樣的順序來尋找key的。

設(shè)值

當(dāng)調(diào)用setValue:屬性值 forKey:@”name“的代碼時咽弦,底層的執(zhí)行機制如下:

  • 程序優(yōu)先調(diào)用set<Key>:屬性值方法徒蟆,代碼通過setter方法完成設(shè)置。注意型型,這里的<key>是指成員變量名段审,首字母大小寫要符合KVC的命名規(guī)則,下同

  • 如果沒有找到setName:方法闹蒜,KVC機制會檢查+ (BOOL)accessInstanceVariablesDirectly方法有沒有返回YES寺枉,默認(rèn)該方法會返回YES,如果你重寫了該方法讓其返回NO的話绷落,那么在這一步KVC會執(zhí)行setValue:forUndefinedKey:方法姥闪,不過一般開發(fā)者不會這么做。所以KVC機制會搜索該類里面有沒有名為_<key>的成員變量嘱函,無論該變量是在類接口處定義甘畅,還是在類實現(xiàn)處定義埂蕊,也無論用了什么樣的訪問修飾符往弓,只在存在以_<key>命名的變量,KVC都可以對該成員變量賦值蓄氧。

  • 如果該類即沒有set<key>:方法函似,也沒有_<key>成員變量,KVC機制會搜索_is<Key>的成員變量喉童。

  • 和上面一樣撇寞,如果該類即沒有set<Key>:方法,也沒有_<key>_is<Key>成員變量堂氯,KVC機制再會繼續(xù)搜索<key>is<Key>的成員變量蔑担。再給它們賦值。

  • 如果上面列出的方法或者成員變量都不存在咽白,系統(tǒng)將會執(zhí)行該對象的setValue:forUndefinedKey:方法啤握,默認(rèn)是拋出異常。

如果開發(fā)者想讓這個類禁用KVC里晶框,那么重寫+ (BOOL)accessInstanceVariablesDirectly方法讓其返回NO即可排抬,這樣的話如果KVC沒有找到set<Key>:屬性名時懂从,會直接用setValue:forUndefinedKey:方法。

下面我們來讓代碼來測試一下上面的KVC機制

@interface Dog : NSObject
@end
@implementation Dog
{
     NSString* toSetName;
    NSString* isName;
    //NSString* name;
    NSString* _name;
    NSString* _isName;
}
// -(void)setName:(NSString*)name{
//     toSetName = name;
// }
//-(NSString*)getName{
//    return toSetName;
//}
+(BOOL)accessInstanceVariablesDirectly{
    return NO;
}
-(id)valueForUndefinedKey:(NSString *)key{
    NSLog(@"出現(xiàn)異常蹲蒲,該key不存在%@",key);
    return nil;
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
     NSLog(@"出現(xiàn)異常番甩,該key不存在%@",key);
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Dog* dog = [Dog new];
        [dog setValue:@"newName" forKey:@"name"];
        NSString* name = [dog valueForKey:@"toSetName"];
        NSLog(@"%@",name);
    }
    return 0;
}

首先我們先重寫accessInstanceVariablesDirectly方法讓其返回NO,再運行代碼(注意上面注釋的部分)届搁,Xcode直接打印出

2016-04-15 15:52:12.039 DemoKVC[9681:287627] 出現(xiàn)異常缘薛,該key不存在name
2016-04-15 15:52:12.040 DemoKVC[9681:287627] 出現(xiàn)異常,該key不存在toSetName
2016-04-15 15:52:12.040 DemoKVC[9681:287627] (null)

這說明了重寫+(BOOL)accessInstanceVariablesDirectly方法讓其返回NO后,KVC找不到setName:方法后咖祭,不再去找name系列成員變量掩宜,而是直接調(diào)用setValue:forUndefinedKey:方法

所以開發(fā)者如果不想讓自己的類實現(xiàn)KVC,就可以這么做么翰。

下面那兩個settergetter的注釋取消掉牺汤,再把

NSString* name = [dog valueForKey:@"toSetName"]; 換成 NSString* name = [dog valueForKey:@"name"];

XCode就可以正確地打印出正確的值了

2016-04-15 15:56:22.130 DemoKVC[9726:289258] newName

下面再注釋掉accessInstanceVariablesDirectly方法,就能測試其他的key查找順序了浩嫌,為了節(jié)省篇幅檐迟,剩下的的KVC對于key尋找機制就不在這里展示了,有興趣的讀者可以寫代碼去驗證码耐。

取值

當(dāng)調(diào)用valueForKey:@”name“的代碼時追迟,KVC對key的搜索方式不同于setValue:屬性值 forKey:@”name“,其搜索方式如下:

  • 首先按get<Key>,<key>,is<Key>的順序方法查找getter方法骚腥,找到的話會直接調(diào)用敦间。如果是BOOL或者Int等值類型, 會將其包裝成一個NSNumber對象束铭。

  • 如果上面的getter沒有找到廓块,KVC則會查找countOf<Key>,objectIn<Key>AtIndex<Key>AtIndexes格式的方法。如果countOf<Key>方法和另外兩個方法中的一個被找到契沫,那么就會返回一個可以響應(yīng)NSArray所?有方法的代理集合(它是NSKeyValueArray带猴,是NSArray的子類),調(diào)用這個代理集合的方法懈万,或者說給這個代理集合發(fā)送屬于NSArray的方法拴清,就會以countOf<Key>,objectIn<Key>AtIndex?或<Key>AtIndexes這幾個方法組合的形式調(diào)用。還有一個可選的get<Key>:range:方法会通。所以你想重新定義KVC的一些功能口予,你可以添加這些方法,需要注意的是你的方法名要符合KVC的標(biāo)準(zhǔn)命名方法涕侈,包括方法簽名沪停。

  • 如果上面的方法沒有找到,那么會同時查找countOf<Key>驾凶,enumeratorOf<Key>,memberOf<Key>格式的方法牙甫。如果這三個方法都找到掷酗,那么就返回一個可以響應(yīng)NSSet所的方法的代理集合,和上面一樣窟哺,給這個代理集合發(fā)NSSet的消息泻轰,就會以countOf<Key>enumeratorOf<Key>,memberOf<Key>組合的形式調(diào)用且轨。

可能上面的兩條查找方案對讀者不好理解浮声,簡單來說就是如果你在自己的類自定義了KVC的實現(xiàn),并且實現(xiàn)了上面的方法旋奢,那么恭喜你泳挥,你可以?將返回的對象當(dāng)數(shù)組(NSArray)用了,詳情見下面的示例代碼

  • 如果還沒有找到至朗,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認(rèn)行為)屉符,那么和先前的設(shè)值一樣,會按_<key>,_is<Key>,<key>,is<Key>的順序搜索成員變量名锹引,這里不推薦這么做矗钟,因為這樣直接訪問實例變量破壞了封裝性,使代碼更脆弱嫌变。如果重寫了類方法+ (BOOL)accessInstanceVariablesDirectly返回NO的話吨艇,那么會直接調(diào)用valueForUndefinedKey:

  • 還沒有找到的話,調(diào)用valueForUndefinedKey:

下面再上代碼測試

@interface TwoTimesArray : NSObject
-(void)incrementCount;
-(NSUInteger)countOfNumbers;
-(id)objectInNumbersAtIndex:(NSUInteger)index;
@end
@interface TwoTimesArray()
@property (nonatomic,readwrite,assign) NSUInteger count;
@property (nonatomic,copy) NSString* arrName;
@end
@implementation TwoTimesArray
-(void)incrementCount{
    self.count ++;
}
-(NSUInteger)countOfNumbers{
    return self.count;

-(id)objectInNumbersAtIndex:(NSUInteger)index{     //當(dāng)key使用numbers時腾啥,KVC會找到這兩個方法东涡。
    return @(index * 2);
}
-(NSInteger)getNum{                 //第一個,自己一個一個注釋試
    return 10;
}
-(NSInteger)num{                       //第二個
    return 11;
}
-(NSInteger)isNum{                    //第三個
    return 12;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        TwoTimesArray* arr = [TwoTimesArray new];
        NSNumber* num =   [arr valueForKey:@"num"];
        NSLog(@"%@",num);
        id ar = [arr valueForKey:@"numbers"];
        NSLog(@"%@",NSStringFromClass([ar class]));
         NSLog(@"0:%@     1:%@     2:%@     3:%@",ar[0],ar[1],ar[2],ar[3]);
        [arr incrementCount];                                                                            //count加1
        NSLog(@"%lu",(unsigned long)[ar count]);                                                         //打印出1
        [arr incrementCount];                                                                            //count再加1
        NSLog(@"%lu",(unsigned long)[ar count]);                                                         //打印出2

        [arr setValue:@"newName" forKey:@"arrName"];
        NSString* name = [arr valueForKey:@"arrName"];
        NSLog(@"%@",name);

    }
    return 0;
}
//打印結(jié)果 
2016-04-17 15:39:42.214 KVCDemo[1088:74481] 10
2016-04-17 15:39:42.215 KVCDemo[1088:74481] NSKeyValueArray
2016-04-17 15:41:24.713 KVCDemo[1102:75424] 0:0     1:2     2:4     3:6                 //太明顯了,直接調(diào)用-(id)objectInNumbersAtIndex:(NSUInteger)index;方法
2016-04-17 15:39:42.215 KVCDemo[1088:74481] 1
2016-04-17 15:39:42.215 KVCDemo[1088:74481] 2
2016-04-17 15:39:42.215 KVCDemo[1088:74481] newName

很明顯倘待,上面的代碼充分說明了說明了KVC在調(diào)用ValueforKey:@”name“時搜索key的機制疮跑。不過還有些功能沒有全部列出,有興趣的讀者可以寫代碼去驗證延柠。

在KVC中使用keyPath

然而在開發(fā)過程中祸挪,一個類的成員變量有可能是自定義類或其他的復(fù)雜數(shù)據(jù)類型锣披,你可以先用KVC獲取該屬性贞间,然后再次用KVC來獲取這個自定義類的屬性,但這樣是比較繁瑣的雹仿,對此增热,KVC提供了一個解決方案,那就是鍵路徑keyPath胧辽。

- (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通過KeyPath來取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通過KeyPath來設(shè)值

@interface Address : NSObject

@end
@interface Address()
@property (nonatomic,copy)NSString* country;
@end
@implementation Address
@end
@interface People : NSObject
@end
@interface People()
@property (nonatomic,copy) NSString* name;
@property (nonatomic,strong) Address* address;
@property (nonatomic,assign) NSInteger age;
@end
@implementation People
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        People* people1 = [People new];
        Address* add = [Address new];
        add.country = @"China";
        people1.address = add;
        NSString* country1 = people1.address.country;
        NSString * country2 = [people1 valueForKeyPath:@"address.country"];
        NSLog(@"country1:%@   country2:%@",country1,country2);
        [people1 setValue:@"USA" forKeyPath:@"address.country"];
         country1 = people1.address.country;
        country2 = [people1 valueForKeyPath:@"address.country"];
        NSLog(@"country1:%@   country2:%@",country1,country2);
    }
    return 0;
}
//打印結(jié)果 
2016-04-17 15:55:22.487 KVCDemo[1190:82636] country1:China   country2:China
2016-04-17 15:55:22.489 KVCDemo[1190:82636] country1:USA   country2:USA

上面的代碼簡單在展示了keyPath是怎么用的峻仇。如果你不小心錯誤的使用了key而非keyPath的話,比如上面的代碼中KVC會直接查找address.country這個屬性邑商,很明顯摄咆,這個屬性并不存在凡蚜,所以會再調(diào)用undefinedKey相關(guān)方法。而KVC對于keyPath是搜索機制第一步就是分離key吭从,用小數(shù)點.來分割key朝蜘,然后再像普通key一樣按照先前介紹的順序搜索下去。

KVC如何處理異常

KVC中最常見的異常就是不小心使用了錯誤的key涩金,或者在設(shè)值中不小心傳遞了nil的值谱醇,KVC中有專門的方法來處理這些異常。

通常在用KVC操作Model時步做,拋出異常的那兩個方法是需要重寫的副渴。雖然一般很小出現(xiàn)傳遞了錯誤的Key值這種情況,但是如果不小心出現(xiàn)了全度,直接拋出異常讓APP崩潰顯然是不合理的煮剧。一般在這里直接讓這個key打印出來即可,或者有些特殊情況需要特殊處理将鸵。通常情況下轿秧,KVC不允許你要在調(diào)用setValue:屬性值 forKey:@”name“(或者keyPath)時對非對象傳遞一個nil的值。很簡單咨堤,因為值類型是不能為nil的菇篡。如果你不小心傳了,KVC會調(diào)用setNilValueForKey:方法一喘。這個方法默認(rèn)是拋出異常驱还,所以一般而言最好還是重寫這個方法。

  [people1 setValue:nil forKey:@"age"]
   *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<People 0x100200080> setNilValueForKey]: could not set nil as the value for the key age.' // 調(diào)用setNilValueForKey拋出異常

如果重寫setNilValueForKey:就沒問題了

@implementation People

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

@end
//打印出
2016-04-17 16:19:55.298 KVCDemo[1304:92472] 不能將age設(shè)成nil

KVC處理非對象和自定義對象

不是每一個方法都返回對象凸克,但是valueForKey:總是返回一個id對象议蟆,如果原本的變量類型是值類型或者結(jié)構(gòu)體,返回值會封裝成NSNumber或者NSValue對象萎战。這兩個類會處理從數(shù)字咐容,布爾值到指針和結(jié)構(gòu)體任何類型。然后開以者需要手動轉(zhuǎn)換成原來的類型蚂维。盡管valueForKey:會自動將值類型封裝成對象戳粒,但是setValue:forKey:卻不行。你必須手動將值類型轉(zhuǎn)換成NSNumber或者NSValue類型虫啥,才能傳遞過去蔚约。

對于自定義對象,KVC也會正確地設(shè)值和取值涂籽。因為傳遞進(jìn)去和取出來的都是id類型苹祟,所以需要開發(fā)者自己擔(dān)保類型的正確性,運行時Objective-C在發(fā)送消息的會檢查類型,如果錯誤會直接拋出異常树枫。

Address* add2 = [Address new];
add2.country = @"England";
[people1 setValue:add2 forKey:@"address"];
NSString* country1 = people1.address.country;
NSString * country2 = [people1 valueForKeyPath:@"address.country"];
NSLog(@"country1:%@   country2:%@",country1,country2);
//打印結(jié)果
2016-04-17 16:29:36.349 KVCDemo[1346:95910] country1:England   country2:England

KVC與容器類

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

不可變的有序容器屬性(NSArray)和無序容器屬性(NSSet)一般可以使用valueForKey:來獲取。比如有一個叫itemsNSArray屬性舔清,你可以用valurForKey:@"items"來獲取這個屬性丝里。前面valueForKey:key搜索模式中,我們發(fā)現(xiàn)其實KVC使用了一種更靈活的方式來管理容器類体谒。蘋果的官方文檔也推薦我們實現(xiàn)這些這些特殊的訪問器杯聚。

而當(dāng)對象的屬性是可變的容器時,對于有序的容器抒痒,可以用下面的方法:

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;

該方法返回一個可變有序數(shù)組幌绍,如果調(diào)用該方法,KVC的搜索順序如下

  • 搜索insertObject:in<Key>AtIndex: , removeObjectFrom<Key>AtIndex: 或者 insert<Key>AdIndexes , remove<Key>AtIndexes 格式的方法

    如果至少找到一個insert方法和一個remove方法故响,那么同樣返回一個可以響應(yīng)NSMutableArray所有方法代理集合(類名是NSKeyValueFastMutableArray2)傀广,那么給這個代理集合發(fā)送NSMutableArray的方法,以insertObject:in<Key>AtIndex: , removeObjectFrom<Key>AtIndex: 或者 insert<Key>AdIndexes , remove<Key>AtIndexes組合的形式調(diào)用彩届。還有兩個可選實現(xiàn)的接口:replaceOnjectAtIndex:withObject:,replace<Key>AtIndexes:with<Key>:

  • 如果上步的方法沒有找到贮聂,則搜索set<Key>: 格式的方法寨辩,如果找到吓懈,那么發(fā)送給代理集合的NSMutableArray最終都會調(diào)用set<Key>:方法靡狞。 也就是說,mutableArrayValueForKey:取出的代理集合修改后甸怕,用set<Key>: 重新賦值回去去甘穿。這樣做效率會低很多蕾各。所以推薦實現(xiàn)上面的方法庆揪。

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

  • 如果還是找不到符衔,則調(diào)用valueForUndefinedKey:判族。

  • 關(guān)于mutableArrayValueForKey:的適用場景项戴,我在網(wǎng)上找了很多,發(fā)現(xiàn)其一般是用在對NSMutableArray添加Observer上辩撑。如果對象屬性是個NSMutableArray合冀、NSMutableSet项贺、NSMutableDictionary等集合類型時敬扛,你給它添加KVO時,你會發(fā)現(xiàn)當(dāng)你添加或者移除元素時并不能接收到變化谍珊。因為KVO的本質(zhì)是系統(tǒng)監(jiān)測到某個屬性的內(nèi)存地址或常量改變時急侥,會添加上- (void)willChangeValueForKey:(NSString *)key- (void)didChangeValueForKey:(NSString *)key方法來發(fā)送通知坏怪,所以一種解決方法是手動調(diào)用者兩個方法铝宵,但是并不推薦华畏,你永遠(yuǎn)無法像系統(tǒng)一樣真正知道這個元素什么時候被改變亡笑。另一種便是利用使用mutableArrayValueForKey:了仑乌。

@interface demo : NSObject
@property (nonatomic,strong) NSMutableArray* arr;
@end
@implementation demo
-(id)init{
    if (self == [super init]){
        _arr = [NSMutableArray new];
        [self addObserver:self forKeyPath:@"arr" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    }
    return self;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
    NSLog(@"%@",change);
}
-(void)dealloc{
    [self removeObserver:self forKeyPath:@"arr"]; //一定要在dealloc里面移除觀察
}
-(void)addItem{
    [_arr addObject:@"1"];
}
-(void)addItemObserver{
    [[self mutableArrayValueForKey:@"arr"] addObject:@"1"];
}
-(void)removeItemObserver{
    [[self mutableArrayValueForKey:@"arr"] removeLastObject];
}
@end
然后再:
demo* d = [demo new];
[d addItem];
[d addItemObserver];
[d removeItemObserver];

打印結(jié)果
2016-04-18 17:48:22.675 KVCDemo[32647:505864] {
    indexes = "<_NSCachedIndexSet: 0x100202c70>[number of indexes: 1 (in 1 ranges), indexes: (1)]";
    kind = 2;
    new =     (
        1
    );
}
2016-04-18 17:48:22.677 KVCDemo[32647:505864] {
    indexes = "<_NSCachedIndexSet: 0x100202c70>[number of indexes: 1 (in 1 ranges), indexes: (1)]";
    kind = 3;
    old =     (
        1
    );
}

從上面的代碼可以看出晰甚,當(dāng)只是普通地調(diào)用[_arr addObject:@"1"]時压汪,Observer并不會回調(diào)古瓤,只有[[self mutableArrayValueForKey:@"arr"] addObject:@"1"];這樣寫時才能正確地觸發(fā)KVO落君。打印出來的數(shù)據(jù)中绎速,可以看出這次操作的詳情纹冤,kind可能是指操作方法(我還不是很確認(rèn)),oldnew并不是成對出現(xiàn)的雁歌,當(dāng)加添新數(shù)據(jù)時是new靠瞎,刪除數(shù)據(jù)時是old

而對于無序的容器乏盐,可以用下面的方法:

- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;

該方法返回一個可變的無序數(shù)組如果調(diào)用該方法父能,KVC的搜索順序如下

  • 搜索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)用。還有兩個可選實現(xiàn)的接口:intersect<Key> , set<Key>: 絮爷。

  • 如果receiverManagedObject坑夯,那么就不會繼續(xù)搜索抡四。

  • 如果上一步的方法沒有找到指巡,則搜索set<Key>: 格式的方法藻雪,如果找到勉耀,那么發(fā)送給代理集合的NSMutableSet最終都會調(diào)用set<Key>:方法。 也就是說至壤,mutableSetValueForKey取出的代理集合修改后崇渗,用set<Key>: 重新賦值回去去宅广。這樣做效率會低很多跟狱。所以推薦實現(xiàn)上面的方法户魏。

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

  • 如果還是找不到胧沫,調(diào)用valueForUndefinedKey:

    可見绒怨,除了檢查receiverManagedObject以外窖逗,其搜索順序和mutableArrayValueForKey基本一至碎紊,

同樣仗考,它們也有對應(yīng)的keyPath版本

- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;

iOS5和OSX10.7以后還有個mutableOrdered版本

- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key

這兩種KVC的用法我還不是清楚秃嗜,目前只能找到用于KVO的例子。如果有讀者能在項目中用到叽赊,希望可以告訴我必指。

KVC和字典

當(dāng)對NSDictionary對象使用KVC時塔橡,valueForKey:的表現(xiàn)行為和objectForKey:一樣葛家。所以使用valueForKeyPath:用來訪問多層嵌套的字典是比較方便的癞谒。

KVC里面還有兩個關(guān)于NSDictionary的方法

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

dictionaryWithValuesForKeys:是指輸入一組key扯俱,返回這組key對應(yīng)的屬性迅栅,再組成一個字典读存。

setValuesForKeysWithDictionary是用來修改Model中對應(yīng)key的屬性让簿。下面直接用代碼會更直觀一點

Address* add = [Address new];
add.country = @"China";
add.province = @"Guang Dong";
add.city = @"Shen Zhen";
add.district = @"Nan Shan";
NSArray* arr = @[@"country",@"province",@"city",@"district"];
NSDictionary* dict = [add dictionaryWithValuesForKeys:arr]; //把對應(yīng)key所有的屬性全部取出來
NSLog(@"%@",dict);

NSDictionary* modifyDict = @{@"country":@"USA",@"province":@"california",@"city":@"Los angle"};
[add setValuesForKeysWithDictionary:modifyDict];            //用key Value來修改Model的屬性
NSLog(@"country:%@  province:%@ city:%@",add.country,add.province,add.city);

//打印結(jié)果
2016-04-19 11:54:30.846 KVCDemo[6607:198900] {
    city = "Shen Zhen";
    country = China;
    district = "Nan Shan";
    province = "Guang Dong";
}
2016-04-19 11:54:30.847 KVCDemo[6607:198900] country:USA  province:california city:Los angle

打印出來的結(jié)果完全符合預(yù)期尔当。

KVC的內(nèi)部實現(xiàn)機制

前面我們對析了KVC是怎么搜索key的椭迎。所以如果明白了key的搜索順序畜号,是可以自己寫代碼實現(xiàn)KVC的简软。在考慮到集合和keyPath的情況下述暂,KVC的實現(xiàn)會比較復(fù)雜畦韭,我們只寫代碼實現(xiàn)最普通的取值和設(shè)值即可廊驼。

@interface NSObject(MYKVC)
-(void)setMyValue:(id)value forKey:(NSString*)key;
-(id)myValueforKey:(NSString*)key;

@end
@implementation NSObject(MYKVC)
-(void)setMyValue:(id)value forKey:(NSString *)key{
    if (key == nil || key.length == 0) {  //key名要合法
        return;
    }
    if ([value isKindOfClass:[NSNull class]]) {
        [self setNilValueForKey:key]; //如果需要完全自定義,那么這里需要寫一個setMyNilValueForKey西饵,但是必要性不是很大眷柔,就省略了
        return;
    }
    if (![value isKindOfClass:[NSObject class]]) {
        @throw @"must be s NSObject type";
        return;
    }

    NSString* funcName = [NSString stringWithFormat:@"set%@:",key.capitalizedString];
    if ([self respondsToSelector:NSSelectorFromString(funcName)]) {  //默認(rèn)優(yōu)先調(diào)用set方法
        [self performSelector:NSSelectorFromString(funcName) withObject:value];
        return;
    }
    unsigned int count;
    BOOL flag = false;
    Ivar* vars = class_copyIvarList([self class], &count);
    for (NSInteger i = 0; i<count; i++) {
        Ivar var = vars[i];
        NSString* keyName = [[NSString stringWithCString:ivar_getName(var) encoding:NSUTF8StringEncoding] substringFromIndex:1];

        if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) {
            flag = true;
            object_setIvar(self, var, value);
            break;
        }

        if ([keyName isEqualToString:key]) {
            flag = true;
            object_setIvar(self, var, value);
            break;
        }
    }
    if (!flag) {
        [self setValue:value forUndefinedKey:key];//如果需要完全自定義,那么這里需要寫一個self setMyValue:value forUndefinedKey:key鞠评,但是必要性不是很大剃幌,就省略了
    }
}

-(id)myValueforKey:(NSString *)key{
    if (key == nil || key.length == 0) {
        return [NSNull new]; //其實不能這么寫的
    }
    //這里為了更方便负乡,我就不做相關(guān)集合的方法查詢了
    NSString* funcName = [NSString stringWithFormat:@"gett%@:",key.capitalizedString];
    if ([self respondsToSelector:NSSelectorFromString(funcName)]) {
       return [self performSelector:NSSelectorFromString(funcName)];
    }

    unsigned int count;
    BOOL flag = false;
    Ivar* vars = class_copyIvarList([self class], &count);
    for (NSInteger i = 0; i<count; i++) {
        Ivar var = vars[i];
        NSString* keyName = [[NSString stringWithCString:ivar_getName(var) encoding:NSUTF8StringEncoding] substringFromIndex:1];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) {
            flag = true;
            return     object_getIvar(self, var);
            break;
        }
        if ([keyName isEqualToString:key]) {
            flag = true;
            return     object_getIvar(self, var);
            break;
        }
    }
    if (!flag) {
        [self valueForUndefinedKey:key];//如果需要完全自定義抖棘,那么這里需要寫一個self myValueForUndefinedKey切省,但是必要性不是很大数尿,就省略了
    }
   return [NSNull new]; //其實不能這么寫的
}
@end

Address* add = [Address new];
add.country = @"China";
add.province = @"Guang Dong";
add.city = @"Shen Zhen";
add.district = @"Nan Shan";

[add setMyValue:nil forKey:@"area"];            //測試設(shè)置 nil value
[add setMyValue:@"UK" forKey:@"country"];
[add setMyValue:@"South" forKey:@"area"];
[add setMyValue:@"300169" forKey:@"postCode"];
NSLog(@"country:%@  province:%@ city:%@ postCode:%@",add.country,add.province,add.city,add._postCode);
NSString* postCode = [add myValueforKey:@"postCode"];
NSString* country = [add myValueforKey:@"country"];
NSLog(@"country:%@ postCode: %@",country,postCode);

//打印結(jié)果:

2016-04-19 14:29:39.498 KVCDemo[7273:275129] country:UK  province:South city:Shen Zhen postCode:300169
2016-04-19 14:29:39.499 KVCDemo[7273:275129] country:UK postCode: 300169

上面就是自己寫代碼實現(xiàn)KVC的部分功能右蹦。其中我省略了自定義KVC錯誤方法何陆,省略了部分KVC搜索key的步驟贷盲,但是邏輯是很清晰明了的巩剖,后面的測試也符合預(yù)期佳魔。當(dāng)然這只是我自己實現(xiàn)KVC的思路鞠鲜,Apple也許并不是這么做的贤姆。

KVC的正確性驗證

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

- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;

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

@implementation Address
-(BOOL)validateCountry:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{  //在implementation里面加這個方法音婶,它會驗證是否設(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é)果 
2016-04-20 14:55:12.055 KVCDemo[867:58871] 鍵值不匹配
2016-04-20 14:55:12.056 KVCDemo[867:58871] country:China

如上面的代碼耳幢,當(dāng)開發(fā)者需要驗證能不能用KVC設(shè)定某個值時睛藻,可以調(diào)用validateValue: forKey:這個方法來驗證店印,如果這個類的開發(fā)者實現(xiàn)了-(BOOL)validate<Key>:error:這個方法吱窝,那么KVC就會直接調(diào)用這個方法來返回院峡,如果沒有,就直接返回YES发魄,注意俩垃,KVC在設(shè)值時不會主動去做驗證口柳,需要開發(fā)者手動去驗證跃闹。所以即使你在類里面寫了驗證方法望艺,但是KVC因為不會去主動驗證找默,所以還是能夠設(shè)值成功惩激。

KVC的使用

KVC在iOS開發(fā)中是絕不可少的利器咧欣,這種基于運行時的編程方式極大地提高了靈活性魄咕,簡化了代碼,甚至實現(xiàn)很多難以想像的功能苟弛,KVC也是許多iOS開發(fā)黑魔法的基礎(chǔ)膏秫。下面我來列舉iOS開發(fā)中KVC的使用場景

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

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

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

對于類里的私有屬性帅刀,Objective-C是無法直接訪問的扣溺,但是KVC是可以的锥余,請參考本文前面的Dog類的例子哈恰。

Model和字典轉(zhuǎn)換

這是KVC強大作用的又一次體現(xiàn),請參考iOS開發(fā)技巧系列---打造強大的BaseMod系列文章锌云,里面

充分地運用了KVC和Objc的runtime組合的技巧桑涎,只用了短短數(shù)行代碼就是完成了很多功能攻冷。

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

這也是iOS開發(fā)中必不可少的小技巧等曼。眾所周知很多UI控件都由很多內(nèi)部UI控件組合而成的禁谦,但是Apple度沒有提供這訪問這些控件的API丧蘸,這樣我們就無法正常地訪問和修改這些控件的樣式力喷。而KVC在大多數(shù)情況可下可以解決這個問題演训。最常用的就是個性化UITextField中的placeHolderText了仇祭。下面演示如果修改placeHolder的文字樣式没讲。這里的關(guān)鍵點是如果獲取你要修改的樣式的屬性名爬凑,也就是key或者keyPath名嘁信。

一般情況下可以運用runtime來獲取Apple不想開放的屬性名

let count:UnsafeMutablePointer<UInt32> =  UnsafeMutablePointer<UInt32>()
var properties = class_copyIvarList(UITextField.self, count)
while properties.memory.debugDescription !=  "0x0000000000000000"{
    let t = ivar_getName(properties.memory)
    let n = NSString(CString: t, encoding: NSUTF8StringEncoding)
    print(n)                                                         //打印出所有屬性,這里我用了Swift語言
    properties = properties.successor()
}

//上面省略了部分屬性
Optional(_disabledBackgroundView)
Optional(_systemBackgroundView)
Optional(_floatingContentView)
Optional(_contentBackdropView)
Optional(_fieldEditorBackgroundView)
Optional(_fieldEditorEffectView)
Optional(_displayLabel)
Optional(_placeholderLabel)                                         //這個正是我想要修改的屬性卦溢。
Optional(_dictationLabel)
Optional(_suffixLabel)
Optional(_prefixLabel)
Optional(_iconView)
//下面省略了部分屬性

可以從里面看到其他還有很多東西可以修改,運用KVC設(shè)值可以獲得自己想要的效果秀又。

操作集合

Apple對KVC的valueForKey:方法作了一些特殊的實現(xiàn)单寂,比如說NSArrayNSSet這樣的容器類就實現(xiàn)了這些方法。所以可以用KVC很方便地操作集合

用KVC實現(xiàn)高階消息傳遞

當(dāng)對容器類使用KVC時吐辙,valueForKey:將會被傳遞給容器中的每一個對象宣决,而不是容器本身進(jìn)行操作。結(jié)果會被添加進(jìn)返回的容器中昏苏,這樣威沫,開發(fā)者可以很方便的操作集合來返回另一個集合。

NSArray* arrStr = @[@"english",@"franch",@"chinese"];
NSArray* arrCapStr = [arrStr valueForKey:@"capitalizedString"];
for (NSString* str  in arrCapStr) {
    NSLog(@"%@",str);
}
NSArray* arrCapStrLength = [arrStr valueForKeyPath:@"capitalizedString.length"];
for (NSNumber* length  in arrCapStrLength) {
    NSLog(@"%ld",(long)length.integerValue);
}
打印結(jié)果
2016-04-20 16:29:14.239 KVCDemo[1356:118667] English
2016-04-20 16:29:14.240 KVCDemo[1356:118667] Franch
2016-04-20 16:29:14.240 KVCDemo[1356:118667] Chinese
2016-04-20 16:29:14.240 KVCDemo[1356:118667] 7
2016-04-20 16:29:14.241 KVCDemo[1356:118667] 6
2016-04-20 16:29:14.241 KVCDemo[1356:118667] 7

方法capitalizedString被傳遞到NSArray中的每一項椒丧,這樣壹甥,NSArray的每一員都會執(zhí)行capitalizedString并返回一個包含結(jié)果的新的NSArray。從打印結(jié)果可以看出壶熏,所有String都成功以轉(zhuǎn)成了大寫句柠。

同樣如果要執(zhí)行多個方法也可以用valueForKeyPath:方法。它先會對每一個成員調(diào)用 capitalizedString方法棒假,然后再調(diào)用length溯职,因為lenth方法返回是一個數(shù)字,所以返回結(jié)果以NSNumber的形式保存在新數(shù)組里帽哑。

用KVC中的函數(shù)操作集合

KVC同時還提供了很復(fù)雜的函數(shù)谜酒,主要有下面這些

①簡單集合運算符

簡單集合運算符共有@avg, @count 妻枕, @max 僻族, @min ,@sum5種屡谐,都表示啥不用我說了吧述么, 目前還不支持自定義。

@interface Book : NSObject
@property (nonatomic,copy)  NSString* name;
@property (nonatomic,assign)  CGFloat price;
@end
@implementation Book
@end

Book *book1 = [Book new];
book1.name = @"The Great Gastby";
book1.price = 22;
Book *book2 = [Book new];
book2.name = @"Time History";
book2.price = 12;
Book *book3 = [Book new];
book3.name = @"Wrong Hole";
book3.price = 111;

Book *book4 = [Book new];
book4.name = @"Wrong Hole";
book4.price = 111;

NSArray* arrBooks = @[book1,book2,book3,book4];
NSNumber* sum = [arrBooks valueForKeyPath:@"@sum.price"];
NSLog(@"sum:%f",sum.floatValue);
NSNumber* avg = [arrBooks valueForKeyPath:@"@avg.price"];
NSLog(@"avg:%f",avg.floatValue);
NSNumber* count = [arrBooks valueForKeyPath:@"@count"];
NSLog(@"count:%f",count.floatValue);
NSNumber* min = [arrBooks valueForKeyPath:@"@min.price"];
NSLog(@"min:%f",min.floatValue);
NSNumber* max = [arrBooks valueForKeyPath:@"@max.price"];
NSLog(@"max:%f",max.floatValue);

打印結(jié)果
2016-04-20 16:45:54.696 KVCDemo[1484:127089] sum:256.000000
2016-04-20 16:45:54.697 KVCDemo[1484:127089] avg:64.000000
2016-04-20 16:45:54.697 KVCDemo[1484:127089] count:4.000000
2016-04-20 16:45:54.697 KVCDemo[1484:127089] min:12.000000
2016-04-20 16:45:54.697 KVCDemo[1484:127089] max:111.000000

②對象運算符

比集合運算符稍微復(fù)雜愕掏,能以數(shù)組的方式返回指定的內(nèi)容度秘,一共有兩種:

@distinctUnionOfObjects

@unionOfObjects

它們的返回值都是NSArray,區(qū)別是前者返回的元素都是唯一的饵撑,是去重以后的結(jié)果氮惯;后者返回的元素是全集伪朽。

用法如下:

NSLog(@"distinctUnionOfObjects");
NSArray* arrDistinct = [arrBooks valueForKeyPath:@"@distinctUnionOfObjects.price"];
for (NSNumber *price in arrDistinct) {
    NSLog(@"%f",price.floatValue);
}
NSLog(@"unionOfObjects");
NSArray* arrUnion = [arrBooks valueForKeyPath:@"@unionOfObjects.price"];
for (NSNumber *price in arrUnion) {
    NSLog(@"%f",price.floatValue);
}

2016-04-20 16:47:34.490 KVCDemo[1522:128840] distinctUnionOfObjects
2016-04-20 16:47:34.490 KVCDemo[1522:128840] 111.000000
2016-04-20 16:47:34.490 KVCDemo[1522:128840] 12.000000
2016-04-20 16:47:34.490 KVCDemo[1522:128840] 22.000000
2016-04-20 16:47:34.490 KVCDemo[1522:128840] unionOfObjects
2016-04-20 16:47:34.490 KVCDemo[1522:128840] 22.000000
2016-04-20 16:47:34.490 KVCDemo[1522:128840] 12.000000
2016-04-20 16:47:34.490 KVCDemo[1522:128840] 111.000000
2016-04-20 16:47:34.490 KVCDemo[1522:128840] 111.000000

前者會將重復(fù)的價格去除后返回所有價格,后者直接返回所有的圖書價格心铃。(因為只返回價格捣郊,沒有返回圖書阳掐,感覺用處不大趣竣。)

③Array和Set操作符

這種情況更復(fù)雜了嗦嗡,說的是集合中包含集合的情況,我們執(zhí)行了如下的一段代碼:

@distinctUnionOfArrays

@unionOfArrays

@distinctUnionOfSets

@distinctUnionOfArrays:該操作會返回一個數(shù)組粱侣,這個數(shù)組包含不同的對象,不同的對象是在從關(guān)鍵路徑到操作器右邊的被指定的屬性里

@unionOfArrays 該操作會返回一個數(shù)組蓖宦,這個數(shù)組包含的對象是在從關(guān)鍵路徑到操作器右邊的被指定的屬性里和@distinctUnionOfArrays不一樣齐婴,重復(fù)的對象不會被移除

@distinctUnionOfSets@distinctUnionOfArrays類似。因為Set本身就不支持重復(fù)稠茂。

KVO

你沒看錯柠偶,KVO是基于KVC實現(xiàn)的情妖。那么是怎么用KVC實現(xiàn)KVO的呢,請期待下章诱担。

總結(jié)

本文全方位介紹了KVC的原理和各種用法毡证。相信讀者看完后對會KVC會有更完全的理解,也會在項目里更好的運用KVC蔫仙。其實這里面所有的東西在官方文檔里都有詳細(xì)的講解說明料睛。只不過全是英文的,我也看過幾遍摇邦,但是英語不好會看得很吃力恤煞,比如官方在介紹@distinctUnionOfArrays時的那句話我想了很好久也不是很明白,而且官方的示例代碼也做得不夠好施籍,所以很難找出某些功能的適用場景居扒。但我還是推薦各位開發(fā)者能夠?qū)W好英語去看官方文檔。再結(jié)合StackOverFlow和Google丑慎。真的可以解決絕大多數(shù)開發(fā)中碰到的難題了喜喂。這篇文章就到這里,下篇我向大家介紹KVO竿裂。 現(xiàn)在網(wǎng)上KVO的文章非常多玉吁,質(zhì)量也不錯,讀者可以搜索閱讀铛绰。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诈茧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子捂掰,更是在濱河造成了極大的恐慌敢会,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件这嚣,死亡現(xiàn)場離奇詭異鸥昏,居然都是意外死亡,警方通過查閱死者的電腦和手機姐帚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門吏垮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人罐旗,你說我怎么就攤上這事膳汪。” “怎么了九秀?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵遗嗽,是天一觀的道長。 經(jīng)常有香客問我鼓蜒,道長痹换,這世上最難降的妖魔是什么征字? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮娇豫,結(jié)果婚禮上匙姜,老公的妹妹穿的比我還像新娘。我一直安慰自己冯痢,他們只是感情好氮昧,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著系羞,像睡著了一般郭计。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上椒振,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天昭伸,我揣著相機與錄音,去河邊找鬼澎迎。 笑死庐杨,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的夹供。 我是一名探鬼主播灵份,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼哮洽!你這毒婦竟也來了填渠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤鸟辅,失蹤者是張志新(化名)和其女友劉穎氛什,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匪凉,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡枪眉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了再层。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贸铜。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖聂受,靈堂內(nèi)的尸體忽然破棺而出蒿秦,到底是詐尸還是另有隱情,我是刑警寧澤蛋济,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布渤早,位于F島的核電站,受9級特大地震影響瘫俊,放射性物質(zhì)發(fā)生泄漏鹊杖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一扛芽、第九天 我趴在偏房一處隱蔽的房頂上張望骂蓖。 院中可真熱鬧,春花似錦川尖、人聲如沸登下。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽被芳。三九已至,卻和暖如春馍悟,著一層夾襖步出監(jiān)牢的瞬間畔濒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工锣咒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留侵状,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓毅整,卻偏偏與公主長得像趣兄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子悼嫉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容

  • KVC(Key-valuecoding)鍵值編碼艇潭,單看這個名字可能不太好理解。其實翻譯一下就很簡單了戏蔑,就是指iOS...
    榕樹頭閱讀 710評論 0 2
  • KVC簡單介紹 KVC(Key-value coding)鍵值編碼蹋凝,就是指iOS的開發(fā)中,可以允許開發(fā)者通過Key...
    公子無禮閱讀 1,389評論 0 6
  • KVC(Key-value coding)鍵值編碼辛臊,單看這個名字可能不太好理解仙粱。其實翻譯一下就很簡單了,就是指iO...
    黑暗中的孤影閱讀 49,721評論 74 441
  • KVC KVC定義 KVC(Key-value coding)鍵值編碼彻舰,就是指iOS的開發(fā)中伐割,可以允許開發(fā)者通過K...
    暮年古稀ZC閱讀 2,141評論 2 9
  • 實干后明白了很多 首先是站著說話不腰疼'!刃唤! 很多事情隔心,我們看上去簡單,別人做的時候我們要不覺得理所當(dāng)然尚胞,更多的時...
    道法自然007閱讀 265評論 1 0