runtime變奏曲废登,那些藏在runtime中的接口(一)

C本身是一個(gè)靜態(tài)語(yǔ)言,數(shù)據(jù)類型和代碼運(yùn)行的結(jié)果都是在編譯的時(shí)候確定的郁惜。而Objective-C的runtime機(jī)制賦予了C一個(gè)新的活力堡距,即運(yùn)行時(shí)機(jī)制。這也就是說(shuō)扳炬,OC代碼或者C代碼在編譯過(guò)后的機(jī)器碼并不能得出運(yùn)行結(jié)果吏颖。而這個(gè)結(jié)果需要在運(yùn)行的時(shí)候才能獲得,這樣就給了我們一個(gè)新的操縱代碼的空間恨樟,也就是運(yùn)行時(shí)半醉。在OC中,運(yùn)行時(shí)是一段提前寫完的一個(gè)模塊的代碼劝术∷醵啵可以這么說(shuō),OC的運(yùn)行時(shí)就是這段代碼賦予的养晋。
前幾篇文章中衬吆,我提到了objc_msgSend的流程,是為了讓大家對(duì)runtime的過(guò)程有一個(gè)大致的了解绳泉。但是逊抡,對(duì)于大部分人來(lái)說(shuō),比起原理零酪,更關(guān)注的是怎么用冒嫡。所以本章的內(nèi)容就是我在runtime小序曲,從運(yùn)行時(shí)多態(tài)看這股神秘力量中提到的runtime的除了objc_msgSend的另外兩種應(yīng)用:NSObject的方法和runtime的函數(shù)四苇。
學(xué)習(xí)進(jìn)度:

一榆骚、NSObject的方法

前幾天片拍,和群里的一位騷年討論了runtime的問(wèn)題。他的看法是妓肢,runtime并沒(méi)有什么用穆碎,不用runtime照樣可以開發(fā)。其實(shí)职恳,前半句并沒(méi)有什么毛病所禀,runtime確實(shí)沒(méi)有什么用,因?yàn)榇蟛糠珠_發(fā)工作基本用不著(其實(shí)我們公司用的蠻多的)放钦。問(wèn)題出在第二句色徘,不用runtime就可以開發(fā)。OC作為一種高級(jí)語(yǔ)言操禀,能讓你方便的使用它的一些接口褂策。比如:

- (BOOL)respondsToSelector:(SEL)aSelector;

這個(gè)方法大家應(yīng)該經(jīng)常使用,尤其是在使用delegate的場(chǎng)合颓屑,基本是必用的斤寂。那么,我們從邏輯上看這個(gè)方法揪惦。從runtime小序曲遍搞,從運(yùn)行時(shí)多態(tài)看這股神秘力量中我就說(shuō)過(guò),OC沒(méi)法在編譯時(shí)刻確定一個(gè)對(duì)象的類型器腋。而這個(gè)方法是判斷一個(gè)繼承自NSObject的class有沒(méi)有實(shí)現(xiàn)一個(gè)SEL溪猿。很明顯,編譯時(shí)刻做不了這件事纫塌,它是在運(yùn)行時(shí)刻做的诊县。所以,不用runtime就可以開發(fā)是錯(cuò)誤的措左。其實(shí)依痊,這種事情并不罕見(jiàn),大部分人不想學(xué)習(xí)runtime的原因也是如此怎披。

  • runtime是C和匯編寫的胸嘁,看不懂。
  • runtime開發(fā)用不著钳枕。

換個(gè)方式再說(shuō)一下缴渊,runtime賦予C面向?qū)ο蟮哪芰Γ杂辛薕C鱼炒。那么說(shuō)實(shí)話衔沼,只要你用到class,其實(shí)都是和runtime相關(guān)的昔瞧,怎么可能避開指蚁。好像跑題了,現(xiàn)在我們回來(lái)自晰。其實(shí)凝化,和respondsToSelector:類似的OC方法還有很多,下述我會(huì)整理一些常用的酬荞。

// 在usr/include中的objc/runtime.h可以查看

