分類Category的本質(zhì),load和initialize的本質(zhì)

問題

  1. Category的實現(xiàn)原理,以及Category為什么只能加方法不能加屬性。
  2. CategoryExtension的區(qū)別是什么?
  3. Category中有load方法嗎?load方法是什么時候調(diào)用的?load方法能繼承嗎?
  4. loadinitialize的區(qū)別,以及它們在Category重寫的時候的調(diào)用的次序。
  5. Category能否添加成員變量?如果可以,如何給Category添加成員變量?

1. Category的使用

使用下面的這一段簡單代碼來分析:

// Preson類 
// Preson.h
#import <Foundation/Foundation.h>
@interface Preson : NSObject
{
    int _age;
}
- (void)run;
@end

// Preson.m
#import "Preson.h"
@implementation Preson
- (void)run
{
    NSLog(@"Person - run");
}
@end

// Preson擴(kuò)展1
// Preson+Test.h
#import "Preson.h"
@interface Preson (Test) <NSCopying>
- (void)test;
+ (void)abc;
@property (assign, nonatomic) int age;
- (void)setAge:(int)age;
- (int)age;
@end

// Preson+Test.m
#import "Preson+Test.h"
@implementation Preson (Test)
- (void)test
{
}

+ (void)abc
{
}
- (void)setAge:(int)age
{
}
- (int)age
{
    return 10;
}
@end

// Preson分類2
// Preson+Test2.h
#import "Preson.h"
@interface Preson (Test2)
@end

// Preson+Test2.m
#import "Preson+Test2.h"
@implementation Preson (Test2)
- (void)run
{
    NSLog(@"Person (Test2) - run");
}
@end

我們之前講到過實例對象的isa指針指向類對象,類對象的isa指針指向元類對象,當(dāng)p調(diào)用run方法時,通過實例對象的isa指針找到類對象,然后在類對象中查找對象方法,如果沒有找到,就通過類對象的super_class指針找到父類對象,接著去尋找run方法。

那么當(dāng)調(diào)用分類的方法時,步驟是否和調(diào)用對象方法一樣呢?

分類中的對象方法依然是存儲在類對象中的,同本類對象方法在同一個地方,調(diào)用步驟也同調(diào)用對象方法一樣。如果是類方法的話,也同樣是存儲在元類對象中。
那么分類方法是如何存儲在類對象中的,我們來通過源碼看一下分類的底層結(jié)構(gòu)。

2. 分類的底層結(jié)構(gòu)

擴(kuò)展的方法不是在編譯時期合并至原來的類,而是在運(yùn)行時合并的。

我們將OC編譯的文件生成底層的C++實現(xiàn)

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Preson+Test.m

2.1 分類結(jié)構(gòu)體

在分類轉(zhuǎn)化為C++文件中可以找到_category_t 結(jié)構(gòu)體中,存放著類名,對象方法列表,類方法列表,協(xié)議列表,以及屬性列表。

struct _category_t {
    const char *name; // 類名
    struct _class_t *cls;
    const struct _method_list_t *instance_methods; // 對象方法列表
    const struct _method_list_t *class_methods; // 類方法列表
    const struct _protocol_list_t *protocols; // 協(xié)議列表
    const struct _prop_list_t *properties; // 屬性列表
};

2.2 分類結(jié)構(gòu)體的成員列表

2.2.1 分類的實例方法結(jié)構(gòu)體

存放實例方法_method_list_t類型的結(jié)構(gòu)體,如下所示

static struct /*_method_list_t*/ {
    unsigned int entsize;  // 方法占用的內(nèi)存
    unsigned int method_count; // 方法數(shù)量
    struct _objc_method method_list[3]; // 方法列表
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    3,
    {{(struct objc_selector *)"test", "v16@0:8", (void *)_I_Person_Test_test},
    {(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_Person_Test_setAge_},
    {(struct objc_selector *)"age", "i16@0:8", (void *)_I_Person_Test_age}}
};

上面中我們發(fā)現(xiàn)這個結(jié)構(gòu)體 _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test 從名稱可以看出是INSTANCE_METHODS對象方法,并且一一對應(yīng)為上面結(jié)構(gòu)體內(nèi)賦值。我們可以看到結(jié)構(gòu)體中存儲了方法占用的內(nèi)存,方法數(shù)量,以及方法列表。并且從上圖中找到分類中我們添加的對象方法,test , setAge, age三個方法。

2.2.2 分類的類方法結(jié)構(gòu)體

存放類方法_method_list_t類型的類方法結(jié)構(gòu)體,如下所示

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"abc", "v16@0:8", (void *)_C_Person_Test_abc}}
};

