GNUstep KVC/KVO探索(二):KVO的內(nèi)部實(shí)現(xiàn)

GNUstep KVC/KVO探索(一):KVC的內(nèi)部實(shí)現(xiàn)
GNUstep KVC/KVO探索(二):KVO的內(nèi)部實(shí)現(xiàn)

概述

KVO全稱(chēng)KeyValueObserving鹊汛,是蘋(píng)果提供的一套事件通知機(jī)制纷责。允許對(duì)象監(jiān)聽(tīng)另一個(gè)對(duì)象特定屬性的改變枪汪,并在改變時(shí)接收到事件篇亭。由于KVO的實(shí)現(xiàn)機(jī)制敞咧,所以對(duì)屬性才會(huì)發(fā)生作用趾代,一般繼承自NSObject的對(duì)象都默認(rèn)支持KVO。

KVO和NSNotificationCenter都是iOS中觀察者模式的一種實(shí)現(xiàn)调鲸。區(qū)別在于,相對(duì)于被觀察者和觀察者之間的關(guān)系挽荡,KVO是一對(duì)一的藐石,而NSNotificationCenter是一對(duì)多的。KVO對(duì)被監(jiān)聽(tīng)對(duì)象無(wú)侵入性定拟,不需要修改其內(nèi)部代碼即可實(shí)現(xiàn)監(jiān)聽(tīng)于微。

KVO可以監(jiān)聽(tīng)單個(gè)屬性的變化,也可以監(jiān)聽(tīng)集合對(duì)象的變化青自。通過(guò)KVC的mutableArrayValueForKey:等方法獲得代理對(duì)象株依,當(dāng)代理對(duì)象的內(nèi)部對(duì)象發(fā)生改變時(shí),會(huì)回調(diào)KVO監(jiān)聽(tīng)的方法延窜。集合對(duì)象包含NSArray和NSSet恋腕。

基礎(chǔ)使用

使用KVO分為三個(gè)步驟:

通過(guò)addObserver:forKeyPath:options:context:方法注冊(cè)觀察者,觀察者可以接收keyPath屬性的變化事件逆瑞。

在觀察者中實(shí)現(xiàn)observeValueForKeyPath:ofObject:change:context:方法荠藤,當(dāng)keyPath屬性發(fā)生改變后,KVO會(huì)回調(diào)這個(gè)方法來(lái)通知觀察者获高。

當(dāng)觀察者不需要監(jiān)聽(tīng)時(shí)哈肖,可以調(diào)用removeObserver:forKeyPath:方法將KVO移除。需要注意的是念秧,調(diào)用removeObserver需要在觀察者消失之前淤井,否則會(huì)導(dǎo)致Crash。

KVO的實(shí)現(xiàn)

在分析KVO的內(nèi)部實(shí)現(xiàn)之前出爹,先來(lái)分析一下KVO的存儲(chǔ)結(jié)構(gòu)庄吼,主要用到了以下幾個(gè)類(lèi):
GSKVOInfo
GSKVOPathInfo
GSKVOObservation

GSKVOInfo

KVO是基于NSObject類(lèi)別實(shí)現(xiàn)的非正式協(xié)議,所以所有繼承于NSObject的類(lèi)都可以使用KVO严就,其中可以通過(guò)- (void*) observationInfo方法獲取對(duì)象相關(guān)聯(lián)的所有KVO信息总寻,而返回值就是GSKVOInfo,源碼如下

@interface  GSKVOInfo : NSObject
{
  NSObject          *instance;  // Not retained.
  GSLazyRecursiveLock           *iLock;
  NSMapTable            *paths;
}
  1. 它保存了一個(gè)對(duì)象的實(shí)例梢为,重點(diǎn) Not retained 由于沒(méi)有持有渐行,也不是weak柒室,所以當(dāng)釋放之后,在調(diào)用會(huì)崩潰危队,需要在對(duì)象銷(xiāo)毀前宙枷,移除所有觀察者
  2. paths 用于保存keyPathGSKVOPathInfo 的映射:
GSKVOPathInfo
@interface  GSKVOPathInfo : NSObject
{
@public
  unsigned              recursion;
  unsigned              allOptions;
  NSMutableArray        *observations;
  NSMutableDictionary   *change;
}