// 獲取對(duì)象對(duì)應(yīng)的class
- (Class)class;
// 判斷一個(gè)對(duì)象或者類是不是某個(gè)class或者這個(gè)class的派生類
- (BOOL)isKindOfClass:(Class)aClass;
// 判斷一個(gè)對(duì)象或者類是不是某個(gè)class
- (BOOL)isMemberOfClass:(Class)aClass;
// 判斷一個(gè)對(duì)象或者類對(duì)應(yīng)的objc_class里面是否實(shí)現(xiàn)了某個(gè)協(xié)議
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
// 判斷一個(gè)對(duì)象或者類對(duì)應(yīng)的objc_class里面有沒(méi)有某個(gè)方法
- (BOOL)respondsToSelector:(SEL)aSelector;

二搓劫、runtime中的數(shù)據(jù)結(jié)構(gòu)

一中舉出了一些OC的runtime方法瞧哟,很易懂,因?yàn)镺C就是我們的開發(fā)語(yǔ)言枪向,下面我會(huì)一個(gè)個(gè)解讀runtime中的C語(yǔ)言的接口勤揩。因?yàn)镃中沒(méi)有class的概念,只有struct秘蛔,所以在介紹C語(yǔ)言接口之前陨亡,這里我將挨個(gè)介紹在runtime中使用到的常見(jiàn)結(jié)構(gòu)體。

1深员、objc_class

類結(jié)構(gòu)體负蠕,對(duì)應(yīng)class。前幾篇文章提到最多的一個(gè)結(jié)構(gòu)倦畅。
objc_class

struct objc_class {
    // 指向元類的的指針遮糖,如果本身是元類,則指向rootMeta
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    // 指向父類
    Class super_class                                        OBJC2_UNAVAILABLE;
    // 類名
    const char *name                                         OBJC2_UNAVAILABLE;
    // 版本號(hào)滔迈,可以用runtime方法set止吁,get
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    // 成員變量列表,存儲(chǔ)成員變量
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    // 方法列表
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    // 方法cache燎悍,msgSend遍歷繼承鏈的時(shí)候需要輔助使用
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    // 協(xié)議列表
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

OBJC2_UNAVAILABLE宏定義是蘋果在 Objc 中對(duì)系統(tǒng)運(yùn)行版本進(jìn)行約束的操作敬惦,為的是兼容非Objective-C 2.0的遺留邏輯,但我們?nèi)阅軓闹蝎@得一些有價(jià)值的信息谈山,有興趣的可以查看源代碼俄删,見(jiàn)底部文獻(xiàn)。

2奏路、與objc_class直接相關(guān)

這里面舉出了存儲(chǔ)于上述objc_class結(jié)構(gòu)體之中的一些相關(guān)結(jié)構(gòu)體畴椰。
objc_method

// 存在objc_method_list里面
struct objc_method {
    // 方法名
    SEL method_name                                          OBJC2_UNAVAILABLE;
    // 參數(shù),返回值編碼
    char *method_types                                       OBJC2_UNAVAILABLE;
    // 方法地址指針
    IMP method_imp                                           OBJC2_UNAVAILABLE;
} 

objc_method_description

// 方法描述
struct objc_method_description {
    // 方法名
    SEL name;     
    // 參數(shù)鸽粉,返回值編碼             
    char *types;           
};

objc_protocol_list

struct objc_protocol_list {
    // 指向下一個(gè)objc_protocol_list的指針
    struct objc_protocol_list *next;
    long count;
    // 協(xié)議結(jié)構(gòu)
    Protocol *list[1];
};

objc_ivar

struct objc_ivar {
    // 成員變量名
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    // 成員變量的類型斜脂,比如@"NSString" 代表NSString
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}  

3、與分類相關(guān)

分類是不會(huì)在編譯時(shí)刻綁定到對(duì)應(yīng)的類里的触机,只能在運(yùn)行時(shí)動(dòng)態(tài)綁定帚戳。所以這里把它拿出來(lái)。
objc_category

struct objc_category {
    // 分類名稱
    char *category_name                                      OBJC2_UNAVAILABLE;
    // 綁定的類名
    char *class_name                                         OBJC2_UNAVAILABLE;
    // 分類中的實(shí)例方法
    struct objc_method_list *instance_methods                OBJC2_UNAVAILABLE;
    // 分類中的類方法
    struct objc_method_list *class_methods                   OBJC2_UNAVAILABLE;
    // 分類實(shí)現(xiàn)的協(xié)議
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
} 