同上面實例方法列表一樣,這個我們可以看出是類方法列表結(jié)構(gòu)體 _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test,同對象方法結(jié)構(gòu)體相同,同樣可以看到我們實現(xiàn)的類方法abc

2.2.3 分類的協(xié)議方法結(jié)構(gòu)體

存放協(xié)議列表結(jié)構(gòu)體_protocol_list_t,假如我們實現(xiàn)了NSCopying協(xié)議

static const char *_OBJC_PROTOCOL_METHOD_TYPES_NSCopying [] __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "@24@0:8^{_NSZone=}16"
};

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", 0}}
};

struct _protocol_t _OBJC_PROTOCOL_NSCopying __attribute__ ((used)) = {
    0,
    "NSCopying",
    0,
    (const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,
    0,
    0,
    0,
    0,
    sizeof(_protocol_t),
    0,
    (const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCopying
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCopying = &_OBJC_PROTOCOL_NSCopying;

static struct /*_protocol_list_t*/ {
    long protocol_count; // 協(xié)議數(shù)量
    struct _protocol_t *super_protocols[1]; // 存儲協(xié)議方法
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_NSCopying
};

通過上述源碼可以看到先將協(xié)議方法通過_method_list_t結(jié)構(gòu)體存儲

之后通過_protocol_t結(jié)構(gòu)體存儲在_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Test中同_protocol_list_t結(jié)構(gòu)體一一對應(yīng),分別為protocol_count協(xié)議數(shù)量以及存儲了協(xié)議方法的_protocol_t結(jié)構(gòu)體。

2.2.4 分類的屬性列表

最后我們可以看到屬性列表結(jié)構(gòu)體_prop_list_t

static struct /*_prop_list_t*/ {
    unsigned int entsize;  // 占用空間
    unsigned int count_of_properties; // 屬性數(shù)量
    struct _prop_t prop_list[1]; // 屬性列表
} _OBJC_$_PROP_LIST_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"age","Ti,N"}} // age屬性
};

屬性列表結(jié)構(gòu)體_OBJC_$_PROP_LIST_Person_$_Test_prop_list_t結(jié)構(gòu)體對應(yīng),存儲屬性的占用空間,屬性屬性數(shù)量,以及屬性列表,從上圖中可以看到我們自己添加的age屬性。

2.2.5 分類_category_t結(jié)構(gòu)體總結(jié)

最后我們可以看到定義了_OBJC_$_CATEGORY_Person_$_Test結(jié)構(gòu)體,并且將我們上面著重分析的結(jié)構(gòu)體一一賦值。

struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;
    const struct _method_list_t *class_methods;
    const struct _protocol_list_t *protocols;
    const struct _prop_list_t *properties;
};

//************************ 上下一一對應(yīng)  ******************************************

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_Person;

static struct _category_t _OBJC_$_CATEGORY_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Test,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Test,
};
static void OBJC_CATEGORY_SETUP_$_Person_$_Test(void ) {
    _OBJC_$_CATEGORY_Person_$_Test.cls = &OBJC_CLASS_$_Person;
}

并且我們看到定義原類_class_t類型的OBJC_CLASS_$_Preson結(jié)構(gòu)體

最后將分類_OBJC_$_CATEGORY_Person_$_Testcls指針指向原類OBJC_CLASS_$_Preson結(jié)構(gòu)體地址。我們這里可以看出,cls指針指向的應(yīng)該是原類的類對象的地址。

3. 源碼分析

通過查看分類的源碼我們可以找到底層分類category_t結(jié)構(gòu)體。

objc源碼路徑:runtime/objc-runtime-new.h

