iOS全解4:KVC、KVO、通知/推送/信号量、Delegate/Protocol、Singleton

面试系列:

一、KVC

全称是Key-value coding,键值编码。使用字符串来标识属性,是间接访问对象的属性, 而不是直接通过调用存取方法(Setter、Getter方法)访问。可以在运行时动态访问和修改对象的属性。
KVC的方法定义在: Foundation/NSKeyValueCoding中。

特点: 可以简化程序代码。
KVC关系图.png


KVC详解

  • 1、KVC原理

    1. KVC 访问私有变量
    2. setter 原理分析
    3. getter 原理分析
    4. forKeyPath、 valueForKeyPath
    5. 异常处理: 赋值/取值、正确性验证
  • 2、KVC与字典

  • 3、KVC的消息传递

  • 4、KVC容器操作

  • 5、KVC集合代理对象

  • 6、KVC的应用


一、KVC原理

1、 KVC 访问私有变量

1.1 能够访问私有成员变量(给对象的私有成员进行:取值/赋值 )
1.2 对数值和结构体型的属性进行的 打包/解包 处理

- valueForUndefinedKey
- setValue:forUndefinedKey:
 myName 、_myName
 myAge、_myAge  
KVC常用的方法

为对象的属性:取值、赋值

# 赋值方法
-(void)setValue:(id)value forKeyPath:(NSString *)keyPath;
-(void)setValue:(id)value forKey:(NSString *)key;
# 取值方法
-(id)valueForKeyPath:(NSString *)keyPath;
-(id)valueForKey:(NSString *)key;

# 案例:
[person1 setValue:@"jack" forKey:@"name"];      # 赋值
NSString *name = [person1 valueForKey:@"name"]; # 取值

# forKeyPath 是对更“深层”的对象进行访问。如数组的某个元素,对象的某个属性。如:
[myModel setValue:@"beijing" forKeyPath:@"address.city"];
# 返回所有对象的name属性值
NSArray *names = [array valueForKeyPath:@"name"];

// 通过keyPath取值
• key:单层访问
• keyPath:可以多层访问

⁃ key:@"age"
⁃ keyPath:@"age"
⁃ keyPath:@"student.age"
⁃ keyPath:@"person.student.age"

注意:setter、getter:会按照 _key,_iskey,key,iskey 的顺序搜索成员


2、setter 原理分析:(赋值过程顺序如下)
  1. 先找相关方法
    //1、-(void) setName
    //2、-(void) _setName
    //3、-(void) setIsName
    //与 -(void) _setIsName 无关

  2. 若没有相关方法,判断是否可以直接找方法成员变量
    + (BOOL)accessInstanceVariablesDirectly
    2.1 NO:系统抛出一个异常,未定义key
    2.2 YES:继续找相关变量
    //1、_name
    //2、_isName
    //3、name
    //4、isName

  3. 方法或成员变量都不存在:
    使用setValue:forUndefinedKey:方法,抛出异常。


3、getter 原理分析(取值过程顺序如下)
  1. 先找相关方法
    //1、- getName
    //2、- name

  2. 若没有相关方法,判断是否可以直接找方法成员变量
    + (BOOL)accessInstanceVariablesDirectly
    2.1 NO:系统抛出一个异常,未定义key
    2.2 YES:继续找相关变量
    //1、_name
    //2、_isName
    //3、name
    //4、isName

  3. 方法或成员都不存在,使用 valueForUndefinedKey:方法,抛出异常

4、setValue:forKeyPath、 valueForKeyPath

寻找多级属性(KeyPath)
赋值:- setValue:forKeyPath:
取值:- valueForKeyPath: :
dog.name
_placeholderLabel.textColor


5、异常处理: 赋值、取值;正确性验证

异常处理 总结:
- 解决方案:重写以下相关方法
赋值:value为空 setNilValueForKey:
赋值:Key值不存在 setValue: forUndefinedKey:
取值:Key值不存在 valueForUndefinedKey:

正确性验证: validateValue
该方法的工作原理:
1. 先找一下你的类中是否实现了方法.
-(BOOL)validate<Key>:error:
2. 如果实现了就会根据实现方法里面的自定义逻辑返回NO或者YES,如果没有实现这个方法,则系统默认返回就是YES

//正确性验证: validateValue(内部验证)

- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue 
               forKey:(NSString *)inKey 
                error:(out NSError **)outError {
    //...
    return YES;
}


二、KVC与字典

<Foundation/NSKeyValueCoding.h>

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


三、KVC的消息传递

