iOS 類的加載

iOS 應(yīng)用程序加載 一篇怎棱,我們得知旁赊,app由內(nèi)核引導(dǎo)啟動(dòng),之后交由dyld 主導(dǎo)绞蹦,完成運(yùn)行環(huán)境的初始化力奋,配合ImageLoader將二進(jìn)制文件按格式加載到內(nèi)存,動(dòng)態(tài)鏈接依賴庫(kù)幽七,并由runtime負(fù)責(zé)加載成objc定義的結(jié)構(gòu)景殷,所有初始化工作結(jié)束之后,dyld調(diào)用應(yīng)用程序的main函數(shù)澡屡。

其中猿挚,dyldobjc 互相配合,dyld 加載動(dòng)態(tài)庫(kù)的過(guò)程中初始化 objc驶鹉,objc在初始化的過(guò)程中注冊(cè)回調(diào)函數(shù) _dyld_objc_notify_register绩蜻,通知dyld執(zhí)行 map_imagesload_images室埋,unmap_images 以完成映射办绝、載入等過(guò)程(詳細(xì)過(guò)程參見(jiàn) iOS 應(yīng)用程序加載 )。

這一設(shè)計(jì)是很有必要的姚淆,一來(lái)分工明確孕蝉,各司其職;二來(lái)runtime接管了這部分工作腌逢,可以實(shí)現(xiàn)runtime的動(dòng)態(tài)性降淮。接下來(lái)我們結(jié)合 libobjc 的初始化來(lái)探究 iOS 類的加載過(guò)程。

0 . 從 _objc_init 開(kāi)始

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(); // 讀取影響運(yùn)行時(shí)的環(huán)境變量
    tls_init();     // 處理線程key的綁定
    static_init();  // 運(yùn)行C++靜態(tài)構(gòu)造函數(shù)
    runtime_init(); // runtime運(yùn)行時(shí)環(huán)境初始化,里面主要是:unattachedCategories,allocatedClasses

    exception_init(); // 初始化libobjc的異常處理系統(tǒng)
    cache_init(); // 緩存條件初始化
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

在任何 image 初始化之前搏讶,要先確保 libSystem_initializer 的正常執(zhí)行佳鳖,它是優(yōu)先級(jí)最高的,必須先保證系統(tǒng)庫(kù)的正常初始化媒惕,之后 經(jīng)由 libdispatch_init --> _os_object_init 繼而調(diào)用 _objc_init 系吩。

在 _objc_init 中:

  • environ_init()
    讀取影響運(yùn)行時(shí)的環(huán)境變量.
  • tls_init()
    處理線程key的綁定,處理每個(gè)線程數(shù)據(jù)的析構(gòu)函數(shù).
  • static_init()
    運(yùn)行C ++靜態(tài)構(gòu)造函數(shù)吓笙,在dyld調(diào)用我們的靜態(tài)構(gòu)造函數(shù)之前淑玫,libc 會(huì)調(diào)用 _objc_init(), 因此我們必須自己做.
  • runtime_init()
    runtime運(yùn)行時(shí)環(huán)境初始化,里面主要是:unattachedCategories(尚未附加到本類的分類表),allocatedClasses(用objc_allocateClassPair分配的所有類(和元類)的表) 兩個(gè)表的創(chuàng)建.
  • exception_init()
    初始化libobjc的異常處理系統(tǒng)以監(jiān)測(cè)異常.
  • cache_init()
    緩存條件初始化
  • _imp_implementationWithBlock_init()
    啟動(dòng) trampoline machinery絮蒿。通常這不會(huì)做什么尊搬,因?yàn)樗械某跏蓟际嵌栊缘模菍?duì)于某些進(jìn)程土涝,我們會(huì)迫不及待地加載trampolines dylib佛寿。

_objc_init 完成上述的 init 工作之后,通知 dyld 在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用 map_images, load_images, unmap_image 函數(shù)但壮。

1. map_images

map_images 的工作是:處理由 dyld 映射的給定鏡像冀泻。負(fù)責(zé)管理文件和動(dòng)態(tài)庫(kù)中所有的符號(hào)(class、selector蜡饵、protocol弹渔、category 等)

void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

map_images 函數(shù)中調(diào)用 map_images_nolock,(這里只關(guān)注讀取鏡像的邏輯溯祸,所以做了一些源碼的省略處理肢专。)

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ......

    // 從objc的元數(shù)據(jù)查找所有的鏡像
    ......

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[I]);
        }
    }
}

