【iOS】Runtime解讀

這段時間在公司要做一個組件開發(fā),需要用到OC Runtime特性的地方很多,于是在以前的了解上又惡補(bǔ)了一下相關(guān)知識屡贺,以下是自己的一些總結(jié)。如果有不對的地方,歡迎大家及時指出.

一甩栈、Runtime 是什么泻仙?

Runtime機(jī)制是Objective-C的一個重要特性,是其區(qū)別于C語言這種靜態(tài)語言的根本量没,C語言的函數(shù)調(diào)用會在編譯期確定好玉转,在編譯完成后直接順序執(zhí)行。而OC是一門動態(tài)語言殴蹄,函數(shù)調(diào)用變成了消息發(fā)送(msgSend)究抓,在編譯期不能確定調(diào)用哪個函數(shù),所以Runtime就是解決如何在運(yùn)行期找到調(diào)用方法這樣的問題袭灯。

二刺下、類的結(jié)構(gòu)定義

要想理解清楚Runtime,首先要清楚的了解類的結(jié)構(gòu)稽荧, 因為Objective-C 是面向?qū)ο笳Z言橘茉,所以可以說 OC 里“一切皆對象”,首先要牢記這一點(diǎn)姨丈。眾所周知一個實例instance 是一個類實例化生成的對象(以下簡稱實例對象)畅卓,那各個不同的類呢?實際上各個不同的類本質(zhì)上也是各個不同的對象(以下簡稱類對象)

先來看張圖:

實例和類的構(gòu)造說明
上圖中:
superClass:類對象所擁有的父類指針蟋恬,指向當(dāng)前類的父類.
isa: 實例和類對象都擁有的指針翁潘,指向所屬類,即當(dāng)前對象由哪個類構(gòu)造生成.

所以從上圖我們可以得出以下幾點(diǎn)結(jié)論:

  • 實例對象的isa指針指向所屬類筋现,所屬類的isa指針指向元類(metaClass) .
  • metaClass也有isa 和superClass 指針唐础,其中isa指針指向Root class (meta) 根元類.
  • superClass 指針追溯整個繼承鏈,自底向上直至根類 (NSObject或NSProxy) .
  • Root class (meta) 根元類的superClass指針指向根類
  • 根類和根元類的isa 指針都指向Root class (meta) 根元類

好矾飞,到這里我們清楚的了解了實例和類在內(nèi)存中的布局構(gòu)造一膨,那么接下來我們來看一下類的結(jié)構(gòu)定義,在 objc/runtime.h中洒沦,類由objc_class結(jié)構(gòu)體構(gòu)造而成豹绪,如下是在objc/runtime.h中,類的結(jié)構(gòu)的定義:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY; // isa指針  指向所屬類

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE; // 父類指針
    const char *name                                         OBJC2_UNAVAILABLE; // 類名
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE; // 成員變量地址列表
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE; // 方法地址列表
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE; // 緩存最近使用的方法地址申眼,以避免多次在方法地址列表中查詢瞒津,提升效率
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE; // 遵循的協(xié)議列表
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

結(jié)合上述結(jié)構(gòu)定義和Runtime提供的一系列方法,我們可以輕而易舉的獲取一個類的相關(guān)信息括尸,譬如:成員變量列表ivars巷蚪、實例方法列表methodLists、遵循協(xié)議列表protocols和屬性列表propertyList等濒翻。

下面簡單的列舉一下獲取相關(guān)列表的方法:

  #import <objc/runtime.h>

    例如:獲取UIView類的相關(guān)信息
    id LenderClass = objc_getClass("UIView");
    unsigned int outCount, i;
    
    //獲取成員變量列表
    Ivar *ivarList = class_copyIvarList(LenderClass, &outCount);
    for (i=0; i<outCount; i++) {
        Ivar ivar = ivarList[i];
        fprintf(stdout, "Ivar:%s \n", ivar_getName(ivar));
    }

    //獲取實例方法列表
    Method *methodList = class_copyMethodList(LenderClass, &outCount);
    for (i=0; i<outCount; i++) {
        Method method = methodList[i];
        NSLog(@"instanceMethod:%@", NSStringFromSelector(method_getName(method)));
    }
    
    //獲取協(xié)議列表
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList(LenderClass, &outCount);
    for (i=0; i<outCount; i++) {
        Protocol *protocol = protocolList[i];
        fprintf(stdout, "protocol:%s \n", protocol_getName(protocol));
    }
    
    //獲取屬性列表
    objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        //第二個輸出為屬性特性屁柏,包含類型編碼啦膜、讀寫權(quán)限等
        fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
    }
    
    //注意釋放
    free(ivarList);
    free(methodList);
    free(protocolList);
    free(properties);