NSArray* lengthArr = [arr valueForKey:@"length"];
NSArray* lowercaseArr = [arr valueForKey:@"lowercaseString"];
# 给成员 length          发送消息(遍历所有成员 长度)
# 给成员 lowercaseString 发送消息(字符串全部转成小写)

#pragma mark - KVC消息传递
- (void)arrayMessagePass{
    NSArray *array = @[@"Alan",@"Xing",@"XZ",@"ZhaiAlan"];
    NSArray *lenStr= [array valueForKeyPath:@"length"];
    NSArray *lowStr= [array valueForKeyPath:@"lowercaseString"];
    NSLog(@"---%@",lenStr);// 消息从array传递给了string
    NSLog(@"---%@",lowStr);
}
输出结果:
 ---( 4, 4, 2, 8 )
 ---( alan, xing, xz, zhaialan )


四、KVC容器操作

1. 聚合操作符 @avg、@count、@max、@min、@sum

平均数、数量、最大值、最小值、合值

    float avg = [[students  valueForKeyPath:@"@avg.height"] floatValue];
    float max = [[students valueForKeyPath:@"@max.height"] floatValue];
    float min = [[students valueForKeyPath:@"@min.height"] floatValue];
    float sum = [[students valueForKeyPath:@"@sum.height"] floatValue];
    int count = [[students valueForKeyPath:@"@count.height"] intValue];
2. 数组操作符

去重:@distinctUnionOfObjects
不去重:@unionOfObjects

    NSArray* arr1 = [students valueForKeyPath:@"@distinctUnionOfObjects.height"];
    NSArray* arr2 = [students valueForKeyPath:@"@unionOfObjects.height"];
3. 嵌套集合(array&set NSMutableArray )操作

@distinctUnionOfArrays@distinctUnionOfSets@unionOfArrays
读取集合中每个元素的键路径指定的属性,放在一个NSArray实例中,将数组进行去重后返回

    NSMutableArray* students1 = [NSMutableArray array];
    NSMutableArray* students2 = [NSMutableArray array];
    //嵌套数组
    NSArray* nestArr = @[students1, students2];
    NSArray* arr1 = [nestArr valueForKeyPath:@"@distinctUnionOfArrays.height"];
    NSArray* arr2 = [nestArr valueForKeyPath:@"@unionOfArrays.height"];
4. 嵌套集合(array&set NSMutableSet)操作 @distinctUnionOfArrays@distinctUnionOfSets@unionOfArrays
    NSMutableSet* students1 = [NSMutableSet set];
    NSMutableSet* students2 = [NSMutableSet set];
    NSSet* nestSet = [NSSet setWithObjects:students1, students2, nil];
    NSArray* arr1 = [nestSet valueForKeyPath:@"@distinctUnionOfSets.height"];
    //不去重-没有此方法(异常崩溃:this class does not implement the unionOfArrays operation.)
    // NSArray* arr2 = [nestSet valueForKeyPath:@"@unionOfArrays.height"];
五、KVC集合代理对象
  • 个人理解(一对多的关系:通过一个key,对应多个方法)

    • 对象的属性可以是一对一的,也可以是一对多的。一对多的属性要么是有序的(数组),要么是无序的(集合)。
    • 属性的一对多关系其实就是一种对容器类的映射。
    • 不可变的有序容器属性(NSArray)和无序容器属性(NSSet)一般可以使用valueForKey:来获取。
    • 如果有一个名为numbers的数组属性,我们可以使用valueForKey:@"numbers"来获取,这个是没问题的,但KVC还能使用更灵活的方式管理集合。——那就是:集合代理对象

    (变量 variable)

     -(void)valueForKey 有如下的搜索规则:
     1、按顺序搜索 getKey、key、isKey,第一个被找到的会用作返回。
     2、countOf<Key>、objectIn<Key>AtIndex 与<key>AtIndexes其中之一,这个组合会使KVC返回一个代理数组。
     3、countOf<Key>、enumeratorOf<Key>、memberOfVar。这个组合会使KVC返回一个代理集合。
     4、名为_val、_isVar、var、isVar的实例变量。到这一步时,KVC会直接访问实例变量,而这种访问操作破坏了封装性,我们应该尽量避免,这可以通过重写+(Bool)accessInstanceVariablesDirectly返回NO来避免这种行为。


6、KVC的应用

6.1、动态地取值和赋值
6.2、用KVC来访问和修改私有变量
对于类里的私有属性,OC是无法直接访问的,但是KVC是可以的。
6.3、Model和字典转换
6.4、修改一些控件的内部属性:最常用的就是UITextField中的placeHolderText了。
6.5、操作集合
6.6、用KVC实现高阶消息传递