從主程序的 【machO】 中找到所有的鏡像文件,調(diào)用 _read_images焦辅,執(zhí)行所有的類注冊(cè)和修復(fù)等功能博杖。在所有設(shè)置完成后調(diào)用鏡像加載。那么在加載鏡像之前的操作就聚集在了 _read_images 里面了筷登,這也是我們重點(diǎn)研究的對(duì)象剃根。源碼很長(zhǎng),精簡(jiǎn)如下:

1.1 _read_images

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    ......

    // ① 控制進(jìn)行只做一次加載(建表:未在dyld共享緩存中的已命名類的列表前方,無(wú)論是否實(shí)現(xiàn))
    if (!doneOnce) {
        doneOnce = YES;
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
    }
    ......
    // ② 修復(fù)@selector 引用(修復(fù)預(yù)編譯階段的 `@selector` 的混亂問(wèn)題)
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) continue;

            bool isBundle = hi->isBundle();
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                SEL sel = sel_registerNameNoLock(name, isBundle);
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }
    // ③ 修復(fù)未解析的 future class狈醉, 標(biāo)記 Bundle class
    for (EACH_HEADER) {
        classref_t const *classlist = _getObjc2ClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[I];
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            if (newCls != cls  &&  newCls) {
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }
    
    // ④ 修復(fù)重映射的類;(一些沒(méi)有被鏡像文件加載進(jìn)來(lái)的類) 
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[I]);
            }
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[I]);
            }
        }
    }
#if SUPPORT_FIXUP

    // ⑤ Fix up old objc_msgSend_fixup call sites  

    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;

        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        for (i = 0; i < count; i++) {
            fixupMessageRef(refs+i);
        }
    }
#endif

    // ⑥ readProtocol

    NXMapTable *protocol_map = protocols();
    protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
    for (EACH_HEADER) {
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

    
    // ⑦ Fix up @protocol references

    for (EACH_HEADER) {
        if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
            continue;
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[I]);
        }
    }
    
    // ⑧ load_categories_nolock
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }

    // ⑨ non-lazy 類的加載

    for (EACH_HEADER) {
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            addClassTableEntry(cls);
            realizeClassWithoutSwift(cls, nil);
        }
    }

    //  ⑩ realize future classes (沒(méi)有被處理的類镣丑,)
    // Realize newly-resolved future classes, in case CF manipulates them  實(shí)現(xiàn)新解析的未來(lái)類舔糖,以防CF操作它們
    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            Class cls = resolvedFutureClasses[I];
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class is not allowed to be future");
            }
            realizeClassWithoutSwift(cls, nil);
            cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }
}