struct category_t {
    const char *name; // 類名
    classref_t cls;
    struct method_list_t *instanceMethods; // 對象方法
    struct method_list_t *classMethods; // 類方法
    struct protocol_list_t *protocols; // 協(xié)議
    struct property_list_t *instanceProperties; // 屬性
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

從源碼基本可以看出我們平時使用categroy的方式,對象方法,類方法,協(xié)議,和屬性都可以找到對應(yīng)的存儲方式。并且我們發(fā)現(xiàn)分類結(jié)構(gòu)體中是不存在成員變量的,因此分類中是不允許添加成員變量的。分類中添加的屬性并不會幫助我們自動生成成員變量,只會生成get set方法的聲明,需要我們自己去實現(xiàn)。

通過源發(fā)現(xiàn),分類的方法,協(xié)議,屬性等好像確實是存放在categroy結(jié)構(gòu)體里面的,那么他又是如何合并存儲到原類的類對象中的?

3.1 分類是如何存儲方法,屬性,協(xié)議的

通過我們runtime的初始化函數(shù)_objc_init來探尋答案。

objc源碼路徑:runtime/objc-os.mm

入口函數(shù):_objc_init:

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    // images指是鏡像、模塊
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

我們來到&map_images讀取模塊(images這里代表模塊)

↓↓↓

來到map_images_nolock函數(shù)

↓↓↓

來到_read_images函數(shù)

最終在_read_images函數(shù)中找到分類相關(guān)代碼:

objc源碼路徑:runtime/objc-runtime-new.mm

// Discover categories. 
    for (EACH_HEADER) {
        // 獲取到分類列表
        category_t **catlist = _getObjc2CategoryList(hi, &count); 
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        // 遍歷,獲取其中的方法,協(xié)議,屬性
        // 內(nèi)部調(diào)用 remethodizeClass
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    // 重新整合分類里面新添加的東西
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }

從上述代碼中我們可以知道這段代碼是用來查找有沒有分類的。通過_getObjc2CategoryList函數(shù)獲取到分類列表之后,進(jìn)行遍歷,獲取其中的方法,協(xié)議,屬性等。可以看到最終都調(diào)用了remethodizeClass(cls)函數(shù)。我們來到remethodizeClass(cls)函數(shù)內(nèi)部查看:

↓↓↓

remethodizeClass(cls)函數(shù):

/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        // 開始附加分類的相關(guān)信息至原類
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

通過上述代碼我們發(fā)現(xiàn)attachCategories函數(shù)接收了類對象cls和分類數(shù)組cats,如我們一開始寫的代碼所示,一個類可以有多個分類。之前我們說到分類信息存儲在category_t結(jié)構(gòu)體中,那么多個分類則保存在category_list中。

↓↓↓

我們來到attachCategories()函數(shù)內(nèi)部:

/** 接受的參數(shù)
Class cls: 原類的類對象
category_list *cats: 所有的分類列表
*/
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // 0. 根據(jù)每個分類中方法列表、屬性列表、協(xié)議列表分類存儲
    
