格物致知類與對象

轉載自https://mp.weixin.qq.com/s/B4Z-7YQ8CMhZ0kCrxe3XAg



欲誠其意者,先致其知;致知在格物。物格而后知至,知至而后意誠。現代漢語詞典中將格物致知解釋為: "推究事物的原理,從而獲得知識"。

在編程中我們接觸最多的也是最基本的就是類和對象,當我們在創建類或者實例化對象時,是否考慮過類和對象到底是什么?理解其本質才能真正掌握一門語言。本文將從結構類型角度并結合實際應用探討下Objective-C的類和對象。

在Objective-C中,對象是廣義的概念,類也是對象,所以嚴謹的說法應該是類對象和實例對象。既然實例對象所屬的類稱為類對象,那類對象有所屬的類嗎?有,稱之為元類(Metaclass)。

類對象(Class)

類對象是由程序員定義并在運行時由編譯器創建的,它沒有自己的實例變量,這里需要注意的是類的成員變量和實例方法列表是屬于實例對象的,但其存儲于類對象當中的。我們在/usr/include/objc/objc.h下看看Class的定義:

/// An opaque type that represents an Objective-C class.

typedef struct objc_class *Class;

可以看到類是由Class類型來表示的,它是一個objc_class結構類型的指針。我們接著來看objc_class結構體的定義:

struct objc_class {

? ? Class? ? ? ? ? ? ? ? ? ? ? isa;? ? ? ? ? ?// 指向所屬類的指針(_Nonnull)

? ? Class? ? ? ? ? ? ? ? ? ? ? super_class;? ?// 父類? ? ? ? ? ? ? ? ??

? ? const char? ? ? ? ? ? ? ? *name;? ? ? ? ? // 類名(_Nonnull)

? ? long? ? ? ? ? ? ? ? ? ? ? ?version;? ? ? ?// 類的版本信息(默認為0)

? ? long? ? ? ? ? ? ? ? ? ? ? ?info;? ? ? ? ? // 類信息(供運行期使用的一些位標識)

? ? long? ? ? ? ? ? ? ? ? ? ? ?instance_size; // 該類的實例變量大小

? ? struct objc_ivar_list? ? ?*ivars;? ? ? ? ?// 該類的成員變量鏈表

? ? struct objc_method_list * *methodLists;? ?// 方法定義的鏈表

? ? struct objc_cache? ? ? ? ?*cache;? ? ? ? ?// 方法緩存

? ? struct objc_protocol_list *protocols;? ? ?// 協議鏈表

};

isa指針是和Class同類型的objc_class結構指針,類對象的指針指向其所屬的類,即元類。元類中存儲著類對象的類方法,當訪問某個類的類方法時會通過該isa指針從元類中尋找方法對應的函數指針

super_class為該類所繼承的父類對象,如果該類已經是最頂層的根類(如NSObject或NSProxy), 則 super_class為NULL

ivars是一個指向objc_ivar_list類型的指針,用來存儲每一個實例變量的地址

info為運行期使用的一些位標識,比如:

CLS_CLASS (0x1L)表示該類為普通類, CLS_META (0x2L)則表示該類為元類

methodLists用來存放方法列表,根據info中的標識信息,當該類為普通類時,存儲的方法為實例方法;如果是元類則存儲的類方法

cache用于緩存最近使用的方法。系統在調用方法時會先去cache中查找,在沒有查找到時才會去methodLists中遍歷獲取需要的方法

實例對象

實例對象是我們對類對象alloc或者new操作時所創建的,在這個過程中會拷貝實例所屬的類的成員變量,但并不拷貝類定義的方法。調用實例方法時,系統會根據實例的isa指針去類的方法列表及父類的方法列表中尋找與消息對應的selector指向的方法。同樣的,我們也來看下其定義:

/// Represents an instance of a class.

struct objc_object {

? ? Class _Nonnull isa? OBJC_ISA_AVAILABILITY;

};

可以看到,這個結構體只有一個isa變量,指向實例對象所屬的類。任何帶有以指針開始并指向類結構的結構都可以被視作objc_object, 對象最重要的特點是可以給其發送消息. NSObject類的alloc和allocWithZone:方法使用函數class_createInstance來創建objc_object數據結構。