通過上面的列子,我們不難發(fā)現(xiàn)一個對象所擁有的實例方法淌喻,都注冊在其所屬類的方法列表methodLists中僧家,同理你會發(fā)現(xiàn)所有的類方法,都注冊在這個類所屬元類的方法列表中裸删。

三八拱、Method

既然我們已經(jīng)清楚的知道不同類型的方法都保存在相對應(yīng)的方法列表methodLists中,那方法列表中所存儲的方法的結(jié)構(gòu)又是怎樣的呢涯塔?弄清這一點(diǎn)對我們下面理解方法調(diào)用很有幫助肌稻。

好,我們回頭看下上面類的結(jié)構(gòu)定義中方法列表的定義:

struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE; // 方法地址列表

不難發(fā)現(xiàn)methodLists是一個指向objc_method_list 結(jié)構(gòu)體類型指針的指針伤塌,那objc_method_list 的結(jié)構(gòu)又是怎樣的呢灯萍?在runtime.h里搜索其定義:

struct objc_method {
    SEL method_name     //方法id                                    OBJC2_UNAVAILABLE;
    char *method_types  //各參數(shù)和返回值類型的typeEncode                                     OBJC2_UNAVAILABLE;
    IMP method_imp      //方法實現(xiàn)                                   OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}  

這就很清楚了轧铁,objc_method_list中有objc_method每聪,而objc_method里有SEL 和 IMP 這兩個關(guān)鍵點(diǎn):

  1. SEL:在編譯時期,根據(jù)根據(jù)方法名字生成的唯一int標(biāo)識(會轉(zhuǎn)為char*字符串使用)齿风,可以將其理解為方法的ID药薯。
  2. IMP:方法實現(xiàn)、函數(shù)指針救斑,該指針指向最終的函數(shù)實現(xiàn)童本。

SEL 結(jié)構(gòu)如下:

typedef struct objc_selector *SEL;

struct objc_selector {
      char *name;                       OBJC2_UNAVAILABLE;
      char *types;                      OBJC2_UNAVAILABLE;
  };

注:既然SEL 和 IMP 一一對應(yīng),那么方法列表中會存在兩個SEL相同的方法嗎脸候?
答案是:會的穷娱。因為methodLists方法列表是一個數(shù)組,當(dāng)我們給一個類添加一個分類运沦,并在分類中重寫這個類的方法時泵额,編譯后會發(fā)現(xiàn)方法列表中有兩個SEL相同的method,對應(yīng)兩個不同的IMP携添,那么當(dāng)調(diào)用這個方法時嫁盲,會調(diào)用執(zhí)行那個IMP呢?答案是分類的那個烈掠,原理會在以后的文章中補(bǔ)上羞秤。

相信到這里,你已經(jīng)大致猜到了方法調(diào)用的過程左敌,其實一個方法的調(diào)用就是通過方法名生成的SEL瘾蛋,到相應(yīng)類的方法列表methodLists中,遍歷查找相匹配的IMP矫限,獲取最終實現(xiàn)并執(zhí)行的過程哺哼。當(dāng)然OC實現(xiàn)這些過程京革,還依賴于一個Runtime的核心:objc_msgSend

四、objc_msgSend

objc_msgSend定義:

/** 
 * Sends a message with a simple return value to an instance of a class.
 * 
 * @param self A pointer to the instance of the class that is to receive the message.
 * @param op The selector of the method that handles the message.
 * @param ... 
 *   A variable argument list containing the arguments to the method.
 * 
 * @return The return value of the method.
 */
void objc_msgSend(void /* id self, SEL op, ... */ )

OC中所有的方法調(diào)用最終都會走到objc_msgSend去調(diào)用幸斥,這個方法支持任意返回值類型匹摇,任意參數(shù)類型和個數(shù)的函數(shù)調(diào)用。

支持所有的函數(shù)調(diào)用甲葬?廊勃?這不是違背了Calling Convention(“調(diào)用規(guī)則”)?這樣最后底層執(zhí)行的時候能夠正確取參并正確返回嗎经窖?難道不會報錯崩潰坡垫?答案是當(dāng)然不會,因為objc_msgSend是用匯編寫的画侣,調(diào)用執(zhí)行的時候冰悠,直接執(zhí)行自己的匯編代碼就OK了,不再需要編譯器根據(jù)相應(yīng)的調(diào)用規(guī)則生成匯編指令配乱,所以它也就不需要遵循相應(yīng)的調(diào)用規(guī)則溉卓。后續(xù)會寫一篇Libffi相關(guān)的文章表述一下。

當(dāng)一個對象調(diào)用[receiver message]的時候搬泥,會被改寫成objc_magSend(self桑寨,_cmd,···)忿檩,其中self 是指向消息接受者的指針尉尾,_cmd 是根據(jù)方法名生成的SEL,后面是方法調(diào)用所需參數(shù)燥透。執(zhí)行過程中就會拿著生成的SEL沙咏,到消息接受者所屬類的方法列表中遍歷查找對應(yīng)的IMP,然后調(diào)用執(zhí)行班套≈辏可以看出OC的方法調(diào)用中間經(jīng)歷了一系列過程,而不是像C一樣直接按地址取用孽尽,所以我們可以利用這一點(diǎn)窖壕,在消息處理的過程中對消息做一些特殊處理,譬如:消息的轉(zhuǎn)發(fā)杉女,消息的替換瞻讽,消息的防崩潰處理等。

objc_msgSend 調(diào)用流程:

  • 檢查SEL是否應(yīng)該被忽略
  • 檢查target 是否為空熏挎,為空則忽略該消息
  • 查找與SEL相匹配的IMP
    • 如果是調(diào)用實例方法速勇,則通過isa指針找到實例對象所屬類,遍歷其緩存列表及方法列表查找對應(yīng)IMP坎拐,如果找不到則去super_class指針?biāo)父割愔胁檎曳炒牛敝粮?
    • 如果是調(diào)用類方法养匈,則通過isa指針找到類對象所屬元類,遍歷其緩存列表及方法列表查找對應(yīng)IMP都伪,如果找不到則去super_class指針?biāo)父割愔胁檎遗缓酰敝粮?
  • 如果都沒找到,則轉(zhuǎn)向攔截調(diào)用陨晶,進(jìn)行消息動態(tài)解析
  • 如果沒有覆寫攔截調(diào)用相關(guān)方法猬仁,則程序報錯:unrecognized selector sent to instance.

注:上述過程中的緩存列表就是類結(jié)構(gòu)定義中的 struct objc_cache *cache 因為OC調(diào)用要經(jīng)過一系列的流程比較慢,所以引入了緩存列表機(jī)制先誉,調(diào)用過的方法會存到緩存列表中湿刽,這一點(diǎn)極大的提高了OC函數(shù)調(diào)用的效率。

五褐耳、動態(tài)消息解析

如四所述诈闺,如果在objc_msgSend調(diào)用的前3個步驟結(jié)束,還未找到SEL 對應(yīng) IMP铃芦,則會轉(zhuǎn)向動態(tài)消息解析流程雅镊,也可簡稱為攔截調(diào)用,所謂攔截調(diào)用就是在消息無法處理 unrecognized selector sent to instance. 之前杨帽,我們有機(jī)會覆寫NSObject 的幾個方法來處理消息漓穿,這也正是OC 動態(tài)性的體現(xiàn)。

這幾個方法分別是:

/* 所調(diào)用類方法是否為動態(tài)添加 */
+ (BOOL)resolveClassMethod:(SEL)sel;
/* 所調(diào)用實例方法是否為動態(tài)添加 */
+ (BOOL)resolveInstanceMethod:(SEL)sel;
/* 將消息轉(zhuǎn)發(fā)到其他目標(biāo)對象處理 */
- (id)forwardingTargetForSelector:(SEL)aSelector;
/* 返回方法簽名 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector注盈;
/* 在這里觸發(fā)調(diào)用 */
- (void)forwardInvocation:(NSInvocation *)anInvocation;

同時來看張網(wǎng)上流轉(zhuǎn)較廣的關(guān)于動態(tài)消息解析的流程圖:


動態(tài)消息解析流程圖

流程說明:

  1. 通過resolveClassMethod:resolveInstanceMethod: 判斷所調(diào)用方法是否為動態(tài)添加,默認(rèn)返回NO叙赚,返回YES則通過 class_addMethod 動態(tài)添加方法老客,處理消息。
  2. forwardingTargetForSelector: 將消息轉(zhuǎn)發(fā)給某個指定的目標(biāo)對象來處理震叮,效率比較高胧砰,如果返回空則進(jìn)入下一步。
  3. methodSignatureForSelector: 此方法用于方法簽名苇瓣,將調(diào)用方法的參數(shù)類型和返回值進(jìn)行封裝并返回尉间,如果返回nil,則說明消息無法處理unrecognized selector sent to instance.击罪,正常返回則進(jìn)入forwardInvocation: 此步拿到的anInvocation哲嘲,包含了方法調(diào)用所需要的全部信息,在這里可以修改方法實現(xiàn)媳禁,修改響應(yīng)對象眠副,然后invoke 執(zhí)行,執(zhí)行成功則結(jié)束竣稽。失敗則報錯unrecognized selector sent to instance.