    // 方法數(shù)組 
    /** 是一個二維數(shù)組
    [
        [// 第一個分類的方法
            method_list_t, 
            method_list_t
        ],
        [// 第二個分類的方法
            method_list_t, 
            method_list_t
        ]
    ]
    */
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    // 屬性數(shù)組
    /** 是一個二維數(shù)組
    [
        [// 第一個分類的屬性
            property_list_t, 
            property_list_t
        ],
        [// 第二個分類的屬性
            property_list_t, 
            property_list_t
        ]
    ]
    */
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    // 協(xié)議數(shù)組
    /** 是一個二維數(shù)組
    [
        [// 第一個分類的協(xié)議
            protocol_list_t, 
            protocol_list_t
        ],
        [// 第二個分類的協(xié)議
            protocol_list_t, 
            protocol_list_t
        ]
    ]
    */
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // 1. 遍歷每一個分類 
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) { 
        // 取出一個分類
        auto& entry = cats->list[i];
        
        // 1.1 將所有分類中的所有方法合并存入mlist數(shù)組中
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
    
        // 1.2 將所有分類中的所有屬性合并存入proplist數(shù)組中
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        // 1.3 將所有分類中的所有協(xié)議合并存入protolist數(shù)組中
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    // 2 調(diào)用attachLists()方法,賦值合并至原來的類對象
    // 獲取原來的類對象rw:即 class_rw_t結(jié)構(gòu)體,是class結(jié)構(gòu)體中用來存儲類對象中的對象方法、屬性、協(xié)議的結(jié)構(gòu)體
    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    
    // 2.1 將所有分類的類對象方法mlists數(shù)組,合并附加到類對象的方法列表中
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
    
    // 2.2 將所有分類的屬性proplists數(shù)組,合并附加到類對象的屬性列表中
    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    // 2.3 將所有分類的協(xié)議protolists數(shù)組,合并附加到類對象的屬性列表中
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

上述源碼中可以看出,首先根據(jù)方法列表,屬性列表,協(xié)議列表,malloc分配內(nèi)存,根據(jù)多少個分類以及每一塊方法需要多少內(nèi)存來分配相應(yīng)的內(nèi)存地址。

之后將每一個分類的所有的方法、屬性、協(xié)議分別放入對應(yīng)mlistproplistsprotolosts數(shù)組中,這三個數(shù)組放著所有分類的所有方法,屬性和協(xié)議。

之后通過類對象的data()方法,拿到類對象的class_rw_t結(jié)構(gòu)體rw,在class結(jié)構(gòu)中我們介紹過,class_rw_t中存放著類對象的方法,屬性和協(xié)議等數(shù)據(jù),rw結(jié)構(gòu)體通過類對象的data方法獲取,所以rw里面存放這類對象里面的數(shù)據(jù)。

之后分別通過rw調(diào)用方法列表、屬性列表、協(xié)議列表的attachList函數(shù),將所有的分類的方法、屬性、協(xié)議列表數(shù)組傳進(jìn)去。

我們大致可以猜想到在attachList方法內(nèi)部將分類和本類相應(yīng)的對象方法,屬性,和協(xié)議進(jìn)行了合并。

↓↓↓

我們來到attachLists函數(shù)內(nèi)部:

objc源碼路徑:runtime/objc-runtime-new.h

/** 接收參數(shù)
addedLists:二維數(shù)組
addedCount: 二位數(shù)組數(shù)量
*/
void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // 原來的列表數(shù)組
            uint32_t oldCount = array()->count;
            
            // 1. 重新申請內(nèi)存
            // 新的的列表數(shù)量:原來的數(shù)量 + 新添加的分類的數(shù)量
            uint32_t newCount = oldCount + addedCount;
            // 將原來的數(shù)組進(jìn)行擴(kuò)容,來存放新添加
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            // 數(shù)組數(shù)量為新的值
            array()->count = newCount;
           
            // 2. 移動原來的方法列表
            // array()->lists 是原來的方法列表位置
            // oldCount * sizeof(array()->lists[0] 是需要移動的字節(jié)數(shù)
            // array()->lists + addedCount新的位置
            // 內(nèi)存移動,將原來的方法列表移動到新的位置,相當(dāng)于前面空出了位置
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            
             // 3. 內(nèi)存復(fù)制
             // array()->lists 是原來的列表位置
             // addedLists是所有的分類的所有列表
             // addedCount * sizeof(array()->lists[0])是需要的字節(jié)數(shù)
             // 內(nèi)存復(fù)制:將所有的分類復(fù)制到原來的位置,在上面一步已經(jīng)提前空出了位置。
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));;
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

由上面的源碼中可以看出,新添加的分類的其實是相當(dāng)于移動到了原來存儲的列表之前的,所以原類和分類重名的時候會優(yōu)先調(diào)用分類的方法。

另外,當(dāng)多個分類有重名的方法的時候由源碼的attachCategories()函數(shù)可以知道,其實是有優(yōu)先調(diào)用最后參與編譯的分類的方法:

...
// 這兒是i--
while (i--) { 
        // 取出一個分類
        auto& entry = cats->list[i];
        ...
}
...

3.2 Extension類擴(kuò)展

類擴(kuò)展其實就是我們平常寫的@interface

// 類擴(kuò)展
@interface AppDelegate ()
// 可以擴(kuò)展一些私有的成員變量、屬性、方法
@end

// 實現(xiàn)
@implementation AppDelegate

@end

和分類categroy不同的是:類擴(kuò)展的信息是在編譯的時候已經(jīng)合并在了類對象中,而分類是在運(yùn)行時合并至原來中的。

3.3 memmovememcpy

上面的源代碼中有兩個重要的數(shù)組:

  1. array()->lists: 原類對象原來的方法列表,屬性列表,協(xié)議列表。
  2. addedLists:傳入所有分類的方法列表,屬性列表,協(xié)議列表。

attachLists函數(shù)中最重要的兩個方法為memmove內(nèi)存移動和memcpy內(nèi)存拷貝。我們先來分別看一下這兩個函數(shù):

// memmove :內(nèi)存移動。
/*  __dst : 移動內(nèi)存的目的地
*   __src : 被移動的內(nèi)存首地址
*   __len : 被移動的內(nèi)存長度
*   將__src的內(nèi)存移動__len塊內(nèi)存到__dst中
*/
void    *memmove(void *__dst, const void *__src, size_t __len);