另外我們常見的id類型,它是一個objc_object結構類型的指針。該類型的對象可以轉換為任何一種對象,類似于C語言中void *指針類型的作用。其定義如下所示:

/// A pointer to an instance of a class.

typedef struct objc_object *id;

元類對象(Metaclass)

元類就是類對象的類,每個類都有自己的元類,也就是objc_class結構體里面isa指針所指向的類. Objective-C的類方法是使用元類的根本原因,因為其中存儲著對應的類對象調用的方法即類方法。

類存儲示意圖.png

所以由上圖可以看到,在給實例對象或類對象發送消息時,尋找方法列表的規則為:

當發送消息給實例對象時,消息是在尋找這個對象的類的方法列表(實例方法)

當發送消息給類對象時,消息是在尋找這個類的元類的方法列表(類方法)

元類,就像之前的類一樣,它也是一個對象,也可以調用它的方法。所以這就意味著它必須也有一個類。所有的元類都使用根元類作為他們的類。比如所有NSObject的子類的元類都會以NSObject的元類作為他們的類。

根據這個規則,所有的元類使用根元類作為他們的類,根元類的元類則就是它自己。也就是說基類的元類的isa指針指向他自己。

我們可以通過代碼來實際驗證下, Runtime提供了object_getClass函數:

Class _Nullable object_getClass(id _Nullable obj)

來獲取對象所屬的類,看到這個函數你也許會好奇這個和我們平常接觸的NSObject的[obj class]有什么區別?

// NSObject.h

- (Class)class;

+ (Class)class;

我們繼續從runtime的源碼里面尋找答案:

Class object_getClass(id obj) {

? ? return _object_getClass(obj);

}

object_getClass實際調用的是_object_getClass函數,我們接著看其實現:

static inline Class _object_getClass(id obj) {

? ? #if SUPPORT_TAGGED_POINTERS

? ? if (OBJ_IS_TAGGED_PTR(obj)){

? ? ? ? uint8_t slotNumber = ((uint8_t)(uint64_t) obj) & 0x0F;

? ? ? ? Class isa = _objc_tagged_isa_table[slotNumber];

? ? ? ? return isa;

? ? }

? ? #endif

? ? ? ? if (obj) return obj->isa;

? ? ? ? else return Nil;

}

顯然_object_getClass函數就是返回對象的isa指針,也就是返回該對象所指向的所屬類。我們接著看[obj class]的具體實現(包括類方法和實例方法兩種):

+ (Class)class {

? ? return self; // 返回自身指針

}

- (Class)class {

? ? return object_getClass(self); // 調用'object_getClass'返回isa指針

}

從代碼中可以看出+ (Class)class返回的是其本身,而- (Class)class則等價于object_getClass函數。

我們來寫個測試代碼,看看這些函數的實際返回值是否和上面的所述保持一致,比如我們有個RJObject繼承與NSObject:

RJObject *obj = [RJObject new];

Class clsClass0 = [RJObject class];? ? ?// 返回RJObject類對象的本身的地址

Class objClass0 = [obj class];? ? ? ? ? // isa指向的RJObject類對象的地址

Class ogcClass0 = object_getClass(obj); // isa指向的RJObject類對象的地址

NSLog(@"clsClass0 -> %p", clsClass0); // -> 0x10fb22068

NSLog(@"objClass0 -> %p", objClass0); // -> 0x10fb22068

NSLog(@"ogcClass0 -> %p", ogcClass0); // -> 0x10fb22068

打印結果可以看出,當obj為實例變量時, object_getClass(obj)與[obj class]輸出結果一致,均返回該對象的isa指針,即指向RJObject類對象的指針。而[RJObject class]則直接返回RJObject類對象本身的地址,所以與前面兩者返回的地址相同。

// 'objClass0'為RJObject類對象(RJObject Class)

Class objClass1 = [objClass0 class];? ? ? ? ? // 返回RJObject類對象本身的地址

Class ogcClass1 = object_getClass(objClass0); // isa指向的RJObject元類的地址

NSLog(@"objClass1 -> %p", objClass1); // -> 0x10fb22068

NSLog(@"ogcClass1 -> %p", ogcClass1); // -> 0x10fb22040