分類和類擴(kuò)展區(qū)別:
extension看起來(lái)很像一個(gè)匿名的category儡首,但是extension和有名字的category幾乎完全是兩個(gè)東西片任。 extension在編譯期決議,它就是類的一部分蔬胯,在編譯期和頭文件里的@interface以及實(shí)現(xiàn)文件里的@implement一起形成一個(gè)完整的類对供,它伴隨類的產(chǎn)生而產(chǎn)生,亦隨之一起消亡氛濒。但是category則完全不一樣产场,它是在運(yùn)行期決議的鹅髓。如想學(xué)習(xí)更多category相關(guān)底層,參見(jiàn):http://tech.meituan.com/DiveIntoCategory.html京景。

4迈勋、屬性相關(guān)

這里為什么把屬性拿出來(lái)呢?因?yàn)樯鲜龅募嫒軴C1.0的objc_class里面沒(méi)有property醋粟。看了runtime的源碼之后重归,發(fā)現(xiàn)property有一個(gè)具體的存儲(chǔ)地點(diǎn)米愿,也在objc_class(實(shí)質(zhì)是繼承了objc_object)中,至于上述objc_class沒(méi)有鼻吮,只是因?yàn)閍pple不想讓我們看見(jiàn)育苟。如想了解更多,參考:runtime源碼的objc-runtime-new.mm和objc-private.h椎木。
objc_property_t

// 屬性對(duì)應(yīng)的結(jié)構(gòu)體objc_property违柏,至于具體結(jié)構(gòu),一般看不著
typedef struct objc_property *objc_property_t;

// 在objc-runtime-old.h中的結(jié)構(gòu)體作為參考吧
struct old_property {
    // 屬性名
    const char *name;
    // objc_property_attribute_t的char*表示
    const char *attributes;
};

objc_property_attribute_t

// 這是用來(lái)修飾表示屬性的一些修飾詞香椎,比如nonatomic漱竖、copy等等
typedef struct {
    const char *name;          
    const char *value;         
} objc_property_attribute_t;

objc_property_attribute_t具體參考:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW1

三畜伐、runtime的函數(shù)

到這也就到了正菜了馍惹。本章我會(huì)把runtime庫(kù)中public部分所有和class相關(guān)的api都調(diào)用一遍,解釋一遍玛界。至于ivar相關(guān)万矾、method相關(guān)、protocol相關(guān)慎框、property相關(guān)和具體應(yīng)用良狈,且待下回分解。

前提

// 為獲取class的protocol準(zhǔn)備
@protocol AProtocol <NSObject>
- (void)aProtocolMethod;
@end
// 為獲取class的相關(guān)信息
@interface A : NSObject {
    NSString *strA;
}
@property (nonatomic, assign) NSUInteger uintA;
@end
@implementation A
@end
// 為為class添加方法準(zhǔn)備
void aNewMethod() {
    NSLog(@"aNewMethod");
}
void aReplaceMethod() {
    NSLog(@"aReplaceMethod");
}

class相關(guān)使用場(chǎng)景1(class一些基礎(chǔ)信息獲缺靠荨):

// 代碼
// 獲取類名
const char *a = class_getName([A class]); 
NSLog(@"%s", a);  // a
// 獲取父類
Class aSuper = class_getSuperclass([A class]);
NSLog(@"%s", class_getName(aSuper));  // b
// 判斷是否是元類
BOOL aIfMeta = class_isMetaClass([A class]);
BOOL aMetaIfMeta = class_isMetaClass(objc_getMetaClass("A"));
NSLog(@"%i  %i", aIfMeta, aMetaIfMeta);   // c
// 類大小
size_t aSize = class_getInstanceSize([A class]);
NSLog(@"%zu", aSize);  // d
// 獲取和設(shè)置類版本號(hào)
class_setVersion([A class], 1);
NSLog(@"%d", class_getVersion([A class]));  // e
// 獲取工程中所有的class薪丁,包括系統(tǒng)class
unsigned int count3;
int classNum = objc_getClassList(NULL, count3);
NSLog(@"%d", classNum);    // f
// 獲取工程中所有的class的數(shù)量
objc_copyClassList(&count3);
NSLog(@"%d", classNum);    // g
Class aClass;
// 獲取name為"A"的class
aClass = objc_getClass("A");    
NSLog(@"%s", class_getName(aClass));    // h
// 獲取name為"A"的class,比getClass少了一次檢查
aClass = objc_lookUpClass("A");
NSLog(@"%s", class_getName(aClass));    // i
// 獲取name為"A"的class猎醇,找不到會(huì)crash
aClass = objc_getRequiredClass("A");
NSLog(@"%s", class_getName(aClass));    // j
// 獲取name為"A"的class元類
Class aMetaClass = objc_getMetaClass("A");
NSLog(@"%d", class_isMetaClass(aMetaClass));    // k