// memcpy :內(nèi)存拷貝。
/*  __dst : 拷貝內(nèi)存的拷貝目的地
*   __src : 被拷貝的內(nèi)存首地址
*   __n : 被移動的內(nèi)存長度
*   將__src的內(nèi)存移動__n塊內(nèi)存到__dst中
*/
void    *memcpy(void *__dst, const void *__src, size_t __n);

下面我們圖示經(jīng)過memmovememcpy方法過后的內(nèi)存變化。

3.3.1 內(nèi)存移動memmove
memmove_bofore

經(jīng)過memmove方法之后,內(nèi)存變化為:

// array()->lists 原來方法、屬性、協(xié)議列表數(shù)組
// addedCount 分類數(shù)組長度
// oldCount * sizeof(array()->lists[0]) 原來數(shù)組占據(jù)的空間
memmove(array()->lists + addedCount, array()->lists, 
                  oldCount * sizeof(array()->lists[0]));
memmove_after

經(jīng)過memmove方法之后,我們發(fā)現(xiàn),雖然本類的方法,屬性,協(xié)議列表會分別后移,但是本類的對應(yīng)數(shù)組的指針依然指向原始位置。

3.3.2 內(nèi)存復(fù)制memcpy
// array()->lists 原來方法、屬性、協(xié)議列表數(shù)組
// addedLists 分類方法、屬性、協(xié)議列表數(shù)組
// addedCount * sizeof(array()->lists[0]) 原來數(shù)組占據(jù)的空間
memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
memcopy

我們發(fā)現(xiàn)原來指針并沒有改變,至始至終指向開頭的位置。并且經(jīng)過memmovememcpy方法之后,分類的方法,屬性,協(xié)議列表被放在了類對象中原本存儲的方法,屬性,協(xié)議列表前面。

那么為什么要將分類方法的列表追加到本來的對象方法前面呢,這樣做的目的是為了保證分類方法優(yōu)先調(diào)用,我們知道當(dāng)分類重寫本類的方法時,會覆蓋本類的方法。
其實經(jīng)過上面的分析我們知道本質(zhì)上并不是覆蓋,而是優(yōu)先調(diào)用。本類的方法依然在內(nèi)存中的。我們可以通過打印所有類的所有方法名來查看

2020-01-14 11:47:02.458927+0800 分類Category的本質(zhì)[84870:4069262] Person (Test2) - run
2020-01-14 11:47:02.459080+0800 分類Category的本質(zhì)[84870:4069262] Person類: age, run, run, setAge:, test, 

調(diào)用的是Test2中的run方法,并且Person類中存儲著兩個run方法。

3.4 總結(jié)

Category的實現(xiàn)原理,以及Category為什么只能加方法不能加屬性?

分類的實現(xiàn)原理是將Category中的方法,屬性,協(xié)議數(shù)據(jù)放在category_t結(jié)構(gòu)體中,然后將結(jié)構(gòu)體內(nèi)的方法列表拷貝到類對象的方法列表中。

Category可以添加屬性,但是并不會自動生成成員變量及set get方法。因為category_t結(jié)構(gòu)體中并不存在成員變量。

通過之前對對象的分析我們知道成員變量是存放在實例對象中的,并且編譯的那一刻就已經(jīng)決定好了。而分類是在運(yùn)行時才去加載的。那么我們就無法再程序運(yùn)行時將分類的成員變量中添加到實例對象的結(jié)構(gòu)體中。因此分類中不可以添加成員變量。

CategoryExtension的區(qū)別是什么?

和分類Categroy不同的是:類擴(kuò)展的信息是在編譯的時候已經(jīng)合并在了類對象中,而分類是在運(yùn)行時合并至原類中的。

4. loadinitialize

4.1 load方法

4.4.1 基本使用

load方法是runtime在加載類和分類的時候會調(diào)用,是在程序入口調(diào)用函數(shù)main之前調(diào)用,而且只會調(diào)用一次。

通過代碼驗證一下調(diào)用本類的load方法調(diào)用。

我們添加Student繼承Presen類,并添加Student+Test分類,分別重寫+load方法。

2020-01-14 14:07:53.689561+0800 分類Category的本質(zhì)[92689:4179307] Person - load
2020-01-14 14:07:53.690079+0800 分類Category的本質(zhì)[92689:4179307] Student - load
2020-01-14 14:07:53.690142+0800 分類Category的本質(zhì)[92689:4179307] Student (Test) - load