簡(jiǎn)化總結(jié)如下:

  • ①:條件控制只做一次:
    建表gdb_objc_realized_classes 娱两,存儲(chǔ)未在dyld共享緩存中的已命名類的列表莺匠,無(wú)論是否實(shí)現(xiàn)
  • ②:修復(fù)@selector 引用:
    修復(fù)預(yù)編譯階段 @selector 的混亂問(wèn)題,我們知道十兢,SEL 是一個(gè)帶地址的字符串趣竣,盡管兩個(gè)方法的名字相同,但方法的地址未必相同旱物。
    如上遥缕,可能多個(gè)框架都會(huì)有init方法,在系統(tǒng)中讀取方法的地址是按照框架在主程中的偏移以及方法在框架內(nèi)的偏移來(lái)定位的宵呛。因此单匣,我們需要對(duì)@selector進(jìn)行適當(dāng)調(diào)整。
  • ③:修復(fù)未解析的 future classes.
    從【MachO】__objc_classlist section中加載類列表,遍歷列表中的類户秤,進(jìn)行readClass码秉,如果readClass的結(jié)果與列表中的類不同,則進(jìn)行修復(fù)操作鸡号,但這一般不會(huì)出現(xiàn)转砖,只有類被移動(dòng)并且沒(méi)有被刪除才會(huì)出現(xiàn)。在readClass中鲸伴,從cls->mangledName()中取到類的名字府蔗,將類的名字與地址關(guān)聯(lián),插入到第①步創(chuàng)建的gdb_objc_realized_classes中汞窗,同時(shí)將該類以及元類插入到allocatedClasses 表姓赤。
    在這一步中,readClass 將類的地址和名字 加載到了內(nèi)存中仲吏。
  • ④:修復(fù)重映射的類:
    如果存在沒(méi)有被鏡像文件加載進(jìn)來(lái)的類模捂,則在此時(shí)進(jìn)行重映射。
  • ⑤:修復(fù)一些舊的 objc_msgSend_fixup 調(diào)用
    主要對(duì)一些舊的消息修復(fù)進(jìn)行強(qiáng)制修復(fù)工作蜘矢,如alloc -> objc_alloc狂男、allocWithZone -> objc_allocWithZone 等。
  • ⑥:readProtocol:
    創(chuàng)建存儲(chǔ)proctol的哈希表品腹,從【MachO】__objc_protolist section中讀取協(xié)議列表岖食,遍歷協(xié)議列表,進(jìn)行readProtocol舞吭,將協(xié)議添加到proctol表泡垃,注冊(cè)到內(nèi)存。
  • ⑦: Fix up @protocol references
    預(yù)先優(yōu)化的鏡像可能已經(jīng)有了正確的protocol引用羡鸥,但是我們并不能確定蔑穴,所以這里進(jìn)行一次修復(fù)工作,以防被引用的協(xié)議被重新分配惧浴。
  • ⑧:load_categories
    加載分類存和,需要注意的是,這里并不會(huì)加載分類衷旅,只有在didInitialAttachCategories賦值為true之后才會(huì)執(zhí)行捐腿,而didInitialAttachCategories賦值為true的過(guò)程是在_dyld_objc_notify_register的調(diào)用完成后的第一個(gè)load_images調(diào)用時(shí)賦值的。
  • ⑨:non-lazy 類的加載
    這里完成的是非懶加載的類的實(shí)現(xiàn)柿顶,也就是實(shí)現(xiàn)了+load方法的類茄袖。
    從【MachO】__objc_nlclslistsection中讀取非懶加載類列表,執(zhí)行addClassTableEntry(cls); 將非懶加載類插入到類表嘁锯,加載到內(nèi)存宪祥。在第③步中聂薪,我們加載到內(nèi)存的類已有了地址和名字,最后執(zhí)行realizeClassWithoutSwift對(duì)類的結(jié)構(gòu)進(jìn)行完善蝗羊。
  • ⑩:realize future classes
    如果存在被處理的 future class 胆建,則需要在這里實(shí)現(xiàn),以防CF操作它們肘交。這里的實(shí)現(xiàn)也是通過(guò)realizeClassWithoutSwift笆载。

_read_images 大致做了上述這些事情,如果只關(guān)注類的加載過(guò)程的話涯呻,可以作如下簡(jiǎn)化理解:

  • 1.建表gdb_objc_realized_classes
    創(chuàng)建一個(gè)不在共享緩存的已命名類的表凉驻,不關(guān)乎這個(gè)類是否實(shí)現(xiàn)。
  • 2.readClass
    從【MachO】__objc_classlist section 中讀取 類的列表數(shù)據(jù)獲取類的地址信息复罐,將類的名字寫(xiě)入涝登,插入gdb_objc_realized_classes表,并且插入allocatedClasses表效诅。自此胀滚,類被載入內(nèi)存,并且有了地址與名字乱投。
  • 3.realizeClassWithoutSwift
    實(shí)現(xiàn)類的細(xì)節(jié)(ro咽笼、rw等處理),保證類的結(jié)構(gòu)的完整性戚炫。

具體的readClassrealizeClassWithoutSwift代碼分析如下:

1.1.1 readClass

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->mangledName();
    // 如果繼承鏈中存在父類缺失或者weak-linked情況剑刑,直接忽略這個(gè)類, return nil
    if (missingWeakSuperclass(cls)) {
      
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->superclass = nil;
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    // 如果這個(gè)類是一個(gè)早先分配的作為將來(lái)要處理的類双肤,那么將objc_class數(shù)據(jù)復(fù)制到future class施掏,保存future的rw數(shù)據(jù)塊
    if (Class newCls = popFutureNamedClass(mangledName)) {
        
        if (newCls->isAnySwift()) {
            _objc_fatal("Can't complete future class request for '%s' "
                        "because the real class is too big.", 
                        cls->nameForLogging());
        }
        
        class_rw_t *rw = newCls->data();
        const class_ro_t *old_ro = rw->ro();
        memcpy(newCls, cls, sizeof(objc_class));
        rw->set_ro((class_ro_t *)newCls->data());
        newCls->setData(rw);
        freeIfMutable((char *)old_ro->name);
        free((void *)old_ro);
        
        addRemappedClass(cls, newCls);
        
        replacing = cls;
        cls = newCls;
    }
    // 如果是預(yù)優(yōu)化的類 并且 不是future class, 則 ASSERT茅糜,
    if (headerIsPreoptimized  &&  !replacing) {
        ASSERT(getClassExceptSomeSwift(mangledName));
    } else {
        // 關(guān)聯(lián)地址與名字 加入gdb_objc_realized_classes表
        addNamedClass(cls, mangledName, replacing);
        // 加入allocatedClasses表
        addClassTableEntry(cls);
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    
    return cls;
}

readClass 處理:

  • 1.從cls->mangledName()獲取類的名字
  • 2.如果繼承鏈中存在父類缺失或者weak-linked情況七芭,直接忽略這個(gè)類(這個(gè)類是不完整的), return nil蔑赘;
  • 3.如果這個(gè)類是一個(gè)早先分配的作為將來(lái)要處理的類狸驳,那么將objc_class數(shù)據(jù)復(fù)制到future class,保存future的rw數(shù)據(jù)塊;
  • 4.如果是預(yù)優(yōu)化的類 并且 不是future class米死, 則 ASSERT锌历;否則
    執(zhí)行addNamedClass關(guān)聯(lián)類的地址與名字 ,插入gdb_objc_realized_classes表.
    執(zhí)行addClassTableEntry 插入 allocatedClasses表;

1.1.1.1 addNamedClass

static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    runtimeLock.assertLocked();
    Class old;
    if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
        inform_duplicate(name, old, cls);

        // getMaybeUnrealizedNonMetaClass uses name lookups.
        // Classes not found by name lookup must be in the
        // secondary meta->nonmeta table.
        addNonMetaClass(cls);
    } else {
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
    ASSERT(!(cls->data()->flags & RO_META));
}

判斷gdb_objc_realized_classes表中是否已經(jīng)存在該名稱的類税灌,如果存在柴钻,插入nonMetaClasses表隐轩。如果不存在插入gdb_objc_realized_classes表。

1.1.1.2 addClassTableEntry

static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked();

    auto &set = objc::allocatedClasses.get();

    ASSERT(set.find(cls) == set.end());

    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

如果該類是在運(yùn)行時(shí)已知的類(如位于共享緩存中物喷,在一個(gè)已加載鏡像的數(shù)據(jù)段中卤材,或者已經(jīng)用obj_allocateClassPair分配),則不需要插入表峦失,否則插入allocatedClasses表扇丛,同時(shí)將類的元類也插入表中。allocatedClasses表是在runtime_init()時(shí)創(chuàng)建的.

至此尉辑,【MachO】中的類已載入內(nèi)存帆精,并且有了名字和地址,但是數(shù)據(jù)還沒(méi)有關(guān)聯(lián)隧魄,而數(shù)據(jù)的關(guān)聯(lián)是在realizeClassWithoutSwift 中進(jìn)行的卓练。

1.1.2 realizeClassWithoutSwift

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;    // 如果類已實(shí)現(xiàn),則直接返回
    ASSERT(cls == remapClass(cls));


    auto ro = (const class_ro_t *)cls->data();   // 從data()中取類的信息
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

 
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    ...... NONPOINTER_ISA的一些處理
#endif

    // Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }

    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls, previously);

    return cls;
}

realizeClassWithoutSwift做簡(jiǎn)化理解如下:

  • cls->data()中讀取類的信息购啄,如果是 future class 襟企,對(duì)rwro賦值狮含;如果是正常的類顽悼,則開(kāi)辟rw空間,對(duì)ro進(jìn)行賦值几迄,并拷貝到rw中蔚龙。

  • 遞歸實(shí)現(xiàn)類的父類以及元類,隨后更新該類的父類和元類以備重新映射映胁。確保繼承鏈以及isa鏈的完整性。

  • ro中賦值一些標(biāo)志到rw中屿愚。

  • 如果父類存在汇跨,則將這個(gè)類鏈接到它的父類的子類列表,否則作為一個(gè)新的根類妆距。

  • 執(zhí)行methodizeClass

1.1.2.1 methodizeClass

static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods()) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name));
        }
        ASSERT(sel_registerName(sel_getName(meth.name)) == meth.name); 
    }
#endif
}