[self.textField setValue:[UIColor blueColor] forKeyPath:@"_placeholderLabel.textColor"];
[self.textField setValue:[UIFont systemFontOfSize:14] forKeyPath:@"_placeholderLabel.font"];
[self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.backgroundColor"];



二、KVO

1、KVO原理分析(概念)
2、KVO基本使用
3、KVO自定义
4、KVO延伸(自动销毁机制、Blocks、YY封装KVO)


1、概念

全称(Key-Value Observing)键值观察
允许对象通知其他对象属性的更改。它对应用程序中模型 Model控制器层 VC之间的通信特别有用。 (在OS X中,控制器层绑定技术严重依赖于键值观察。)控制器对象通常观察模型对象的属性,视图对象通过控制器观察模型对象的属性。然而,另外,模型对象可以观察其他模型对象(通常用于确定从属值何时改变)或甚至自身(再次确定从属值何时改变)。

原理分析:
1. 动态生成一个继承自原类的新类
2. 使用Person的分类,重写新类的set方法
3. 修改isa指针的指向。将self的isa指针指向新类,也就是为了在调用setter方法的时候,会去新类中查找。
4. 将self的isa指针指向原类,为了获得旧值,然后赋值新值。最后去通知观察者值的改变,isa指针再指向新类(为了继续观察属性,下次进行通知)。

引用:
KVO原理以及自定义KVO
iOS探索KVO实现原理,重写KVO

//设置旧值
change[NSKeyValueChangeOldKey] = oldValue;
//设置新值
change[NSKeyValueChangeNewKey] = newValue;
//调用observer里面的回调方法
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),keyPath,observer,change,context);
//self的isa指针指向新类
object_setClass(self, newClass);   

——————————————————————————



特点:观察属性,一对一关系和多对多关系。
一个简单的例子:假设Person对象与Account账户对象交互。 Person的实例要知道Account实例的某些方面何时发生变化,例如余额或利率。

  • 一个对象的一个属性、多个属性;
  • 多个对象的一个属性、多个属性;

ViewController VC --> Observing Model Property
View --> VC --> Observing Model Property


1.1、Runtime 实现KVO

KVO的实现依赖于 Objective-C 强大的 Runtime,当观察某对象 People 时,KVO 机制动态创建一个对象people当前类的子类,并为这个新的子类重写了被观察属性 keyPathsetter方法。setter 方法随后负责通知观察对象属性的改变状况。

Apple 使用了isa-swizzling 来实现 KVO 。当观察对象people时,KVO机制动态创建一个新的名为:NSKVONotifying_People的新类,该类继承自对象People 的本类,且 KVO 为 NSKVONotifying_People 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。

1.2、NSKVONotifying_People 类剖析
NSLog(@"self->isa: %@",self->isa);  
NSLog(@"self class: %@",[self class]);  

# 在建立KVO监听前,打印结果为:
self->isa: People
self class: People

# 在建立KVO监听之后,打印结果为:
self->isa: NSKVONotifying_People
self class: People


1.3、子类setter方法剖析

KVO 的键值观察通知依赖于 NSObject 的两个方法:

  • willChangeValueForKey: 被观察属性发生改变之前被调用,通知即将改变、
  • didChangeValueForKey: 被观察属性发生改变之后被调用,通知已经变更。

在存取数值的前后分别调用 这2 个方法,且重写观察属性的setter 方法这种继承方式的注入是在运行时而不是编译时实现的。

手动调用KVO

KVO 为子类的观察者属性重写调用存取方法的工作原理在代码中相当于:

- (void)setName:(NSString *)newName { 
      [self willChangeValueForKey:@"name"];    # KVO 在调用存取方法之前总调用 
      [super setValue:newName forKey:@"name"]; # 调用父类的存取方法 
      [self didChangeValueForKey:@"name"];     # KVO 在调用存取方法之后总调用
}

如果想控制当前对象的自动调用过程,也就是由上面两个方法发起的KVO调用,则可以重写下面方法。方法返回YES则表示可以调用,如果返回NO则表示不可以调用。

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
    BOOL automatic = NO;
    if ([theKey isEqualToString:@"balance"]) {
        automatic = NO;
    }
    else {
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}
分析观察者的整个过程:

1、未被观察的属性,直接找到属性的 setter/getter方法 赋值/取值
2、被观察的属性,会动态创建一个子类 NSKVONotifying_object,然后添加一些方法,重写对应属性的 setter/getter 方法,观察子类属性的变化,并通知给子类