此時objClass0為RJObject的類對象,所以類方法[objClass0 class]返回的objClass1為self, 即RJObject類對象本身的地址,故結果與上面的地址相同。而ogcClass1返回的為RJObject元類的地址。

// 'ogcClass1'為RJObject的元類(RJObject metaClass)

Class objClass2 = [ogcClass1 class];? ? ? ? ? // 返回RJObject元類對象的本身的地址

Class ogcClass2 = object_getClass(ogcClass1); // isa指向的RJObject元類的元類地址

NSLog(@"objClass2 -> %p", objClass2); // -> 0x10fb22040

NSLog(@"ogcClass2 -> %p", ogcClass2); // -> 0x110ad9e58

同理,這邊ogcClass2為RJObject元類的元類的地址,那問題來了,某個類它的元類的元類的是什么類呢?這樣下去豈不是元類無窮盡了?擒賊先擒王,我們先來看看根類NSObject的元類和它元類的元類分別是什么:

Class rootMetaCls0 = object_getClass([NSObject class]); // 返回NSObject元類(根元類)的地址

Class rootMetaCls1 = object_getClass(rootMetaCls0);? ? ?// 返回NSObject元類(根元類)的元類地址

NSLog(@"rootMetaCls0 -> %p", rootMetaCls0); // -> 0x110ad9e58

NSLog(@"rootMetaCls1 -> %p", rootMetaCls1); // -> 0x110ad9e58

看到結果就一目了然了,根元類的isa指針指向自己,也就是根元類的元類即其本身。另外,可以發現ogcClass2的地址和根元類isa的地址相同,說明任意元類的isa指針都指向根元類,這樣就構成一個封閉的循環。

另外,我們可以通過class_isMetaClass函數來判斷某個類是否是元類,比如:

NSLog(@"ogcClass0 is metaClass: %@", class_isMetaClass(objClass0) ? @"YES" : @"NO");

NSLog(@"ogcClass1 is metaClass: %@", class_isMetaClass(ogcClass1) ? @"YES" : @"NO");

輸出結果為:

LearningClass[58516:3424874] ogcClass0 is metaClass: NO

LearningClass[58516:3424874] ogcClass1 is metaClass: YES

日志表明ogcClass0為類對象,而ogcClass1則為元類對象,這與我們上面的分析是一致的。

類和元類的父類指向情況也可以參照上面的步驟,通過class_getSuperclass或者[obj superClass]函數來獲取分析,這邊就不再贅述了。

除了isa聲明了實例與所屬類的關系,還有superClass表明了類和元類的繼承關系,類對象和元類對象都有父類。同樣,為了形成一個閉環,根類的父類為nil, 根元類的父類則指向其根類。我們可以通過一張示意圖來看下三種對象之間的連接關系:

類關系示意圖

總結一下實例對象,類對象以及元類對象之間的isa指向和繼承關系的規則為:

規則一:?實例對象的isa指向該類,類的isa指向元類(metaClass)

規則二:類的superClass指向其父類,如果該類為根類則值為nil

規則三:元類的isa指向根元類,如果該元類是根元類則指向自身

規則四:?元類的superClass指向父元類,若根元類則指向該根類

動態創建類

Objective-C作為動態語言的優勢在于它能在運行時創建類和對象,并向類中增加方法和實例變量。具體示例如下:

Class newClass = objc_allocateClassPair([NSObject class], "RJInfo", 0);

if (!class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:")) {

? ? NSLog(@"Add method 'report' failed!");

}

if (!class_addIvar(newClass, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *))) {

? ? NSLog(@"Add ivar '_name' failed!");

}

objc_registerClassPair(newClass);

上面代碼創建了一個RJInfo的類,并分別添加了_name成員變量和report實例方法。需要注意的是,方法和變量必須在objc_allocateClassPair和objc_registerClassPair之間進行添加。所以,在運行時創建一個類只需要3個步驟:

首先是調用objc_allocateClassPair為新建的類分配內存,三個參數依次為newClass的父類,newClass的名稱,第三個參數通常為0, 從這個函數名字可以看到新建的類是一個pair, 也就是成對的類,那為什么新建一個類會出現一對類呢?是的,元類!類和元類是成對出現的,每個類都有自己所屬的元類,所以新建一個類需要同時創建類以及它的元類。