methodizeClass方法中穷遂,主要做了這些事情:

  • ① install 類的方法列表(method_list_t)
  • ② install 類的屬性列表(property_list_t)娱据;
  • ③ install 類的協(xié)議列表(protocol_list_t)蚪黑;
  • ④ Attach categories.
  • install 類的方法列表(method_list_t)
method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

ro中讀取baseMethods(),如果列表有值中剩,通過(guò)prepareMethodLists對(duì)方法列表進(jìn)行預(yù)處理

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle)
{
    runtimeLock.assertLocked();
    if (addedCount == 0) return;
    if (baseMethods) {
        ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore());
    }
    for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[I];
        ASSERT(mlist);

        // Fixup selectors if necessary
        if (!mlist->isFixedUp()) {
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }
    if (cls->isInitialized()) {
        objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount);
        objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount);
        objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount);
    }
}

對(duì)于某些類的方法會(huì)存在 RR/AWZ/Core 這樣的特殊情況忌穿,但是我們不需要做任何處理,因?yàn)槟J(rèn)的 RR/AWZ/Core 是不會(huì)在setInitialized() 方法執(zhí)行之前設(shè)置的结啼。

之后通過(guò)fixupMethodList對(duì)方法列表進(jìn)行排序掠剑,這也是我們?cè)谶M(jìn)行方法查找流程中對(duì)方法列表進(jìn)行二分查找的前提:保證方法列表中的方法是有序的。

static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    runtimeLock.assertLocked();
    ASSERT(!mlist->isFixedUp());
    if (!mlist->isUniqued()) {
        mutex_locker_t lock(selLock);
    
        // Unique selectors in list.
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name);
            meth.name = sel_registerNameNoLock(name, bundleCopy);
        }
    }

    // Sort by selector address.
    if (sort) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(mlist->begin(), mlist->end(), sorter);
    }
    
    // Mark method list as uniqued and sorted
    mlist->setFixedUp();
}

fixup 做了兩件事:UniqueSort.

Unique: 保證方法的唯一性郊愧;
Sort: 對(duì)方法列表進(jìn)行排序:排序的過(guò)程是根據(jù)方法的name進(jìn)行排序朴译;

struct SortBySELAddress :
      public std::binary_function<const method_t&,
                                   const method_t&, bool>
   {
       bool operator() (const method_t& lhs,
                        const method_t& rhs)
       { return lhs.name < rhs.name; }
   };

最后標(biāo)記方法列表為已修復(fù)狀態(tài)井佑,后續(xù)不需再進(jìn)行fixup了。

prepareMethodLists的工作完成后眠寿,判斷rwe是否存在躬翁,如果存在,則rwe->methods.attachLists盯拱。而此時(shí)rwe是不存在的盒发,這里補(bǔ)充說(shuō)明一下:

Tips:

iOS 類的結(jié)構(gòu)分析 這篇文章中,我們已知:類在編譯期時(shí)狡逢,類的一些數(shù)據(jù)信息保存在 ro 中宁舰,包含了類的名稱,方法甚侣,協(xié)議明吩,實(shí)例變量等 編譯期確定 的信息。當(dāng)類被Runtime加載之后殷费,runtime會(huì)為它分配額外的用于 讀取/寫(xiě)入rw印荔。

ro 是只讀的,存放的是 編譯期間就確定 的字段信息详羡;而 rw 是在 runtime 時(shí)才創(chuàng)建的仍律,它會(huì)先將 ro 的內(nèi)容拷貝一份,再將類的分類实柠、屬性水泉、方法、協(xié)議等信息添加進(jìn)去窒盐。

而對(duì)于存在動(dòng)態(tài)更改行為的類草则,會(huì)將這部分動(dòng)態(tài)的內(nèi)容提取到 rwe 中。

到目前為止蟹漓,類的加載過(guò)程炕横,對(duì)于 ro, rw 均已賦值完畢,而 rwe 的賦值是要在類存在動(dòng)態(tài)更改行為時(shí)觸發(fā)的葡粒。此時(shí)rwe還不存在份殿,所以并不需要rwe->methods.attachLists。那么rwe是何時(shí)被賦值的呢嗽交?

class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>();
        } else {
            return extAlloc(v.get<const class_ro_t *>());
        }
    }

rwe 的創(chuàng)建是通過(guò)extAllocIfNeeded卿嘲。搜索extAllocIfNeeded可得到下面的結(jié)果。

上午11.51.25.png