它保存了一個(gè)keypath對(duì)應(yīng)的所有觀察者
observations 保存了所有的觀察者(GSKVOObservation 類(lèi)型)
allOptions 保存了觀察者的options集合
change 保存了KVO觸發(fā)要傳遞的內(nèi)容

GSKVOObservation
@interface  GSKVOObservation : NSObject
{
@public
  NSObject      *observer;      // Not retained (zeroing weak pointer)
  void          *context;
  int           options;
}
@end

它保存了單個(gè)觀察的所有信息

  1. observer保存觀察者 注意這里也是 Not retained
  2. context options 都是添加觀察者時(shí)傳入的參數(shù)
    image.png

    上面幾個(gè)類(lèi)都是用來(lái)存儲(chǔ)KVO信息,而KVO是如何實(shí)現(xiàn)屬性變化通知的呢蕴忆?
    KVO是通過(guò)isa-swizzling技術(shù)實(shí)現(xiàn)的(這句話是整個(gè)KVO實(shí)現(xiàn)的重點(diǎn))颤芬。在運(yùn)行時(shí)根據(jù)原類(lèi)創(chuàng)建一個(gè)中間類(lèi),這個(gè)中間類(lèi)是原類(lèi)的子類(lèi)套鹅,并動(dòng)態(tài)修改當(dāng)前對(duì)象的isa指向中間類(lèi)站蝠。并且將class方法重寫(xiě),返回原類(lèi)的Class卓鹿。所以蘋(píng)果建議在開(kāi)發(fā)中不應(yīng)該依賴(lài)isa指針菱魔,而是通過(guò)class實(shí)例方法來(lái)獲取對(duì)象類(lèi)型。
    要實(shí)現(xiàn)isa-swizzling技術(shù)吟孙,主要通過(guò)以下幾個(gè)類(lèi)實(shí)現(xiàn):
    GSKVOReplacement
    GSKVOBase
    GSKVOSetter
GSKVOReplacement
@interface  GSKVOReplacement : NSObject
{
  Class         original;       /* The original class */
  Class         replacement;    /* The replacement class */
  NSMutableSet  *keys;          /* The observed setter keys */
}
- (id) initWithClass: (Class)aClass;
- (void) overrideSetterFor: (NSString*)aKey;
- (Class) replacement;
@end

// 創(chuàng)建
- (id) initWithClass: (Class)aClass
{
  NSValue       *template;
  NSString      *superName;
  NSString      *name;

  original = aClass;

  /*
   * Create subclass of the original, and override some methods
   * with implementations from our abstract base class.
   */
  superName = NSStringFromClass(original);      // original == Temp
  name = [@"GSKVO" stringByAppendingString: superName];    // name = GSKVOTemp
  template = GSObjCMakeClass(name, superName, nil);   // template = GSKVOTemp
  GSObjCAddClasses([NSArray arrayWithObject: template]);
  replacement = NSClassFromString(name);
  GSObjCAddClassBehavior(replacement, baseClass);

  /* Create the set of setter methods overridden.
   */
  keys = [NSMutableSet new];

  return self;
}

  1. 這個(gè)類(lèi)保存了被觀察對(duì)象原始類(lèi)信息 original
  2. 創(chuàng)建一個(gè)原始類(lèi)的子類(lèi) 命名為GSKVO<原類(lèi)名>澜倦,iOS系統(tǒng)使用命名規(guī)則為NSKVONotifying_原類(lèi)名
  3. 拷貝GSKVOBase類(lèi)中的方法到新類(lèi)中
  4. 后續(xù)會(huì)通過(guò)object_setClass, 將被觀察對(duì)象的isa指向這個(gè)新類(lèi),也就是isa-swizzling技術(shù)杰妓,而isa保存的是類(lèi)的信息藻治,也就是說(shuō)被觀察者對(duì)象就變成了新類(lèi)的實(shí)例,這個(gè)新類(lèi)用于實(shí)現(xiàn)KVO通知機(jī)制
GSKVOBase

這個(gè)類(lèi)默認(rèn)提供了幾個(gè)方法巷挥,都是對(duì)NSObject方法的重寫(xiě)栋艳,而從上面得知,這些方法都要拷貝到新創(chuàng)建的替換類(lèi)中句各。也就是被觀察者會(huì)擁有這幾個(gè)方法的實(shí)現(xiàn)