然后就可以向newClass中添加變量及方法了,注意若要添加類方法,需用objc_getClass(newClass)獲取元類,然后向元類中添加類方法。因為示例方法是存儲在類中的,而類方法則是存儲在元類中。最后必須把newClass注冊到運行時系統,否則系統是不能識別這個類的。

上面的代碼中添加了一個成員變量_name, 我們來看下實際應用中如何獲取和使用這個變量:

unsigned int varCount;

Ivar *varList = class_copyIvarList(newClass, &varCount);

for (int i = 0; i < varCount; i++) {

? ? NSLog(@"var name: %s", ivar_getName(varList[i]));

}

free(varList);

id infoInstance = [[newClass alloc] init];

Ivar nameIvar? ?= class_getInstanceVariable(newClass, "_name");

object_setIvar(infoInstance, nameIvar, @"Ryan Jin");

NSLog(@"var value: %@",object_getIvar(infoInstance, nameIvar));

我們可以通過class_copyIvarList來查看實例變量列表,注意獲取的varList列表需要調用free()函數釋放。當前只添加了一個變量,所以varCount為1, 在調用ivar_getName打印出變量的名字。如若對_name賦值,則需要先實例化newClass對象,并取出對象的該變量后調用object_setIvar進行賦值操作。示例代碼的輸出結果為:

LearningClass[58516:3424874] var name: _name

LearningClass[58516:3424874] var value: Ryan Jin

好了,驗證完變量的添加,繼續看方法的添加和使用。上文的示例中添加了report方法,但僅僅是做了SEL方法名的聲明,我們來接著完成其IMP所指向函數ReportFunction的具體實現:

void ReportFunction(id self, SEL _cmd) {

? ? Class currentClass = [self class];

? ? Class metaClass? ? = objc_getMetaClass(class_getName(currentClass));


? ? NSLog(@"Class is %@, and super - %@.", currentClass, [self superclass]);

? ? NSLog(@"%@'s meta class is %p.", NSStringFromClass(currentClass), metaClass);

}

在函數實現中我們打印了類,父類以及元類的相關信息,為了運行ReportFunction, 我們需要創建一個動態實例來創建類的實例對象并調用report方法:

id instanceOfNewClass = [[newClass alloc] init];


[instanceOfNewClass performSelector:@selector(report)];

輸出結果:

LearningClass[58516:3424874] Class is RJInfo, and super - NSObject.

LearningClass[58516:3424874] RJInfo's meta class is 0x600000253920.

除了給類添加方法,我們同樣也可以動態修改已存在方法的實現,比如:

class_replaceMethod(newClass, @selector(report), (IMP)ReportReplacedFunction, "v@:");

這樣就將report這個SEL所指向的IMP實現換成了ReportReplacedFunction. 如果類中不存在name指定的方法, class_replaceMethod則類似于class_addMethod函數一樣會添加方法;如果類中已存在name指定的方法,則類似于method_setImplementation一樣替代原方法的實現。

看到class_replaceMethod的解釋,相信你已經發現了,這不就是Method Swizzling嗎?沒錯,所謂的黑魔法,其實就是底層原理的應用而已!

本質探究

知其然亦知其所以然才是獲取知識的正確方式,理解了類和對象的本質后,我們來看看格物致知后的理論可以引導出哪些應用和認識:

屬性(Property)

在Objective-C中,屬性和成員變量是不同的。那么,屬性的本質是什么?它和成員變量之間有什么區別?簡單來說屬性是添加了存取方法的成員變量,也就是:

@property = ivar + getter + setter;

因此,我們每定義一個@property都會添加對應的ivar, getter和setter到類結構體objc_class中。具體來說,系統會在objc_ivar_list中添加一個成員變量的描述,然后在methodLists中分別添加setter和getter方法的描述。

父類對象

我們直接來看一個面試題, Father繼承與NSObject, Son則繼承于Father類,分別調用[self class]和[super class], 輸出結果是?

@implementation Son : Father

- (instancetype)init

{

? ? self = [super init];

? ? if (self) {

? ? ? ? NSLog(@"%@", NSStringFromClass([self class]));

? ? ? ? NSLog(@"%@", NSStringFromClass([super class]));

? ? }

? ? return self;

}