六囱怕、Runtime相關(guān)實踐

經(jīng)過上面的講解霍弹,相信大家已經(jīng)對Runtime 的原理有了比較清晰的理解,那么下面我們來看看Runtime的相關(guān)應(yīng)用吧娃弓。

- 動態(tài)添加方法

如果我們調(diào)用一個方法列表中不存在的方法newMethod:典格,根據(jù)上述的動態(tài)消息解析流程可知,會先走進(jìn)resolveClassMethod:resolveInstanceMethod:台丛,假設(shè)消息接受者receiver為一個實例對象:

/* 調(diào)用一個不存在的方法 */
[receiver performSelector:@selector(newMethod:) withObject:@"add_newMethod_suc"];

層層查找方法列表均為找到對應(yīng)IMP钝计,轉(zhuǎn)向動態(tài)消息解析,此時需要在目標(biāo)對象的類里重寫resolveInstanceMethod:

void newMethod(id self, SEL _cmd, NSString *string){
    NSLog(@"%@", string);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
     if (sel == @selector(newMethod)) {
        // 參數(shù)依次是:給哪個類添加方法齐佳、方法ID:SEL私恬、函數(shù)實現(xiàn):IMP、方法類型編碼:types
        class_addMethod(self, @selector(newMethod), newMethod, "v@:@");
        return YES;
     }
    return [super resolveInstanceMethod:sel];
}

/**class_addMethod 
 * Adds a new method to a class with a given name and implementation.
 * 
 * @param cls The class to which to add a method.
 * @param name A selector that specifies the name of the method being added.
 * @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
 * @param types An array of characters that describe the types of the arguments to the method. 
 * 
 * @return YES if the method was added successfully, otherwise NO 
*/
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) 
- 方法替換 & 關(guān)聯(lián)對象

關(guān)于這兩個方面炼吴,下面通過一個UIButton的防重點(diǎn)擊的實現(xiàn)來說明:

#import <UIKit/UIKit.h>

@interface UIButton (IgnoreEvent)
// 按鈕點(diǎn)擊的間隔時間
@property (nonatomic, assign) NSTimeInterval clickDurationTime;

@end
#import "UIButton+IgnoreEvent.h"
#import <objc/runtime.h>

// 默認(rèn)的點(diǎn)擊間隔時間
static const NSTimeInterval defaultDuration = 0.0001f;

// 記錄是否忽略按鈕點(diǎn)擊事件本鸣,默認(rèn)第一次執(zhí)行事件
static BOOL _isIgnoreEvent = NO;

// 設(shè)置執(zhí)行按鈕事件狀態(tài)
static void resetState() {
    _isIgnoreEvent = NO;
}

@implementation UIButton (IgnoreEvent)

@dynamic clickDurationTime;

+ (void)load {
    SEL originSEL = @selector(sendAction:to:forEvent:);
    SEL mySEL = @selector(my_sendAction:to:forEvent:);
    
    Method originM = class_getInstanceMethod([self class], originSEL);
    IMP originIMP = method_getImplementation(originM);
    const char *typeEncodinds = method_getTypeEncoding(originM);
    
    Method newM = class_getInstanceMethod([self class], mySEL);
    IMP newIMP = method_getImplementation(newM);

    // 方法替換
    if (class_addMethod([self class], originSEL, newIMP, typeEncodinds)) {
        class_replaceMethod([self class], mySEL, originIMP, typeEncodinds);
    } else {
        method_exchangeImplementations(originM, newM);
    }
}

- (void)my_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    
    if ([self isKindOfClass:[UIButton class]]) {
        
        //1. 按鈕點(diǎn)擊間隔事件
        self.clickDurationTime = self.clickDurationTime == 0 ? defaultDuration : self.clickDurationTime;
        
        //2. 是否忽略按鈕點(diǎn)擊事件
        if (_isIgnoreEvent) {
            //2.1 忽略按鈕事件
            return;
        } else if(self.clickDurationTime > 0) {
            //2.2 不忽略按鈕事件
            
            // 后續(xù)在間隔時間內(nèi)直接忽略按鈕事件
            _isIgnoreEvent = YES;
            
            // 間隔事件后,執(zhí)行按鈕事件
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.clickDurationTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                resetState();
            });
            
            // 發(fā)送按鈕點(diǎn)擊消息
            [self my_sendAction:action to:target forEvent:event];
        }
    } else {
        [self my_sendAction:action to:target forEvent:event];
    }
}