分別對(duì)應(yīng)了:attachCategories夫壁, demangledName拾枣,class_setVersionaddMethod掌唾,addMethods放前,class_addProtocol忿磅,_class_addProperty糯彬,objc_duplicateClass 凭语。無(wú)一不是動(dòng)態(tài)修改的行為。

如果rwe已創(chuàng)建撩扒,且方法列表有值似扔,attachLists(&list, 1)

  • install 類的屬性列表(property_list_t)
property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

如果rwe已創(chuàng)建,且屬性列表有值搓谆,attachLists(&proplist, 1)

  • install 類的協(xié)議列表(protocol_list_t)
protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

如果rwe已創(chuàng)建炒辉,且協(xié)議列表有值,attachLists(&protolist, 1)

可見(jiàn)只要滿足條件泉手,都會(huì)執(zhí)行attachLists函數(shù)

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            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]));
        }
    }

attachLists函數(shù)區(qū)分了三種情況黔寇,我們按照?qǐng)?zhí)行流程分析如下:

  • 【第一種】0 lists --> 1 list
    從無(wú)到有的過(guò)程,lists中還未有任何內(nèi)容斩萌,此時(shí)直接將 new list 內(nèi)容賦值過(guò)來(lái)即可缝裤。
  • 【第二種】1 list -> many lists
    此時(shí) lists 中已有【第一種】 中賦值的數(shù)組。要添加的大小與原大小相加計(jì)算出所需容量大小颊郎,根據(jù)容量開(kāi)辟 array()空間并賦值:將舊的內(nèi)容(oldList)放在數(shù)組末尾憋飞,新的內(nèi)容(addedLists)從lists的首地址開(kāi)始,大小為addedCount 個(gè)空間姆吭,拷貝到lists中榛做,這樣,舊數(shù)據(jù)放最后内狸,新數(shù)據(jù)整體放在前检眯。
  • 【第三種】many lists -> many lists
    在 【第二種】 中,array() 已賦值昆淡,所以當(dāng)有新的數(shù)組 attach 時(shí)锰瘸,重新開(kāi)辟array()空間,大小依然是舊數(shù)組容量與新數(shù)據(jù)容量之和瘪撇。將舊數(shù)據(jù)移動(dòng)到最后获茬,新數(shù)據(jù)整體拷貝到舊數(shù)據(jù)的前面。
  • Attach categories
// Attach categories.
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

Attach categories 是添加分類的過(guò)程倔既,此部分內(nèi)容放在 iOS 分類詳解 中探討恕曲。

至此為止,類的加載過(guò)程(除分類以外)大致完成渤涌,通過(guò)上面這些步驟佩谣,類的結(jié)構(gòu)已大致完備。
通過(guò) readClass 將類的地址與名字進(jìn)行關(guān)聯(lián)并且載入內(nèi)存实蓬;
通過(guò) realizeClassWithoutSwift 對(duì)類的 ro茸俭,rw 完成賦值吊履;
通過(guò) methodizeClass 對(duì)類的方法、屬性调鬓、協(xié)議進(jìn)行完備艇炎,并且完成分類的加載。
當(dāng)存在動(dòng)態(tài)修改等行為時(shí)腾窝,也會(huì)將類的動(dòng)態(tài)修改部分存入rwe缀踪。

這些都是在 map_images 中完成的。下面我們接著研究load_images虹脯。

2. load_images

當(dāng)鏡像文件映射完畢后驴娃,會(huì)繼而執(zhí)行 load_images ,處理在dyld中已映射完畢的鏡像的 +load 方法.

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }
    if (!hasLoadMethods((const headerType *)mh)) return;
    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

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

首先循集,如果 libobjc.A.dylib 動(dòng)態(tài)庫(kù)的鏡像文件映射完畢且首次執(zhí)行load_images的時(shí)候唇敞,會(huì)調(diào)用loadAllCategories() 函數(shù),完成所有分類的加載咒彤。(分類相關(guān)的內(nèi)容本章暫不探討疆柔,放在 iOS分類詳解 里統(tǒng)一探討);

系統(tǒng)依賴的動(dòng)態(tài)庫(kù)的很多蔼紧,每一個(gè)動(dòng)態(tài)庫(kù)在執(zhí)行load_images 函數(shù)的時(shí)候都會(huì)判斷當(dāng)前動(dòng)態(tài)庫(kù)的【MachO】中__objc_nlclslist__objc_nlcatlist 列表是否有內(nèi)容婆硬。沒(méi)有直接return。如果有奸例,執(zhí)行prepare_load_methods