// 輸出
2017-01-21 12:15:55.909 block[2493:1919841] A    // a
2017-01-21 12:15:55.912 block[2493:1919841] NSObject    // b
2017-01-21 12:15:55.913 block[2493:1919841] 0  1    // c
2017-01-21 12:15:55.913 block[2493:1919841] 24    // d
2017-01-21 12:15:55.914 block[2493:1919841] 1    // e
2017-01-21 12:44:32.401 block[5103:1948802] 4733    // f
2017-01-21 12:44:32.402 block[5103:1948802] 4733    // g
2017-01-21 12:44:32.402 block[5103:1948802] A    // h 
2017-01-21 12:44:32.403 block[5103:1948802] A    // i
2017-01-21 12:44:32.403 block[5103:1948802] A    // j
2017-01-21 12:44:32.403 block[5103:1948802] 1    // k

class相關(guān)使用場(chǎng)景2(class中的ivar和property)

// 代碼

// 獲取類實(shí)例成員變量窥突,只能取到本類的,父類的訪問(wèn)不到
Ivar aInstanceIvar = class_getInstanceVariable([A class], "strA");
NSLog(@"%s", ivar_getName(aInstanceIvar));    // a
// 獲取類成員變量硫嘶,相當(dāng)于class_getInstanceVariable(cls->isa, name)阻问,感覺(jué)除非給metaClass添加成員,否則不會(huì)獲取到東西
Ivar aClassIvar = class_getClassVariable([A class], "strA");
NSLog(@"%s", ivar_getName(aClassIvar));    // b
// 往A類添加成員變量不會(huì)成功的沦疾。因?yàn)閏lass_addIvar不能給現(xiàn)有的類添加成員變量称近,也不能給metaClass添加成員變量第队,那怎么添加,且往后看
if (class_addIvar([A class], "intA", sizeof(int), log2(sizeof(int)), @encode(int))) {
    NSLog(@"綁定成員變量成功");    // c
}
// 獲取類中的ivar列表刨秆,count為ivar總數(shù)
unsigned int count;
Ivar *ivars = class_copyIvarList([A class], &count);
NSLog(@"%i", count);    // d
// 獲取某個(gè)名為"uIntA"的屬性
objc_property_t aPro = class_getProperty([A class], "uintA");
NSLog(@"%s", property_getName(aPro));    // e
// 獲取類的全部屬性
class_copyPropertyList([A class], &count);
NSLog(@"%i", count);    // f
// 創(chuàng)建objc_property_attribute_t凳谦,然后動(dòng)態(tài)添加屬性
objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
objc_property_attribute_t ownership0 = { "C", "" }; // C = copy
objc_property_attribute_t ownership = { "N", "" }; //N = nonatomic
objc_property_attribute_t backingivar  = { "V", [[NSString stringWithFormat:@"_%@", @"aNewProperty"] UTF8String] };  //variable name
objc_property_attribute_t attrs[] = { type, ownership0, ownership, backingivar };
if(class_addProperty([A class], "aNewProperty", attrs, 4)) {
    // 只會(huì)增加屬性,不會(huì)自動(dòng)生成set衡未,get方法
    NSLog(@"綁定屬性成功");    // g
}
// 創(chuàng)建objc_property_attribute_t尸执,然后替換屬性
objc_property_attribute_t typeNew = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
objc_property_attribute_t ownership0New = { "C", "" }; // C = copy
objc_property_attribute_t ownershipNew = { "N", "" }; //N = nonatomic
objc_property_attribute_t backingivarNew  = { "V", [[NSString stringWithFormat:@"_%@", @"uintA"] UTF8String] };  //variable name
objc_property_attribute_t attrsNew[] = { typeNew, ownership0New, ownershipNew, backingivarNew };
class_replaceProperty([A class], "uintA", attrsNew, 4);
// 這有個(gè)很大的坑。替換屬性指的是替換objc_property_attribute_t缓醋,而不是替換name如失。如果替換的屬性class里面不存在,則會(huì)動(dòng)態(tài)添加這個(gè)屬性
objc_property_t pro = class_getProperty([A class], "uintA");
NSLog(@"123456   %s", property_getAttributes(pro));    // h
// class_getIvarLayout送粱、class_setIvarLayout褪贵、class_getWeakIvarLayout、class_setWeakIvarLayout用來(lái)設(shè)定和獲取成員變量的weak抗俄、strong脆丁。參見(jiàn)http://blog.sunnyxx.com/2015/09/13/class-ivar-layout/