- (void) dealloc
{
  // Turn off KVO for self ... then call the real dealloc implementation.
  [self setObservationInfo: nil];
  object_setClass(self, [self class]);
  [self dealloc];
  GSNOSUPERDEALLOC;
}

對(duì)象釋放后吸占,移除KVO數(shù)據(jù),將對(duì)象重新指向原始類(lèi)

- (Class) class
{
  return class_getSuperclass(object_getClass(self));
}

此方法用來(lái)隱藏替換類(lèi)信息凿宾,應(yīng)用層獲取類(lèi)的信息矾屯,仍然是原始類(lèi)的信息. 所以蘋(píng)果建議在開(kāi)發(fā)中不應(yīng)該依賴(lài)isa指針,而是通過(guò)class實(shí)例方法來(lái)獲取對(duì)象類(lèi)型初厚。

- (Class) superclass
{
  return class_getSuperclass(class_getSuperclass(object_getClass(self)));
}

此方法和class方法原理相同

- (void) setValue: (id)anObject forKey: (NSString*)aKey
{
  Class     c = [self class];
  void      (*imp)(id,SEL,id,id);

  imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];

  if ([[self class] automaticallyNotifiesObserversForKey: aKey])
    {
      [self willChangeValueForKey: aKey];
      imp(self,_cmd,anObject,aKey);
      [self didChangeValueForKey: aKey];
    }
  else
    {
      imp(self,_cmd,anObject,aKey);
    }
}

這個(gè)方法是屬于KVC中的件蚕,重寫(xiě)這個(gè)方法,實(shí)現(xiàn)在原始類(lèi)KVC調(diào)用前后添加[self willChangeValueForKey: aKey][self didChangeValueForKey: aKey],而這兩個(gè)方法是觸發(fā)KVO通知的關(guān)鍵产禾。
所以說(shuō)KVO是基于KVC的排作,而KVC正是KVO觸發(fā)的入口。

GSKVOBase
@interface  GSKVOSetter : NSObject
- (void) setter: (void*)val;
- (void) setterChar: (unsigned char)val;
- (void) setterDouble: (double)val;
- (void) setterFloat: (float)val;
- (void) setterInt: (unsigned int)val;
- (void) setterLong: (unsigned long)val;
#ifdef  _C_LNG_LNG
- (void) setterLongLong: (unsigned long long)val;
#endif
- (void) setterShort: (unsigned short)val;
- (void) setterRange: (NSRange)val;
- (void) setterPoint: (NSPoint)val;
- (void) setterSize: (NSSize)val;
- (void) setterRect: (NSRect)rect;
@end

這個(gè)類(lèi)和上面重寫(xiě)KVC方法原理相同亚情,將來(lái)會(huì)替換被觀察者keypathsetter方法實(shí)現(xiàn)妄痪。會(huì)在原始setter方法前后添加[self willChangeValueForKey: aKey][self didChangeValueForKey: aKey]

小結(jié)

那么到這里,就對(duì)KVO的實(shí)現(xiàn)有個(gè)大致的了解楞件,通過(guò)isa-swizzling技術(shù), 替換被觀察的類(lèi)信息衫生,并且hook被觀察keyPath setter方法裳瘪,在原始方法調(diào)用前后添加[self willChangeValueForKey: aKey][self didChangeValueForKey: aKey],從而達(dá)到監(jiān)聽(tīng)屬性變化的功能


源碼實(shí)現(xiàn)

接下來(lái)從源碼中查看KVO的所有流程

- (void) addObserver: (NSObject*)anObserver
      forKeyPath: (NSString*)aPath
         options: (NSKeyValueObservingOptions)options
         context: (void*)aContext
{
  GSKVOInfo             *info;
  GSKVOReplacement      *r;
  NSKeyValueObservationForwarder *forwarder;
  NSRange               dot;

  setup();
  [kvoLock lock];

  // Use the original class
  r = replacementForClass([self class]);

  /*
   * Get the existing observation information, creating it (and changing
   * the receiver to start key-value-observing by switching its class)
   * if necessary.
   */
  info = (GSKVOInfo*)[self observationInfo];
  if (info == nil)
    {
      info = [[GSKVOInfo alloc] initWithInstance: self];
      [self setObservationInfo: info];
      object_setClass(self, [r replacement]);
    }

  /*
   * Now add the observer.
   */
  dot = [aPath rangeOfString:@"."];
  if (dot.location != NSNotFound)
    {
      forwarder = [[NSKeyValueObservationForwarder alloc]
        initWithKeyPath: aPath
           ofObject: self
         withTarget: anObserver
        context: aContext];
      [info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: forwarder];
    }
  else
    {
      [r overrideSetterFor: aPath];
      [info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: aContext];
    }

  [kvoLock unlock];
}