#pragma mark - associate
// 關(guān)聯(lián)對象
- (void)setClickDurationTime:(NSTimeInterval)clickDurationTime {
    objc_setAssociatedObject(self, @selector(clickDurationTime), @(clickDurationTime), OBJC_ASSOCIATION_RETAIN_ASSIGN);
}

- (NSTimeInterval)clickDurationTime {
    return [objc_getAssociatedObject(self, @selector(clickDurationTime)) doubleValue];
}

@end

上述方法交換的代碼已經(jīng)很清楚了硅蹦,簡單說下關(guān)聯(lián)對象的兩個函數(shù):

  • 設(shè)置關(guān)聯(lián)對象 :objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
  1. id object:給誰設(shè)置關(guān)聯(lián)對象
  2. const void *key: 關(guān)聯(lián)對象唯一的key
  3. id value: 關(guān)聯(lián)對象的值
  4. objc_AssociationPolicy policy:關(guān)聯(lián)策略荣德,有以下幾種:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /< Specifies a weak reference to the associated object. /
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /
< Specifies a strong reference to the associated object.
* The association is not made atomically. /
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /
< Specifies that the associated object is copied.
* The association is not made atomically. /
OBJC_ASSOCIATION_RETAIN = 01401, /
< Specifies a strong reference to the associated object.
* The association is made atomically. /
OBJC_ASSOCIATION_COPY = 01403 /
< Specifies that the associated object is copied.
* The association is made atomically. */
};
```

  • 獲取關(guān)聯(lián)對象 :id objc_getAssociatedObject(id object, const void *key)
  1. id object:獲取誰的關(guān)聯(lián)對象
  2. const void *key: 根據(jù)key獲取相應(yīng)的關(guān)聯(lián)對象值

Runtime的相關(guān)應(yīng)用還有很多很多,大家可以在以后的開發(fā)過程中慢慢探索童芹。

綜上涮瞻,就是這次對Runtime的一些總結(jié),對于Runtime整體來說可能只是很小的一部分假褪,但是對于大家理解一些常見的Runtime使用應(yīng)該還是有所幫助的署咽,鑒于蘋果API一直在更新和自己能力尚淺,文章中如有錯誤或不妥之處生音,還請大家及時指出宁否。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缀遍,隨后出現(xiàn)的幾起案子慕匠,更是在濱河造成了極大的恐慌,老刑警劉巖域醇,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件台谊,死亡現(xiàn)場離奇詭異,居然都是意外死亡譬挚,警方通過查閱死者的電腦和手機(jī)锅铅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來殴瘦,“玉大人狠角,你說我怎么就攤上這事◎揭福” “怎么了丰歌?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵姨蟋,是天一觀的道長。 經(jīng)常有香客問我立帖,道長眼溶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任晓勇,我火速辦了婚禮堂飞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绑咱。我一直安慰自己绰筛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布描融。 她就那樣靜靜地躺著铝噩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪窿克。 梳的紋絲不亂的頭發(fā)上骏庸,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機(jī)與錄音年叮,去河邊找鬼具被。 笑死,一個胖子當(dāng)著我的面吹牛只损,可吹牛的內(nèi)容都是我干的一姿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼改执,長吁一口氣:“原來是場噩夢啊……” “哼啸蜜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起辈挂,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎裹粤,沒想到半個月后终蒂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡遥诉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年拇泣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矮锈。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡霉翔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出苞笨,到底是詐尸還是另有隱情债朵,我是刑警寧澤子眶,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站序芦,受9級特大地震影響臭杰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谚中,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一渴杆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宪塔,春花似錦磁奖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至来吩,卻和暖如春敢辩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背弟疆。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工戚长, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怠苔。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓同廉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親柑司。 傳聞我的和親對象是個殘疾皇子迫肖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,715評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,556評論 33 466
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,135評論 0 9
  • 《朽木花開》 分享原唱:萬芳(電影《花漾》插曲)的單曲《歌妓祭鬼 (閩南)》 祭鬼吧 一爐黃昏 生死不改沒落之愛 ...
    藍(lán)海豚酒吧的主人閱讀 257評論 0 0
  • 代碼樣板(來自jisuanke) 說明 并查集有兩個最重要的操作:merge和find整個class有一個私有數(shù)組...
    qratosone閱讀 264評論 0 0