2.1 prepare_load_methods

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, I;
    runtimeLock.assertLocked();
    // 類的load
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);  
    for (i = 0; i < count; i++) {  
        schedule_class_load(remapClass(classlist[i]));
    }
    // 分類的load
    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);
    }
}

prepare_load_methods 做了兩件事:

    1. 從當(dāng)前動(dòng)態(tài)庫(kù)的【MachO】header中讀取__objc_nlclslist彬犯,執(zhí)行 schedule_class_load;
    1. 從當(dāng)前動(dòng)態(tài)庫(kù)的【MachO】header中讀取__objc_nlcatlist,隨后迫使主類實(shí)現(xiàn)查吊,繼而執(zhí)行add_category_to_loadable_list谐区,將待執(zhí)行+load的分類添加到loadable_categories表中。

2.1.1 schedule_class_load

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images should realize
    // 如已執(zhí)行add_class_to_loadable_list 則不需繼續(xù)添加
    if (cls->data()->flags & RW_LOADED) return; 

    // 確保父類先執(zhí)行
    schedule_class_load(cls->superclass);  
    // 插表
    add_class_to_loadable_list(cls);
    // 設(shè)置標(biāo)記:cls已執(zhí)行add_class_to_loadable_list
    cls->setInfo(RW_LOADED);    
}

對(duì)于不存在的類或者未實(shí)現(xiàn)的類進(jìn)行攔截逻卖,同時(shí)對(duì)于已執(zhí)行過(guò)add_class_to_loadable_list 函數(shù)的類也進(jìn)行過(guò)濾宋列。當(dāng)滿足條件向下執(zhí)行的時(shí)候,要先確保該類的父類先于該類執(zhí)行+load(如果父類實(shí)現(xiàn)了+load)评也,隨后執(zhí)行add_class_to_loadable_list 插表操作炼杖,并且標(biāo)記cls為已完成插表操作。

2.1.1.1 add_class_to_loadable_list

void add_class_to_loadable_list(Class cls)
{
    IMP method;
    loadMethodLock.assertLocked();
    // 取到要存入loadable_classes表的類 +load方法的 IMP
    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());
    }

    // 空表或者達(dá)最大容量盗迟,則開(kāi)辟或擴(kuò)容 坤邪,每個(gè)動(dòng)態(tài)庫(kù)中的所有+load方法會(huì)存儲(chǔ)在同一個(gè)表中
    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++;
}

通過(guò)getLoadMethod()cls中的ro()->baseMethods(),遍歷查詢load方法罚缕,找到則返回方法的 IMP艇纺,執(zhí)行插表操作,未找到直接return.

每個(gè)動(dòng)態(tài)庫(kù)中需要執(zhí)行+load的類會(huì)存入到同一張表loadable_classes,根據(jù)已占用空間和開(kāi)辟空間進(jìn)行對(duì)比黔衡,以判斷是夠需要擴(kuò)容重新開(kāi)辟空間蚓聘。

2.1.2 add_category_to_loadable_list

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++;
}

分類和主類的插表操作大致相同的,通過(guò)_category_getLoadMethod 從分類的cat->classMethods中查找+load方法盟劫,找到則插入loadable_categories表夜牡。

這里需要注意的是在執(zhí)行add_category_to_loadable_list 操作之前,先要完成主類的實(shí)現(xiàn)realizeClassWithoutSwift捞高。

2.2 call_load_methods()

prepare_load_methods 函數(shù)氯材,將類的+load渣锦,父類的+load硝岗,分類的+load,都以準(zhǔn)備完畢袋毙,接下來(lái)就要執(zhí)行call_load_methods()型檀。

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) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        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;
}

進(jìn)入do....while循環(huán),從先前存入的表數(shù)據(jù)loadable_classes中取出可執(zhí)行+load方法的類數(shù)據(jù)听盖,進(jìn)行消息發(fā)送胀溺,執(zhí)行+load方法,從loadable_categories表中取出可執(zhí)行+load方法的分類數(shù)據(jù)皆看,進(jìn)行消息發(fā)送仓坞,執(zhí)行+load方法。

這樣腰吟,+load 方法得以被調(diào)用无埃。

3. 總結(jié)

本篇文章主要是從_objc_init開(kāi)始,結(jié)合map_images毛雇、load_images 來(lái)探究類的加載過(guò)程嫉称。 如果你對(duì) dyld 還不是很熟悉,建議先看一看我的另一篇文章 iOS 應(yīng)用程序加載 灵疮,再來(lái)看這篇文章的時(shí)候會(huì)更清晰些织阅。