@end

輸出結果都為Son, 為什么[super class]的結果不是Father? 我們簡單分析下就明白了。實例對象的方法列表是存放在isa所指向的類對象中的,所以調用[self class]的時候會去self的isa所指向的Son類對象中尋找該方法,在沒有重載[obj class]的情況下, Son類對象是沒有這個方法的,此時會接著在父類對象的方法列表中查找,最終會發現NSObject存儲了該方法,所以[self class]會返回實例對象(self)所屬的Son這個類對象

而[super class]則指定從父類Father的方法列表開始去查找- (Class)class這個方法,顯然Father沒有這個方法,最終還是要查找到NSObject類對象的方法列表中,需要注意的是不管是[self class]還是[super class], 它們都是調用的實例對象的- (Class)class方法,雖然其指向的類對象不同,但實例對象都是self本身,再強調下區分開實例對象和類對象!因而返回的也是當前self的isa所指向的Son類。

其實super是objc_super類型的結構體,它包含了當前的實例對象self以及父類的類對象。更詳細的解答可以參考@iOS程序犭袁的博文。

除了用super來指向父類外,我們還可以用isKindOfClass和isMemberOfClass來判斷對象的繼承關系。這兩個函數有什么區別呢?同樣,先來看一個測試題:

BOOL r1 = [[NSObject class] isKindOfClass:[NSObject class]]; // -> YES

BOOL r2 = [[RJObject class] isKindOfClass:[RJObject class]]; // -> NO

BOOL r3 = [[NSObject class] isMemberOfClass:[NSObject class]]; // -> NO

BOOL r4 = [[RJObject class] isMemberOfClass:[RJObject class]]; // -> NO

為什么只有r1是YES? 實際上isKindOfClass是判斷對象是否為Class的實例或子類,而isMemberOfClass則是判斷對象是否為Class的實例。還是不明白?沒關系,我們直接來看看這兩個函數的源碼實現,看看它們本質上是以什么作為判斷標準的:

+ (BOOL)isKindOfClass:(Class)cls

{

? ? for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {

? ? ? ? if (tcls == cls) return YES;

? ? }

? ? return NO;

}

- (BOOL)isKindOfClass:(Class)cls?

{

? ? for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {

? ? ? ? if (tcls == cls) return YES;

? ? }

? ? return NO;

}

+ (BOOL)isMemberOfClass:(Class)cls {

? ? return object_getClass((id)self) == cls;??

}

- (BOOL)isMemberOfClass:(Class)cls {

? ? return [self class] == cls;?

}

注意上面的題目是調用的類方法,所以我們分析下類方法的實現,至于實例方法也是類似的。可以看到isMemberOfClass的判斷是先調用object_getClass獲取isa所指向的歸屬類,也就是元類,然后直接判斷cls是否就是被比較的對象的元類。而[NSObject class]的元類是根元類,顯然不等于[NSObject class]本身,所以r3返回NO, r4也是同理。

而isKindOfClass也是先獲取當前對象的元類,但是會循環獲取其isa所指向類的父類進行比較,只要該元類或者元類的父類與cls相對則返回YES. RJObject的元類,以及父元類(最終指向根元類)都不等于RJObject對象,所以r2返回NO. 那為什么r1返回YES呢?還記得上文所說的閉環嗎?根元類的父類指向根類本身!顯然, r1符合了isKindOfClass的判斷標準。

學以致用

到這里理論部分就結束了。那么,問題來了,理解了類和對象的本質原理有什么實際應用價值嗎?可以讓我們更優雅的解決項目中遇到的問題和需求嗎?Talk is cheap, show me the code:

比如App常見的記錄用戶行為的數據統計需求,俗稱埋點。具體來說假設我們需要記錄用戶對按鈕的點擊。通常情況下,我們會在按鈕的點擊事件里面直接加上數據統計的代碼,但這樣做的問題在于會對業務代碼進行侵入,且統計的代碼散落各處,難以維護。

當然,我們還可以創建一個UIButton的子類,在子類中重載點擊事件的響應函數,并在其中加上統計數據部分的代碼:

-(void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event

這樣做是可以的,但是現有工程中所有需要支持數據統計的按鈕都必須替換成該子類,而且如果哪天不需要支持埋點功能了并需要遷移復用業務代碼,那還得一個個再改回去。所以,我們需要一個更優雅的實現。

我們可以利用動態創建類并添加方法的思路來實現這個需求,這邊只是以埋點作為示例,你也可以利用該思路擴展任意需要處理的需求和功能。簡單來說就是我們創建一個UIButton的Category, 然后在需要埋點的情況下動態生成一個新的UIButton子類,并給其添加一個可以記錄數據的事件響應方法來替代默認的方法,如下所示:

//

//? UIButton+Tracking.m

//? LearningClass

//

//? Created by Ryan Jin on 07/03/2018.

//? Copyright ? 2018 ArcSoft. All rights reserved.

//

#import "UIButton+Tracking.h"

#import?

#import?

@implementation UIButton (Tracking)

- (void)enableEventTracking

{

? ? NSString *className = [NSString stringWithFormat:@"EventTracking_%@",self.class];

? ? Class kClass? ? ? ? = objc_getClass([className UTF8String]);


? ? if (!kClass) {

? ? ? ? kClass = objc_allocateClassPair([self class], [className UTF8String], 0);

? ? }

? ? SEL setterSelector? = NSSelectorFromString(@"sendAction:to:forEvent:");

? ? Method setterMethod = class_getInstanceMethod([self class], setterSelector);


? ? object_setClass(self, kClass); // 轉換當前類從UIButton到新建的EventTracking_UIButton類


? ? const char *types? ?= method_getTypeEncoding(setterMethod);


? ? class_addMethod(kClass, setterSelector, (IMP)eventTracking_SendAction, types);


? ? objc_registerClassPair(kClass);

}

static void eventTracking_SendAction(id self, SEL _cmd, SEL action ,id target , UIEvent *event) {

? ? struct objc_super superclass = {

? ? ? ? .receiver? ? = self,

? ? ? ? .super_class = class_getSuperclass(object_getClass(self))

? ? };

? ? void (*objc_msgSendSuperCasted)(const void *, SEL, SEL, id, UIEvent *) = (void *)objc_msgSendSuper;


? ? // to do event tracking...

? ? NSLog(@"Click event record: target = %@, action = %@, event = %ld", target, NSStringFromSelector(action), (long)event.type);


? ? objc_msgSendSuperCasted(&superclass, _cmd, action, target, event);

}

@end

然后在添加按鈕的地方,如果需要數據統計功能,則調用enableEventTracking函數來內嵌打點功能。使用示例如下:

- (void)viewDidLoad

{

? ? [super viewDidLoad];


? ? UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 50, 30)];


? ? button.layer.borderColor? ?= [[UIColor redColor] CGColor];

? ? button.layer.borderWidth? ?= 1.0f;

? ? button.layer.cornerRadius? = 4.0f;

? ? button.layer.masksToBounds = YES;


? ? [button addTarget:self action:@selector(trackingButtonAction:)

? ? ? ? ? ? ? ? ?forControlEvents:UIControlEventTouchUpInside];


? ? [self.view addSubview:button];

? ? [button enableEventTracking];

}

- (void)trackingButtonAction:(UIButton *)sender

{

? ? // to do whatever you want...

? ? NSLog(@"%s", __func__);

}

打印輸出信息為:

LearningClass[58516:3424874] Click event record: target = , action = trackingButtonAction:, event = 0

LearningClass[58516:3424874] -[ViewController trackingButtonAction:]

浮于表面探究問題不失為一種方法,但是弄清楚本質才是真正意義上的解決疑惑。


自己心得:

1.很重要一點,所有類的根元類是NSObject的元類,NSObject的根元類是元類本身,但NSObject自身并不是NSObject的元類,NSObject是所有類的根類。但神奇的是NSObject的元類的superClass父類又指回了NSObject。這很神奇。

也就是說object_getClass([NSObject class])和object_getClass(object_getClass([NSObject class]))是一個地址,而[object_getClass([NSObject class]) superclass]和[NSObject class]又是一個地址。

參考地址:https://blog.csdn.net/ChSaDiN/article/details/51672087。

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

推薦閱讀更多精彩內容