KVO的入口罪针,添加觀察者方法彭羹,主要做了以下幾件事

  1. replacementForClass,創(chuàng)建替換類(lèi)泪酱,并且加入到全局classTable中派殷,方便以后使用
  2. 獲取對(duì)象的監(jiān)聽(tīng)者數(shù)據(jù)GSKVOInfo,如果沒(méi)有就創(chuàng)建新的
  3. 接下來(lái)分為兩種情況
    1?? 如果利用了點(diǎn)語(yǔ)法(self.keyPath.keyPath), 會(huì)利用NSKeyValueObservationForwarder遞歸創(chuàng)建子對(duì)象監(jiān)聽(tīng)墓阀,子對(duì)象在將監(jiān)聽(tīng)到的變化轉(zhuǎn)發(fā)到上層愈腾,以后再具體分析
    2?? 默認(rèn)情況(keyPath)直接監(jiān)聽(tīng)對(duì)象的某個(gè)屬性,則會(huì)調(diào)用overrideSetterFor方法岂津,hook屬性的setter方法,將setter方法的實(shí)現(xiàn)替換為相應(yīng)參數(shù)類(lèi)型的GSKVOSetter中的方法實(shí)現(xiàn)
  4. 然后調(diào)用[info addObserver: anObserver forKeyPath: aPath options: options context: aContext];方法悦即,將新的監(jiān)聽(tīng)保存起來(lái)吮成。

GSKVOInfo 中的添加方法

- (void) addObserver: (NSObject*)anObserver
      forKeyPath: (NSString*)aPath
         options: (NSKeyValueObservingOptions)options
         context: (void*)aContext
{
  GSKVOPathInfo         *pathInfo;
  GSKVOObservation      *observation;
  unsigned              count;

  if ([anObserver respondsToSelector:
    @selector(observeValueForKeyPath:ofObject:change:context:)] == NO)
    {
      return;
    }
  [iLock lock];
  pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
  if (pathInfo == nil)
    {
      pathInfo = [GSKVOPathInfo new];
      // use immutable object for map key
      aPath = [aPath copy];
      NSMapInsert(paths, (void*)aPath, (void*)pathInfo);
      [pathInfo release];
      [aPath release];
    }

  observation = nil;
  pathInfo->allOptions = 0;
  count = [pathInfo->observations count];
  while (count-- > 0)
    {
      GSKVOObservation      *o;

      o = [pathInfo->observations objectAtIndex: count];
      if (o->observer == anObserver)
        {
          o->context = aContext;
          o->options = options;
          observation = o;
        }
      pathInfo->allOptions |= o->options;
    }
  if (observation == nil)
    {
      observation = [GSKVOObservation new];
      GSAssignZeroingWeakPointer((void**)&observation->observer,
    (void*)anObserver);
      observation->context = aContext;
      observation->options = options;
      [pathInfo->observations addObject: observation];
      [observation release];
      pathInfo->allOptions |= options;
    }

  if (options & NSKeyValueObservingOptionInitial)
    {
      /* If the NSKeyValueObservingOptionInitial option is set,
       * we must send an immediate notification containing the
       * existing value in the NSKeyValueChangeNewKey
       */
      [pathInfo->change setObject: [NSNumber numberWithInt: 1]
                           forKey:  NSKeyValueChangeKindKey];
      if (options & NSKeyValueObservingOptionNew)
        {
          id    value;

          value = [instance valueForKeyPath: aPath];
          if (value == nil)
            {
              value = null;
            }
          [pathInfo->change setObject: value
                               forKey: NSKeyValueChangeNewKey];
        }
      [anObserver observeValueForKeyPath: aPath
                                ofObject: instance
                                  change: pathInfo->change
                                 context: aContext];
    }
  [iLock unlock];
}