// 輸出
2017-01-21 12:44:32.377 block[5103:1948802] strA    // a
2017-01-21 12:44:32.377 block[5103:1948802] (null)    // b
2017-01-21 12:44:32.377 block[5103:1948802] 2    // d
2017-01-21 12:44:32.377 block[5103:1948802] uintA    // e
2017-01-21 12:44:32.378 block[5103:1948802] 1    // f
2017-01-21 12:44:32.378 block[5103:1948802] 綁定屬性成功    // g
2017-01-21 12:44:32.379 block[5103:1948802] 123456   T@"NSString",C,N,V_uintA    // h

class相關(guān)使用場(chǎng)景3(class中的method)

// 代碼

// 動(dòng)態(tài)添加方法
class_addMethod([A class], @selector(aNewMethod), (IMP)aNewMethod, "v");
// 向元類動(dòng)態(tài)添加類方法
class_addMethod(objc_getMetaClass("A"), @selector(aNewMethod), (IMP)aNewMethod, "v");
// 獲取類實(shí)例方法
Method aMethod = class_getInstanceMethod([A class], @selector(aNewMethod));
// 獲取元類中類方法
Method aClassMethod = class_getClassMethod([A class], @selector(aNewMethod));
NSLog(@"%s", method_getName(aMethod));    // a
NSLog(@"%s", method_getName(aClassMethod));    // b
// 獲取類中的method列表
unsigned int count1;
Method *method = class_copyMethodList([A class], &count1);
// 多了一個(gè)方法,打印看出.cxx_destruct动雹,只在arc下有槽卫,析構(gòu)函數(shù)
NSLog(@"%i", count1);    // c
NSLog(@"%s", method_getName(method[2]));    // d
 // 替換方法,其實(shí)是替換IMP
class_replaceMethod([A class], @selector(aNewMethod), (IMP)aReplaceMethod, "v");
 // 調(diào)用aNewMethod洽胶,其實(shí)是調(diào)用了aReplaceMethod
[[A new] performSelector:@selector(aNewMethod)];    // aReplaceMethod會(huì)輸出 e
// 獲取類中某個(gè)SEL的IMP
IMP aNewMethodIMP = class_getMethodImplementation([A class], @selector(aNewMethod));
aNewMethodIMP();    // 會(huì)調(diào)用aReplaceMethod的輸出 f
// 獲取類中某個(gè)SEL的IMP
IMP aNewMethodIMP_stret = class_getMethodImplementation_stret([A class], @selector(aNewMethod));
aNewMethodIMP_stret();    // 會(huì)調(diào)用aReplaceMethod的輸出 g
// 判斷A類中有沒(méi)有一個(gè)SEL
if(class_respondsToSelector([A class], @selector(aNewMethod))) {
    NSLog(@"存在這個(gè)方法");    // h
}

// 輸出
2017-01-21 12:44:32.379 block[5103:1948802] aNewMethod    // a
2017-01-21 12:44:32.379 block[5103:1948802] aNewMethod    // b
2017-01-21 12:44:32.380 block[5103:1948802] 4    // c 
2017-01-21 12:44:32.380 block[5103:1948802] setUintA:    // d
2017-01-21 12:44:32.380 block[5103:1948802] aReplaceMethod    // e
2017-01-21 12:44:32.381 block[5103:1948802] aReplaceMethod    // f
2017-01-21 12:44:32.381 block[5103:1948802] aReplaceMethod    // g
2017-01-21 12:44:32.381 block[5103:1948802] 存在這個(gè)方法    // h