通過驗證我們發(fā)現(xiàn)不僅調(diào)用了分類的load方法,而且調(diào)用了原類的load方法,這和上面我們驗證的優(yōu)先調(diào)用分類的方法的邏輯相沖突,到底為什么會調(diào)用原類的方法,我們通過底層的源碼一探究竟。

4.4.2 調(diào)用原理

同樣的我們從runtime的入口_objc_init函數(shù)的load_images函數(shù)尋找答案:

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    // images指是鏡像、模塊
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

↓↓↓

最終來到了call_load_methods函數(shù)

objc源碼路徑:runtime/objc-loadmethod.mm

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            // 先調(diào)用本類的load方法
            call_class_loads(); 
        }

        // 2. Call category +loads ONCE
        // 再調(diào)用分類的load方法
        more_categories = call_category_loads(); 

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

↓↓↓

先調(diào)用原類的load方法:

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        // 得到load方法的函數(shù)地址
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        // 直接調(diào)用原類的load方法
        (*load_method)(cls, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

↓↓↓

再調(diào)用分類的load方法:

static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    // Detach current loadable list.
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;

    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        
        // 獲取分類的load方法地址
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;
        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            
            // 調(diào)用分類的load方法
            (*load_method)(cls, @selector(load));
            cats[i].cat = nil;
        }
    }
    
    ...
    ...
    ...
}

runtime的消息發(fā)送機(jī)制不同的是,消息發(fā)送機(jī)制是需要通過isa指針去逐層尋找,而load方法是不需要通過消息發(fā)送的,而是直接通過函數(shù)的地址來調(diào)用的

struct loadable_class {
    Class cls;  // may be nil
    IMP method; // 函數(shù)實現(xiàn)地址,指向的是原類的load方法
};

struct loadable_category {
    Category cat;  // may be nil
    IMP method; // 函數(shù)的實現(xiàn)地址,指向的是分類的load方法
};
4.4.3 調(diào)用順序

即使是再復(fù)雜繼承關(guān)系,原類、分類、子類的load方法都會被調(diào)用,并且是按照一定的順序調(diào)用的。

通過上面的源碼可以看到在調(diào)用原類和分類的load方法的時候,都是分別通過一個數(shù)組loadable_classesloadable_categories進(jìn)行for循環(huán)去遍歷的,所以知道數(shù)組的順序,就可以知道方法的調(diào)用順序

我們回到之前的入口函數(shù)load_images,發(fā)現(xiàn)在調(diào)用call_load_methods()函數(shù)之前調(diào)用了prepare_load_methods()方法:

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        // 之前調(diào)用
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

↓↓↓

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertLocked();

    // 1. 先遍歷所有的原類的的load方法
    // 這個順序是有編譯順序決定的,可以手動設(shè)置順序
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        // 1.1 先將父類的load方法進(jìn)行添加
        // 1.2 再將子類的load方法進(jìn)行添加
        schedule_class_load(remapClass(classlist[i]));
    }

    // 2. 再遍歷分類的load方法
    // 這個順序也是有編譯順序決定的,可以手動設(shè)置編譯順序
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

↓↓↓

原類的load方法添加到loadable_classes數(shù)組的順序:

// 1. 會先添加父類的load方法
static void schedule_class_load(Class cls)
{
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // 先通過遞歸調(diào)用 
    // 將父類的load方法添加到loadable_classes數(shù)組
    schedule_class_load(cls->superclass);

    // 再將子類的cls添加到loadable_classes數(shù)組的
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

// 2. 添加到loadable_classes數(shù)組
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

分類的load方法添加到loadable_categories數(shù)組的順序:

// 直接添加到loadable_categories數(shù)組
void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat);

    // Don't bother if cat has no +load method
    if (!method) return;

    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(cat));
    }
    
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }

    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

通過以上源碼的邏輯處理,我們發(fā)現(xiàn)數(shù)組的添加順序?qū)е铝?code>load方法的調(diào)用順序:先將元類添加到數(shù)組,同時會先去將父類添加到數(shù)組,再講子類添加到數(shù)組,最后將分類添加到數(shù)組。所以在調(diào)用load方法的時候也會是按照類的添加順序來調(diào)用。

4.4.4 總結(jié)
  1. 先調(diào)用所有原類的laod方法

    • 按照編譯順序調(diào)用(可以手動設(shè)置編譯順序)
    • 調(diào)用子類的load之前會先調(diào)用父類的load方法
  2. 再調(diào)用分類的laod方法

    • 按照編譯順序調(diào)用(可以手動設(shè)置編譯順序)