此方法主要是保存觀察者信息

  1. 查詢(xún)相應(yīng)的GSKVOPathInfo --GSKVOObservation如果有就更新,如果沒(méi)有就創(chuàng)建新的并保存
  2. 如果options中包含NSKeyValueObservingOptionInitial辜梳,則立馬調(diào)用[anObserver observeValueForKeyPath: aPath ofObject: instance change: pathInfo->change context: aContext];發(fā)送消息給觀察者

If the NSKeyValueObservingOptionInitial option is set, we must send an immediate notification containing the existing value in the NSKeyValueChangeNewKey

  1. 其中獲取當(dāng)前值通過(guò)KVC中的[instance valueForKeyPath: aPath];獲取

willChangeValueForKey didChangeValueForKey

這兩個(gè)方法分別添加在setterKVC 賦值前后粱甫,用來(lái)保存屬性值的變化,以及發(fā)送消息給觀察者

  1. willChangeValueForKey
    主要記錄屬性值的 oldValue保存到pathInfo->change中作瞄,如果options包含NSKeyValueObservingOptionPrior茶宵,則會(huì)遍歷所有觀察者,立馬發(fā)送消息給觀察者
    NSKeyValueObservingOptionPrior 表示屬性值修改前后都會(huì)收到通知
  2. didChangeValueForKey
    根據(jù)options保存屬性的新舊值宗挥,遍歷所有的觀察者乌庶,發(fā)送消息
移除觀察者
/*
 * removes the observer
 */
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
  GSKVOPathInfo *pathInfo;

  [iLock lock];
  pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
  if (pathInfo != nil)
    {
      unsigned  count = [pathInfo->observations count];

      pathInfo->allOptions = 0;
      while (count-- > 0)
        {
          GSKVOObservation      *o;

          o = [pathInfo->observations objectAtIndex: count];
          if (o->observer == anObserver || o->observer == nil)
            {
              [pathInfo->observations removeObjectAtIndex: count];
              if ([pathInfo->observations count] == 0)
                {
                  NSMapRemove(paths, (void*)aPath);
                }
            }
          else
            {
              pathInfo->allOptions |= o->options;
            }
    }
    }
  [iLock unlock];
}

此方法主要用來(lái)移除相應(yīng)keyPath的觀察者,方法實(shí)現(xiàn)很簡(jiǎn)單契耿,根據(jù)參數(shù)傳入的anObserveraPath在前面介紹的數(shù)據(jù)結(jié)構(gòu)中查詢(xún)并移除

總結(jié)

到這里KVO的大致流程就分析完了

  1. 主要用了isa-swizzling,修改了觀察者的類(lèi)信息瞒大,并且hooksetter方法,當(dāng)setter方法調(diào)用時(shí)發(fā)送消息給所有觀察者
  2. 由上面源碼可以看出對(duì)觀察者搪桂、被觀察者的引用都是Not Retain, 所以對(duì)象釋放前一定要移除觀察者透敌。
  3. 消息的發(fā)送主要由[self willChangeValueForKey: key], [self didChangeValueForKey: key]觸發(fā),并且必須成對(duì)出現(xiàn)踢械,automaticallyNotifiesObserversForKey方法用來(lái)控制酗电,是否要主要添加上述的兩個(gè)方法,默認(rèn)返回值為YES内列,如果返回NO則不會(huì)自動(dòng)添加撵术,也就是說(shuō)setter的調(diào)用以及KVC修改都不會(huì)觸發(fā)通知
  4. + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key
    此方法用來(lái)設(shè)置依賴(lài)關(guān)系,有時(shí)候需要某屬性值隨著同一對(duì)象的其他屬性的改變而改變话瞧『苫纾可以通過(guò)事先將這樣的依賴(lài)關(guān)系在類(lèi)中注冊(cè)退渗,那么即便屬性值間接地發(fā)生了改變,也會(huì)發(fā)送通知消息蕴纳,被觀察者類(lèi)重寫(xiě)返回和key依賴(lài)的所有key集合
    內(nèi)部實(shí)現(xiàn)也比較簡(jiǎn)單会油,將所有依賴(lài)關(guān)系存儲(chǔ)在全局的dependentKeyTable中,然后hook了所有依賴(lài)的keysetter方法古毛,當(dāng)[self willChangeValueForKey: key], [self didChangeValueForKey: key]調(diào)用時(shí)會(huì)查找所有的依賴(lài)關(guān)系翻翩,然后發(fā)送消息
  5. KVO內(nèi)部多次用到了KVC
    1?? 重寫(xiě) setValue:forKey
    2?? 使用valueForKey --- valueForKeyPath獲取屬性的值,尤其是在使用點(diǎn)語(yǔ)法的時(shí)候稻薇,只有valueForKeyPath可以獲得深層次的屬性值嫂冻。
    所以KVO是基于KVC而實(shí)現(xiàn)的。