盡管類的加載細(xì)節(jié)是相同的,但加載的【時(shí)機(jī)】卻不盡相同震捣,為此有必要在這里也補(bǔ)充一下 懶加載類 與 非懶加載類荔棉。

補(bǔ)充:懶加載類與非懶加載類

非懶加載類:隨著程序啟動(dòng),在map_iamges階段既完成加載的類.
懶加載類:當(dāng)?shù)谝淮问褂妙悤r(shí)才加載的類(通常為第一次發(fā)送消息時(shí))蒿赢。

我們可以通過(guò)實(shí)現(xiàn)+load方法來(lái)迫使類提前加載润樱,即非懶加載類。

對(duì)于幾種實(shí)現(xiàn)+load方法的情況的堆棧情況如下圖:

  • 主類實(shí)現(xiàn)+load

當(dāng)主類實(shí)現(xiàn)+load 的時(shí)候诉植,在_read_images的第⑨步中就會(huì)實(shí)現(xiàn)類祥国。

  • 子類實(shí)現(xiàn)+load

當(dāng)子類實(shí)現(xiàn)+load時(shí),上圖堆棧中會(huì)執(zhí)行兩次realizeClassWithoutSwift,如果前面探討的內(nèi)容你有仔細(xì)看的話舌稀,應(yīng)該記得啊犬,在realizeClassWithoutSwift方法內(nèi),為了保證繼承鏈的完整性壁查,會(huì)遞歸調(diào)用realizeClassWithoutSwift觉至,去實(shí)現(xiàn)父類。

  • 分類實(shí)現(xiàn)+load

分類實(shí)現(xiàn)+load 時(shí)睡腿,在prepare_load_methods時(shí)语御,會(huì)先調(diào)用realizeClassWithoutSwift,確保本類先實(shí)現(xiàn)席怪。所以堆棧信息是這樣的应闯。

  • 均未實(shí)現(xiàn)+load

當(dāng)上述+load均未實(shí)現(xiàn)的時(shí)候,類的實(shí)現(xiàn)將直到第一次使用它時(shí)挂捻,這里的情況一般就是發(fā)送消息的時(shí)候了碉纺。在消息的慢速查找流程中,如果類還沒(méi)有實(shí)現(xiàn)刻撒,為了保證程序的正常運(yùn)行骨田,會(huì)在這里實(shí)現(xiàn)類,這也是符合正常邏輯的声怔。

才疏學(xué)淺态贤,文章如有誤導(dǎo)之處,還請(qǐng)指正醋火,若覺(jué)得對(duì)你有所幫助悠汽,也可以給我一個(gè)贊?? 感謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胎撇,一起剝皮案震驚了整個(gè)濱河市介粘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晚树,老刑警劉巖姻采,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異爵憎,居然都是意外死亡慨亲,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門宝鼓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)刑棵,“玉大人,你說(shuō)我怎么就攤上這事愚铡◎惹” “怎么了胡陪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)碍舍。 經(jīng)常有香客問(wèn)我柠座,道長(zhǎng),這世上最難降的妖魔是什么片橡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任妈经,我火速辦了婚禮,結(jié)果婚禮上捧书,老公的妹妹穿的比我還像新娘吹泡。我一直安慰自己,他們只是感情好经瓷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布爆哑。 她就那樣靜靜地躺著,像睡著了一般了嚎。 火紅的嫁衣襯著肌膚如雪泪漂。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天歪泳,我揣著相機(jī)與錄音,去河邊找鬼露筒。 笑死呐伞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的慎式。 我是一名探鬼主播伶氢,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瘪吏!你這毒婦竟也來(lái)了癣防?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掌眠,失蹤者是張志新(化名)和其女友劉穎蕾盯,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蓝丙,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡级遭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了渺尘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挫鸽。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鸥跟,靈堂內(nèi)的尸體忽然破棺而出丢郊,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布枫匾,位于F島的核電站迅诬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏婿牍。R本人自食惡果不足惜侈贷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望等脂。 院中可真熱鬧俏蛮,春花似錦、人聲如沸上遥。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)粉楚。三九已至辣恋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間模软,已是汗流浹背伟骨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留燃异,地道東北人携狭。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像回俐,于是被迫代替她去往敵國(guó)和親逛腿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355