Category中有load方法嗎?load方法是什么時候調(diào)用的?load 方法能繼承嗎?

Category中有load方法,load方法在程序加載了類和分類的時候就會調(diào)用,在main函數(shù)之前調(diào)用。load方法可以繼承。調(diào)用子類的load方法之前,會先調(diào)用父類的load方法。一般我們不會手動去調(diào)用load方法,而是讓系統(tǒng)去調(diào)用。

如果非要手動調(diào)用load方法,那么就會按照消息發(fā)送機(jī)制通過isa指針來尋找方法。

4.2 initialize方法

我們?yōu)?code>Preson、Person+TestStudentStudent+Test 添加initialize方法。

4.2.1 基本使用

initialize類第一次接收到消息時,就會調(diào)用,相當(dāng)于第一次使用類的時候就會調(diào)用initialize方法。

initialize方法的調(diào)用是通過消息發(fā)送機(jī)制調(diào)用的,不像load方法是直接通過函數(shù)指針去調(diào)用。

再來驗證一下調(diào)用順序:

2020-04-28 21:22:18.874260+0800 分類Category的本質(zhì)[90053:17829138] Person (Test) initialize
2020-04-28 21:22:18.874726+0800 分類Category的本質(zhì)[90053:17829138] Student (Test) initialize

調(diào)用子類的initialize之前,會先保證調(diào)用父類的initialize方法。如果之前已經(jīng)調(diào)用過initialize,就不會再調(diào)用initialize方法了。

當(dāng)分類重寫initialize方法時會先調(diào)用分類的方法不再調(diào)用原類的方法。

initialize是通過消息發(fā)送機(jī)制調(diào)用的,消息發(fā)送機(jī)制通過isa指針找到對應(yīng)的方法與實現(xiàn),因此優(yōu)先找到分類方法中的實現(xiàn),會優(yōu)先調(diào)用分類方法中的實現(xiàn)。

另外還有一點需要注意:如果子類沒有實現(xiàn)initialize方法,那么會調(diào)用父類的initialize方法,這一點我們會通過源碼去驗證。

4.2.2 源碼分析

在底層源碼里面通過函數(shù)class_getInstanceMethod和函數(shù)class_getClassMethod來找到實例方法和類方法

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

    Method meth;
    meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);
    if (meth == (Method)1) {
        // Cache contains forward:: . Stop searching.
        return nil;
    } else if (meth) {
        return meth;
    }
        
    // 搜索方法
    lookUpImpOrForward(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);
    if (meth == (Method)1) {
        // Cache contains forward:: . Stop searching.
        return nil;
    } else if (meth) {
        return meth;
    }

    return _class_getMethod(cls, sel);
}

↓↓↓

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    Class curClass;
    IMP methodPC = nil;
    Method meth;
    bool triedResolver = NO;

    methodListLock.assertUnlocked();

    // Optimistic cache lookup
    if (behavior & LOOKUP_CACHE) {
        methodPC = _cache_getImp(cls, sel);
        if (methodPC) goto out_nolock;
    }

    // Check for freed class
    if (cls == _class_getFreedObjectClass())
        return (IMP) _freedHandler;

    // 檢查是否已經(jīng)調(diào)用了+initialize方法,如果沒有調(diào)用過initialize方法
    if ((behavior & LOOKUP_INITIALIZE)  &&  !cls->isInitialized()) {
        // 調(diào)用initialize方法
        initializeNonMetaClass (_class_getNonMetaClass(cls, inst));
        // If sel == initialize, initializeNonMetaClass will send +initialize 
        // and then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }
    
    ...
    ...
    ...
}

↓↓↓

void initializeNonMetaClass(Class cls)
{
    ASSERT(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    // 在調(diào)用initialize方法之前,需要先判斷是否調(diào)用了父類的initialize方法
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    
    ...
    ...
    ...
    
#if __OBJC2__
        @try
#endif
        {
            // 開始調(diào)用initialize方法
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             objc_thread_self(), cls->nameForLogging());
            }
        }
#if __OBJC2__
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                             "threw an exception",
                             objc_thread_self(), cls->nameForLogging());
            }
            @throw;
        }
        @finally