class相關(guān)使用場(chǎng)景4(動(dòng)態(tài)創(chuàng)建類)

// 代碼

// 動(dòng)態(tài)創(chuàng)建一個(gè)類和其元類
Class aNewClass = objc_allocateClassPair([NSObject class], "aNewClass", 0);
// 添加成員變量
if (class_addIvar(aNewClass, "intA", sizeof(int), log2(sizeof(int)), @encode(int))) {
    NSLog(@"綁定成員變量成功");    // a
}
// 注冊(cè)這個(gè)類晒夹,之后才能用
objc_registerClassPair(aNewClass);
// 銷毀這個(gè)類和元類
objc_disposeClassPair(aNewClass);    

// 輸出
2017-01-21 12:44:32.382 block[5103:1948802] 綁定成員變量成功    // a

class相關(guān)使用場(chǎng)景5(class中的protocol)

// 代碼

// 添加protocol到class
if(class_addProtocol([A class], @protocol(AProtocol))) {
    NSLog(@"綁定Protocol成功");    // a
}
// 查看類是不是遵循protocol
if(class_conformsToProtocol([A class], @protocol(AProtocol))) {
    NSLog(@"A遵循AProtocol");    // b
}
// 獲取類中的protocol
unsigned int count2;
Protocol *__unsafe_unretained  *aProtocol = class_copyProtocolList([A class], &count2);
NSLog(@"%s", protocol_getName(aProtocol[0]));    // c

// 輸出
2017-01-21 12:44:32.381 block[5103:1948802] 綁定Protocol成功    // a
2017-01-21 12:44:32.381 block[5103:1948802] A遵循AProtocol    // b
2017-01-21 12:44:32.382 block[5103:1948802] AProtocol    // c

四、小結(jié)

本章介紹了runtime的一些OC方法和runtime的數(shù)據(jù)結(jié)構(gòu)姊氓,另外把runtime庫(kù)public部分中所有與class相關(guān)的C方法都介紹了丐怯。下一節(jié),將繼續(xù)介紹runtime庫(kù)public部分中中的其他的api翔横。(其實(shí)读跷,我覺(jué)得runtime學(xué)習(xí)不要太關(guān)注應(yīng)用,準(zhǔn)確的說(shuō)禾唁,每一個(gè)api都是一種應(yīng)用)

五效览、文獻(xiàn)

1、https://developer.apple.com/reference/objectivec/1657527-objective_c_runtime?language=objc
2荡短、https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
3丐枉、http://blog.sunnyxx.com/2015/09/13/class-ivar-layout/
4、http://tech.meituan.com/DiveIntoCategory.html
5掘托、https://github.com/opensource-apple/objc4

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘦锹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌弯院,老刑警劉巖辱士,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異听绳,居然都是意外死亡颂碘,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門椅挣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)头岔,“玉大人,你說(shuō)我怎么就攤上這事鼠证∏杏停” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵名惩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我孕荠,道長(zhǎng)娩鹉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任稚伍,我火速辦了婚禮弯予,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘个曙。我一直安慰自己锈嫩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布垦搬。 她就那樣靜靜地躺著呼寸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猴贰。 梳的紋絲不亂的頭發(fā)上对雪,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音米绕,去河邊找鬼瑟捣。 笑死,一個(gè)胖子當(dāng)著我的面吹牛栅干,可吹牛的內(nèi)容都是我干的迈套。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼碱鳞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼桑李!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤芙扎,失蹤者是張志新(化名)和其女友劉穎星岗,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戒洼,經(jīng)...
    沈念sama閱讀 45,775評(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,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖磷蜀,靈堂內(nèi)的尸體忽然破棺而出召耘,到底是詐尸還是另有隱情,我是刑警寧澤褐隆,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布污它,位于F島的核電站,受9級(jí)特大地震影響庶弃,放射性物質(zhì)發(fā)生泄漏衫贬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一歇攻、第九天 我趴在偏房一處隱蔽的房頂上張望固惯。 院中可真熱鬧,春花似錦缴守、人聲如沸葬毫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)贴捡。三九已至,卻和暖如春村砂,著一層夾襖步出監(jiān)牢的瞬間栈暇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工箍镜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留源祈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓色迂,卻偏偏與公主長(zhǎng)得像香缺,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子歇僧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

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