NSKeyValueObservationForwarder
@interface NSKeyValueObservationForwarder : NSObject
{
  id                                    target;
  NSKeyValueObservationForwarder        *child;
  void                                  *contextToForward;
  id                                    observedObjectForUpdate;
  NSString                              *keyForUpdate;
  id                                    observedObjectForForwarding;
  NSString                              *keyForForwarding;
  NSString                              *keyPathToForward;
}

最后塞椎,當(dāng)使用key1.key2.key3作為keypath時(shí)桨仿,會(huì)利用NSKeyValueObservationForwarder來(lái)轉(zhuǎn)發(fā),會(huì)生成如下關(guān)系:

被觀察者 觀察者 keyPath
self forwarder1 key1
key1 forwarder2 key2
key2 forwarder3 key3

其中觀察者會(huì)依次相互引用

- (void) observeValueForKeyPath: (NSString *)keyPath
                       ofObject: (id)anObject
                         change: (NSDictionary *)change
                        context: (void *)context
{
  if (anObject == observedObjectForUpdate)
    {
      [self keyPathChanged: nil];
    }
  else
    {
      [target observeValueForKeyPath: keyPathToForward
                            ofObject: observedObjectForUpdate
                              change: change
                             context: contextToForward];
    }
}

在收到通知時(shí)會(huì)進(jìn)行轉(zhuǎn)發(fā)案狠,所以當(dāng)key3進(jìn)行變化時(shí)服傍,forwarder3 ---> forwarder2 ---> forwarder1 ---> 應(yīng)用層注冊(cè)的target

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市骂铁,隨后出現(xiàn)的幾起案子吹零,更是在濱河造成了極大的恐慌,老刑警劉巖拉庵,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灿椅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡钞支,警方通過(guò)查閱死者的電腦和手機(jī)茫蛹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)烁挟,“玉大人麻惶,你說(shuō)我怎么就攤上這事⌒欧颍” “怎么了窃蹋?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)静稻。 經(jīng)常有香客問(wèn)我警没,道長(zhǎng),這世上最難降的妖魔是什么振湾? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任杀迹,我火速辦了婚禮,結(jié)果婚禮上押搪,老公的妹妹穿的比我還像新娘树酪。我一直安慰自己浅碾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布续语。 她就那樣靜靜地躺著垂谢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疮茄。 梳的紋絲不亂的頭發(fā)上滥朱,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音力试,去河邊找鬼徙邻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛畸裳,可吹牛的內(nèi)容都是我干的缰犁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼怖糊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼帅容!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蓬抄,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎夯到,沒(méi)想到半個(gè)月后嚷缭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡耍贾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年阅爽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荐开。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡付翁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晃听,到底是詐尸還是另有隱情百侧,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布能扒,位于F島的核電站佣渴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏初斑。R本人自食惡果不足惜辛润,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望见秤。 院中可真熱鬧砂竖,春花似錦真椿、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至三圆,卻和暖如春狞换,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舟肉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工修噪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人路媚。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓黄琼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親整慎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子脏款,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • KVC KVC定義 KVC(Key-value coding)鍵值編碼,就是指iOS的開(kāi)發(fā)中裤园,可以允許開(kāi)發(fā)者通過(guò)K...
    暮年古稀ZC閱讀 2,128評(píng)論 2 9
  • KVC KVC定義 KVC(Key-value coding)鍵值編碼撤师,就是指iOS的開(kāi)發(fā)中,可以允許開(kāi)發(fā)者通過(guò)K...
    jackyshan閱讀 51,820評(píng)論 9 200
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,089評(píng)論 1 32
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,321評(píng)論 8 265
  • 因?yàn)轫?xiàng)目需要拧揽,和小伙伴pair學(xué)習(xí)了下Moco. 一.Moco是什么剃盾? 官方網(wǎng)站https://github.co...
    星期六1111閱讀 3,255評(píng)論 2 3