*isa --> subClass --> superClass --> mateClass ... --> rootClass
(先找子类方法,再找父类方法,再找元类方法,根类方法 根类的元类就是它自己,元类最后指向自己)

  • TZPerson: isa --> NSKVONotifiying_TZPerson--> supclass TZPerson
  • 添加观察后:动态去创建一个新类,并添加一些方法/属性,继承了原类(移除观察者后,isa指针重新指向 TZPerson 原类)
重写了子类:指针指向原类
 - (Class) class {
    return object_superClass(object_getClass(self));
 }
方法调用过程:
 - 即将改变:NSKeyValueWillChange
 -[TZPerson setSteps:]
 - 已经改变:NSKeyValueDidChange
 - 通知观察者:NSKeyValueNotifyObserver
 - (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context 


2、基本使用

使用三步骤:

  1. 添加观察者
  2. 实现监听方法
  3. 移除观察者
KVO使用三步骤.png
3、KVO自定义
  • 动态创建一个类
    // 1. 拼接子类名 NSKVONotifying_object
    // 2. 创建并注册类
    • class 创建并注册类
    • class 添加一些方法
    • class (重写属性的 setter 方法)
    • 方法反射,添加一个方法
    • zm_setter
    • 添加析构方法 - dealloc
    • return newClass;
  • 修改isa的指向
  • 关联方法


4、KVO延伸(自动销毁机制、Blocks、YY封装KVO)

在YY_KVO 中使用到
---------------------------- "NSObject+YYAddForKVO.h" ----------------------------

   # //实现监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (!self.block) return;
    
    BOOL isPrior = [[change objectForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue];
    if (isPrior) return;
    # //使用中间类 
    NSKeyValueChange changeKind = [[change objectForKey:NSKeyValueChangeKindKey] integerValue];
    if (changeKind != NSKeyValueChangeSetting) return;
    
    id oldVal = [change objectForKey:NSKeyValueChangeOldKey];
    if (oldVal == [NSNull null]) oldVal = nil;
    
    id newVal = [change objectForKey:NSKeyValueChangeNewKey];
    if (newVal == [NSNull null]) newVal = nil;
    # //Block 回调监听结果
    self.block(object, oldVal, newVal);

问题:

1、KVO是否会对一个 变量 进行通知?
答:不会,只观察对象的属性。


三、通知/信号量

1、通知:NSNotification

目前分为四个推送:

1.用户推送:NSNotificationCenter
2.本地推送:UILocalNotification(状态栏提示:例如闹钟、挂号排队)
3.远程推送:RemoteNotifications
4.地理位置推送:必须配合CLLocation使用

过程:注册、接收、处理

有以下特点:

  • 一对一 或 一对多
  • 消息的发送者 告知 接收者事件已经发生或者将要发送,仅此而已,接收者不能影响 发送者的行为。
#//1.1、添加:通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"通知变化" object:nil];
#//1.2、接收:通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ChangNotificat:) name:@"通知变化" object:nil];
#//1.3、移除:通知
[[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:self];
    
# // 2、监听通知:键盘
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(NotificatClick:) 
name:UIKeyboardWillHideNotification object:nil];

# /* 3、监听通知:输入框
    * UITextFieldTextDidBeginEditingNotification;
    * UITextFieldTextDidEndEditingNotification;
    * UITextFieldTextDidChangeNotification;
    */
    [[NSNotificationCenter defaultCenter] addObserverForName:UITextFieldTextDidEndEditingNotification 
 object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notificate){
        NSLog(@" textField ");
        if ([notificate.object isEqual:self]){
        }
    }];

2、信号量:dispatch_semaphore

dispatch_semaphore 是信号量,但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时,它的性能比 pthread_mutex 还要高,但一旦有等待情况出现时,性能就会下降许多。相对于 OSSpinLock 来说,它的优势在于等待时不会消耗 CPU 资源。对磁盘缓存来说,它比较合适。

一般在GCD职工使用,我理解的dispatch_semaphore有两个主要应用 :

  1. 保持线程同步
  2. 为线程加锁,等待执行任务完成后,通知后面的任务执行


四、代理协议:Delegate/Protocol

sender: 消息的发送者
receiver:消息的接收者
delegate:代理者

Delegate 代理

代理的目的是改变或传递控制链。可以减少框架复杂度。
允许一个类在某些特定时刻通知到其他类,而不需要获取到那些类的指针。
可以减少框架复杂度。

消息的发送者(sender)告知接收者(receiver)某个事件将要发生,delegate同意,然后发送者响应事件:发送者 委托 接收者 去做某件事。
delegate机制使得接收者可以改变发送者的行为。
通常发送者和接收者的关系是直接的 一对一的关系。

IBOutlet可以为weak,NSString为copy,Delegate一般为weak。

Protocol 小结:

类对象遵守了Protocol,那么该类就有了Protocol里面声明的方法。类必须实现@required方法、可选实现@optional方法,具体怎么实现由该类自行决定,Protocol不过问。


五、单例:Singleton

1、单例模式的三个要点:
1) 某个类 只能有一个实例(另外创建必须crash抛出异常提示);
2) 它必须 自行创建这个实例;
3) 它必须自行 向整个系统提供这个实例。