#endif
        {
            // Done initializing.
            lockAndFinishInitializing(cls, supercls);
        }
        return;
    }
    
    else if (cls->isInitializing()) {
        ...
        ...
        ...
    }
    
    else if (cls->isInitialized()) {
        
        return;
    }
    
    else {
        // We shouldn't be here. 
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}

↓↓↓

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}

最終來到了消息發(fā)送objc_msgSend函數(shù),給類對象發(fā)送initialize消息,消息發(fā)送機(jī)制通過isa指針找到對應(yīng)的方法與實現(xiàn)。

上面我們說到的如果子類沒有實現(xiàn)initialize方法,那么當(dāng)我們有多個類繼承了父類的時候,父類的initialize方法有可能會調(diào)用多次:

[[Student alloc] init];
[[Student1 alloc] init];

StudentStudent1都繼承自Person,只實現(xiàn)了兩個子類的initialize方法,沒有實現(xiàn)父類的,那么當(dāng)?shù)谝淮握{(diào)用兩個子類的時候會輸出如下:

2020-04-28 22:20:48.690528+0800 分類Category的本質(zhì)[92558:17870282] Person initialize
2020-04-28 22:20:48.690715+0800 分類Category的本質(zhì)[92558:17870282] Person initialize
2020-04-28 22:20:48.690859+0800 分類Category的本質(zhì)[92558:17870282] Person initialize

我們發(fā)現(xiàn)父類的initialize方法調(diào)用了3次,我們通過上面的源碼我們已經(jīng)知道了只有當(dāng)類在第一次接收消息的時候才會被調(diào)用,也就是說每個類只會initialize一次,但是父類的``initialize```為什么會被多次調(diào)用呢?

我們通過底層的邏輯將上面的代碼轉(zhuǎn)化為偽代碼:

// 1. 調(diào)用子類Student之前先判斷父類的initialize
if (Student沒有調(diào)用initialize) {
    if (Student的父類Person沒有調(diào)用initialize) {
        // 1.1 調(diào)用父類Person的initialize
        objc_msgSend)(Person類, SEL_initialize)
    }
}

// 1.2 調(diào)用子類Student的initialize
objc_msgSend)(Student類, SEL_initialize)

// 2. 調(diào)用子類Student1之前先判斷父類的initialize
if (Student1沒有調(diào)用initialize) {
    // **在此,父類已經(jīng)initialize了,所以不再執(zhí)行**
    if (Student1的父類Person沒有調(diào)用initialize) {
        // 2.1 調(diào)用父類Person的initialize
        objc_msgSend)(Person類, SEL_initialize)
    }
}

// 2.2 調(diào)用子類Student1的initialize
objc_msgSend)(Student1類, SEL_initialize)

所以其實相當(dāng)于最后發(fā)送了3次消息:

objc_msgSend)(Person類, SEL_initialize)
objc_msgSend)(Student類, SEL_initialize)
objc_msgSend)(Student1類, SEL_initialize)

消息發(fā)送機(jī)制是通過isa指針尋找到各自的元類對象中的類方法initialize的實現(xiàn),但是兩個子類都沒有實現(xiàn)initialize方法,所以會通過super_class指針找到父類的實現(xiàn),所有最后才會來到父類的initialize方法。

注意:雖然父類被多次調(diào)用initialize方法,但是父類也是只初始化了一次。

4.3 總結(jié)

loadinitialize的區(qū)別,以及它們在category重寫的時候的調(diào)用的次序。

區(qū)別在于調(diào)用方式和調(diào)用時刻

調(diào)用方式:load是根據(jù)函數(shù)地址直接調(diào)用,initialize是通過objc_msgSend消息發(fā)送調(diào)用

調(diào)用時刻:loadruntime加載類、分類的時候調(diào)用(只會調(diào)用1次),initialize是類第一次接收到消息的時候調(diào)用,每一個類只會initialize一次(父類的initialize方法可能會被調(diào)用多次)

調(diào)用順序:先調(diào)用本類的load方法,先編譯那個類,就先調(diào)用load。在調(diào)用load之前會先調(diào)用父類的load方法。分類中load方法不會覆蓋本類的load方法,先編譯的分類優(yōu)先調(diào)用load方法。initialize先初始化父類,之后再初始化子類。如果子類沒有實現(xiàn)+initialize,會調(diào)用父類的+initialize(所以父類的+initialize可能會被調(diào)用多次),如果分類實現(xiàn)了+initialize,就覆蓋類本身的+initialize調(diào)用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,978評論 2 374