2、单例模式的优点:
  1.实例控制:Singleton 会阻止其他对象实例化其自己的 Singleton 对象的副本,从而 确保所有对象都访问唯一实例。
  2.灵活性:因为类控制了实例化过程,所以类可以更加灵活修改实例化过程

一个共享的实例,该实例只会被创建一次。

**该方法有很多优势: **
1、线程安全
2、很好满足静态分析器要求
3、和自动引用计数(ARC)兼容
4、仅需要少量代码

使用继承单例 源类,便于管理,代码如下:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

//Impl:初始微程序装载(Initial Microprogram Loading)
//#if DEBUG
//    #define SINGLETON_INSTANCE_LOG(x) NSLog(@"Singleton object <%s(%@)> has been created.",#x, [x class])
//#else
//    #define SINGLETON_INSTANCE_LOG(x)
//#endif
// DEBUG 宏定义模式不用
#define SINGLETON_INSTANCE_LOG(x)


//1、继承于 Singleton 的初始微程序装载(实例化)
#define Singleton_Instance_method_Interface(ClassName) \
        + (ClassName *)instance;
#define Singleton_Instance_method_Impl(ClassName) \
        + (ClassName *)instance \
        {   \
            static ClassName *_g_##ClassName##_obj = nil;  \
            static dispatch_once_t onceToken;   \
            dispatch_once(&onceToken, ^{    \
            _g_##ClassName##_obj = [[self singletonAlloc] init];    \
            SINGLETON_INSTANCE_LOG(_g_##ClassName##_obj);   \
            }); \
        return _g_##ClassName##_obj;    \
        }


//2、不继承于 Singleton 的初始微程序装载(实例化)
#define Singleton_Instance_method_ImplAlloc(ClassName) \
        + (ClassName *)instance \
        {   \
            static ClassName *_g_##ClassName##_obj = nil;  \
            static dispatch_once_t onceToken;   \
            dispatch_once(&onceToken, ^{    \
            _g_##ClassName##_obj = [[self alloc] init];    \
            SINGLETON_INSTANCE_LOG(_g_##ClassName##_obj);   \
            }); \
        return _g_##ClassName##_obj;    \
        }


/**
 *  我建议:
   单例类都应该继承JKSingletonObject这个项目。
   强迫每个人都使用+ singletonAlloc对团队有好处。
 */
@interface Singleton : NSObject
+ (id)singletonAlloc;
@end


#import "Singleton.h"
@implementation Singleton
/**
 *  覆盖这个方法是阻止其他程序员编码“[[Singleton alloc]init]”。
   你永远不应该使用alloc自己。请写+实例在自己的子类。
   请参阅类方法+ Singleton.h实例。
 */
+ (id)alloc {
    @throw [NSException exceptionWithName:@"单例模式的规则"
                                   reason:@"禁止使用alloc自己创建这个对象,请使用+ singletonAlloc代替。"
                                 userInfo:@{}];
    return nil;
}
+ (id)singletonAlloc{
    return [super alloc];
}


/**
 *  原始方式:没有使用,看看就行(另外创建,没有抛出异常)
 *  @return 返回一个实例
 */
+ (Singleton *)singleton
{
    static Singleton *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}




最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 原创:知识点总结性文章创作不易,请珍惜,之后会持续更新,不断完善个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈...
    时光啊混蛋_97boy阅读 675评论 0 2
  • KVC定义 KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过Key名直...
    SheIsMySin_72e7阅读 376评论 0 0
  • KVC KVC定义 KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过K...
    jackyshan阅读 51,802评论 9 200
  • KVC KVC定义 KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过K...
    恋空K阅读 707评论 0 2
  • 1. KVC 1.0 KVC的使用 LGStudent.h LGPerson.h 我们在平时一般使用LGPerso...
    Jeffery_zc阅读 451评论 1 1