Effective Objective-C 2.0 讀書(shū)筆記二(下)

10. 在既有類(lèi)中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)

注意關(guān)鍵詞“關(guān)聯(lián)對(duì)象”钞啸,就是把兩個(gè)對(duì)象關(guān)聯(lián)起來(lái)遥巴,例如把對(duì)象B關(guān)聯(lián)到對(duì)象A上面伍玖,這樣只要我們知道對(duì)象A婿失,就能通過(guò)關(guān)聯(lián)方法拿到對(duì)象B钞艇,這是一個(gè)很有用的特性,可以幫助我們攜帶一些數(shù)據(jù)豪硅,以及一些信息哩照。如果通俗一點(diǎn)理解的話可以把對(duì)象A理解成一個(gè)字典,對(duì)象B是存放在對(duì)象A中的一個(gè)對(duì)象懒浮,通過(guò)對(duì)應(yīng)的key值就能拿到對(duì)應(yīng)的對(duì)象B飘弧。
下面是關(guān)聯(lián)對(duì)象對(duì)應(yīng)的三個(gè)方法(只有三個(gè)方法):
1.通過(guò)給定的鍵值和關(guān)聯(lián)策略對(duì)某對(duì)象設(shè)置關(guān)聯(lián)對(duì)象

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

第一個(gè)參數(shù),被關(guān)聯(lián)對(duì)象砚著,對(duì)應(yīng)上面的對(duì)象A次伶。
第二個(gè)參數(shù),鍵值稽穆,通過(guò)參數(shù)形式我們知道冠王,這是一個(gè)指針,一般我們?cè)诙x這個(gè)指針的時(shí)候使用靜態(tài)全局變量舌镶,因?yàn)檫@是一個(gè)“不透明指針”(自行查找什么是“不透明指針”)柱彻。
第三個(gè)參數(shù),關(guān)聯(lián)的對(duì)象餐胀,對(duì)應(yīng)上面的對(duì)象B哟楷。
第四個(gè)參數(shù),關(guān)聯(lián)策略骂澄,是一個(gè)枚舉值吓蘑,對(duì)應(yīng)定義屬性時(shí)候添加的屬性特性,用于維護(hù)內(nèi)存管理坟冲,下表列出對(duì)應(yīng)關(guān)系:

關(guān)聯(lián)類(lèi)型 等效的屬性特性
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic, retain
OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic, copy
OBJC_ASSOCIATION_RETAIN retain
OBJC_ASSOCIATION_COPY copy

2.通過(guò)給定的鍵值取出相應(yīng)的關(guān)聯(lián)對(duì)象

id objc_getAssociatedObject(id object, const void *key)

第一個(gè)參數(shù),被關(guān)聯(lián)的對(duì)象溃蔫,對(duì)應(yīng)對(duì)象A健提。
第二個(gè)參數(shù),鍵值伟叛。
返回值私痹,關(guān)聯(lián)對(duì)象,對(duì)應(yīng)對(duì)象B。
3.移除被關(guān)聯(lián)對(duì)象的所有關(guān)聯(lián)對(duì)象

void objc_removeAssociatedObjects(id object)

參數(shù)紊遵,被關(guān)聯(lián)對(duì)象账千,對(duì)應(yīng)對(duì)象B。
上面就是關(guān)聯(lián)對(duì)象的所有方法暗膜,但是在用的時(shí)候需要注意匀奏,關(guān)聯(lián)對(duì)象應(yīng)該被我們列在最后的選擇方案,因?yàn)殛P(guān)聯(lián)對(duì)象之間的關(guān)系沒(méi)有正式的定義学搜,其內(nèi)存管理是在設(shè)置關(guān)聯(lián)的時(shí)候才定義的娃善,而不是在接口中預(yù)先設(shè)定好的,有時(shí)會(huì)出現(xiàn)一些不易查找的錯(cuò)誤瑞佩。
PS:偶爾在代碼中寫(xiě)點(diǎn)這樣的代碼聚磺,會(huì)增加代碼的“氣質(zhì)”,你懂的炬丸。

11. 理解objc_msgSend作用

這一小節(jié)的內(nèi)容和我們寫(xiě)代碼沒(méi)有什么關(guān)系瘫寝,但是我們可以了解一下OC中方法的調(diào)用過(guò)程,對(duì)我們的程序調(diào)試很是很有用的稠炬。
首先說(shuō)一下C語(yǔ)言的函數(shù)調(diào)用方式焕阿,用以和OC做比較,C語(yǔ)言使用“靜態(tài)綁定”酸纲,也就是說(shuō)捣鲸,在編譯期就能決定運(yùn)行時(shí)應(yīng)該調(diào)用的函數(shù),而大家都知道闽坡,OC是一門(mén)動(dòng)態(tài)語(yǔ)言栽惶,與之差別的就是OC中有時(shí)候是使用“動(dòng)態(tài)綁定”,就是在運(yùn)行期調(diào)用對(duì)應(yīng)的函數(shù)疾嗅,甚至可以在程序運(yùn)行時(shí)改變外厂。
寫(xiě)一個(gè)簡(jiǎn)單的方法調(diào)用的例子,解釋一下方法的構(gòu)成:

id returnValue = [someObject messageName:parameter];

在這句調(diào)用語(yǔ)句中代承,someObject就是類(lèi)或類(lèi)的實(shí)例汁蝶,messageName就是方法名,parameter就是參數(shù)论悴,編譯器會(huì)把這條語(yǔ)句編譯成一條標(biāo)準(zhǔn)的C語(yǔ)句掖棉,編譯后的語(yǔ)句如下:

id returnValue = objc_msgSend(someObject, @selector(messageName), parameter)

objc_msgSend是一個(gè)可變參數(shù)的函數(shù),對(duì)應(yīng)OC中方法參數(shù)的增加膀估,參數(shù)也會(huì)增加幔亥,相信大家都知道這個(gè)方法中參數(shù)的意思。
objc_msgSend函數(shù)會(huì)根據(jù)參數(shù)察纯,找到對(duì)應(yīng)類(lèi)的對(duì)應(yīng)“方法列表”帕棉,然后找到對(duì)應(yīng)實(shí)現(xiàn)代碼针肥,若找不到會(huì)沿著繼承關(guān)系向上查找,如果還沒(méi)找到香伴,觸發(fā)“消息轉(zhuǎn)發(fā)”機(jī)制(后面會(huì)介紹這個(gè)機(jī)制)慰枕。
這樣下來(lái)調(diào)用一個(gè)方法大家可能感覺(jué)步驟太多,其實(shí)不會(huì)即纲,objc_msgSend會(huì)將匹配結(jié)果放到一張“快速映射表”里具帮,每個(gè)類(lèi)都有一個(gè)這樣的表,加快調(diào)用速度崇裁。另外還有一些特殊情況匕坯,OC運(yùn)行環(huán)境中還有另外一些相關(guān)的處理函數(shù),例如objc_msgSend_stret拔稳、objc_msgSend_fpret葛峻、objc_msgSendSuper就不在一一介紹。
另外提一個(gè)點(diǎn)巴比,OC對(duì)象的每一個(gè)方法當(dāng)編譯成C語(yǔ)言的時(shí)候可以看成是下面這種的形式的

<returnType> Class_selector(id self, SEL _cmd, ...)

其中的方法名是隨意起的术奖,大家發(fā)現(xiàn)這個(gè)函數(shù)和objc_msgSend的形式很想,這是為了利用“尾調(diào)用優(yōu)化”轻绞,是調(diào)用函數(shù)更簡(jiǎn)單采记、高效。

12. 理解消息轉(zhuǎn)發(fā)機(jī)制

這小節(jié)介紹一下上面提到的消息轉(zhuǎn)發(fā)機(jī)制政勃,大家都知道唧龄,觸發(fā)了消息轉(zhuǎn)發(fā)機(jī)制,是因?yàn)槲覀儧](méi)有找到對(duì)應(yīng)的方法奸远,下面看消息轉(zhuǎn)發(fā)機(jī)制怎么處理這個(gè)問(wèn)題既棺。
介紹一下消息轉(zhuǎn)發(fā)機(jī)制,大致分為三個(gè)階段:
1.第一階段懒叛,動(dòng)態(tài)方法解析
對(duì)象在無(wú)法解讀方法的時(shí)候丸冕,首先會(huì)調(diào)用所屬類(lèi)下面這個(gè)方法

+ (BOOL)resolveInstanceMethod:(SEL)sel

sel就是方法名,返回值為Boolean類(lèi)型薛窥,表示這個(gè)類(lèi)是否能新增實(shí)例方法處理這個(gè)方法(如果是類(lèi)方法會(huì)調(diào)用+ (BOOL)resolveClassMethod:(SEL)sel方法)胖烛,我們需要自定義一些處理方法,用于動(dòng)態(tài)添加到類(lèi)中诅迷,用以解決問(wèn)題(可以看后面的例子)佩番,如果這一步不能解決問(wèn)題,轉(zhuǎn)到第二階段罢杉。
2.第二階段答捕,備援接收者
來(lái)到這一步,我們就要改變解決問(wèn)題的思路屑那,既然這個(gè)類(lèi)不能處理這個(gè)方法拱镐,我們可不可以找別的類(lèi)處理,這時(shí)候?qū)?yīng)的處理方法:

- (id)forwardingTargetForSelector:(SEL)aSelector

aSelector是方法名持际,如果當(dāng)前類(lèi)能夠找到一個(gè)類(lèi)幫忙處理這個(gè)方法沃琅,就返回這個(gè)類(lèi),若找不到就放回nil(通過(guò)這個(gè)方法我們可以實(shí)現(xiàn)類(lèi)似“多繼承”)蜘欲。
3.第三階段益眉,完整的消息轉(zhuǎn)發(fā)
如果已經(jīng)來(lái)到了這一步,我們就要做一個(gè)完整的消息轉(zhuǎn)發(fā)姥份。首先創(chuàng)建一個(gè)NSInvocation對(duì)象郭脂,把未處理方法的所有信息封裝在里面,此對(duì)象包含方法名澈歉、目標(biāo)展鸡、參數(shù),這一步要調(diào)用下面的方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation

這一步處理的方法很簡(jiǎn)單埃难,就是在新的類(lèi)上調(diào)用方法莹弊,如果這樣做的話就和第二階段沒(méi)有什么差別了。通常在這一步的時(shí)候會(huì)做一些改進(jìn)涡尘,會(huì)選擇某種方式改變消息內(nèi)容忍弛,例如追加參數(shù),改變方法名等考抄。
對(duì)于消息的處理细疚,越早越好。
下面粘貼一個(gè)利用動(dòng)態(tài)解析方法實(shí)現(xiàn)@dynamic屬性的例子:
這個(gè)例子實(shí)現(xiàn)一個(gè)類(lèi)川梅,類(lèi)似字典的功能疯兼,只不過(guò)寫(xiě)入和讀取信息的時(shí)候用屬性,而不是像字典一樣用關(guān)鍵字挑势。
.h文件中:

#import <Foundation/Foundation.h>
@interface EOCAutoDictionary : NSObject
@property (nonatomic, strong) NSString *string;
@property (nonatomic, strong) NSNumber *number;
@property (nonatomic, strong) NSData *date;
@property (nonatomic, strong) id opaqueObject;
@end

.m文件中:

#import "EOCAutoDictionary.h"
#import <objc/runtime.h> // 主要頭文件的引用
@interface EOCAutoDictionary ()
@property (nonatomic, strong) NSMutableDictionary *backingStore;
@end
@implementation EOCAutoDictionary
@dynamic string, number, date, opaqueObject;
- (id)init{
    if ((self = [super init])) {
        _backingStore = [NSMutableDictionary new];
    }
    return self;
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSString *selectorString = NSStringFromSelector(sel);
    // 通過(guò)是否以“set”開(kāi)頭判斷方法名
    if ([selectorString hasPrefix:@"set"]) {
        /**
         * 向類(lèi)中添加一個(gè)方法
         * 參數(shù)一 指定類(lèi)名.
         * 參數(shù)二 新添加的方法的方法名.
         * 參數(shù)三 函數(shù)指針镇防,指向待添加方法.
         * 參數(shù)四 待添加方法的類(lèi)型編碼.
         */
        class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
    } else {
        class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
    }
    return YES;
}

id autoDictionaryGetter(id self, SEL _cmd){
    // 拿到存儲(chǔ)數(shù)據(jù)的字典
    EOCAutoDictionary *typedSelf = (EOCAutoDictionary *)self;
    NSMutableDictionary *backingStore = typedSelf.backingStore;
    // 拿到方法名
    NSString *key = NSStringFromSelector(_cmd);
    // 返回對(duì)應(yīng)的值
    return [backingStore objectForKey:key];
}
void autoDictionarySetter(id self, SEL _cmd, id value){
    // 拿到存儲(chǔ)數(shù)據(jù)的字典
    EOCAutoDictionary *typedSelf = (EOCAutoDictionary *)self;
    NSMutableDictionary *backingStore = typedSelf.backingStore;
    // 拿到方法名并對(duì)其進(jìn)行處理
    NSString *selectorString = NSStringFromSelector(_cmd);
    NSMutableString *key = [selectorString mutableCopy];
    // 移除方法名中的“:”
    [key deleteCharactersInRange:NSMakeRange(key.length-1, 1)];
    // 移除方法名中的“set”
    [key deleteCharactersInRange:NSMakeRange(0, 3)];
    // 將方法名第一個(gè)字符轉(zhuǎn)為小寫(xiě)
    NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
    // 如果有值,寫(xiě)入字典中
    if (value) {
        [backingStore setObject:value forKey:key];
    } else {
        [backingStore removeObjectForKey:key];
    }   
}
@end

EOCAutoDictionary的用法也很簡(jiǎn)單潮饱,只要直接通過(guò)對(duì)應(yīng)的屬性名来氧,就可以進(jìn)行數(shù)據(jù)的存儲(chǔ)。

13. 用“方法調(diào)配技術(shù)”調(diào)試“黑盒方法”

方法調(diào)配技術(shù)香拉,簡(jiǎn)言之就是啦扬,將方法名和方法實(shí)現(xiàn)分割開(kāi)來(lái),任意組合凫碌。這樣一來(lái)我們可以任意改變一個(gè)方法的實(shí)現(xiàn)扑毡,另外還可以通過(guò)這種辦法給原有方法添加功能,對(duì)不知道內(nèi)部實(shí)現(xiàn)的方法添加提示語(yǔ)句(黑盒調(diào)試)等等盛险。
之所以能這么做瞄摊,主要是因?yàn)榉椒ň灾羔樀男问絹?lái)表示勋又,這種指針叫IMP,我們?cè)谡{(diào)用方法的時(shí)候换帜,只要將指針指向改變楔壤,就能實(shí)現(xiàn)我們想要的效果,運(yùn)用起來(lái)也很簡(jiǎn)單惯驼,通過(guò)下面的例子大家就會(huì)運(yùn)用(注意運(yùn)行時(shí)頭文件的引用):

Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString))
method_exchangeImplementations(originalMethod, swappedMethod);

通過(guò)上面的例子蹲嚣,我們就把NSString的lowercaseString方法和uppercaseString方法調(diào)換了羽氮,是不是很簡(jiǎn)單净嘀。
其實(shí)這樣做并沒(méi)有什么意義,因?yàn)榫唧w的方法實(shí)現(xiàn)已經(jīng)都存在了诺凡,我們沒(méi)必要改變一個(gè)方法實(shí)現(xiàn)说贝,但是我們通過(guò)這種方法給已知的方法添加功能议惰,例如下面的例子:
.h文件:

@interface NSString (EOCMyAdditions)
- (NSString *)eoc_myLowercaseString; // 在分類(lèi)中給NSString添加功能
@end

.m文件:

@implementation NSString (EOCMyAdditions)
- (NSString *)eoc_myLowercaseString{
    NSString *lowercase = [self eoc_myLowercaseString];
    NSLog(@"%@ => %@", self, lowercase);
    return lowercase;
}
@end

然后我們使用方法調(diào)配技術(shù),將上面的方法和lowercaseString方法進(jìn)行調(diào)換:

Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(eoc_myLowercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);

這樣執(zhí)行完后狂丝,當(dāng)我們?cè)僬{(diào)用lowercaseString方法的時(shí)候會(huì)有下面的結(jié)果:

NSString *string = @"This is tHe StRing";
NSString *lowercaseString = [string lowercaseString];
// Output:This is tHe StRing => this is the string

通過(guò)這個(gè)方法我們發(fā)現(xiàn)换淆,我們可以為那些不知道內(nèi)部實(shí)現(xiàn)的黑盒方法添加日志記錄功能。
一般來(lái)說(shuō)几颜,我們很少用“方法調(diào)配”倍试,只有在調(diào)試程序的時(shí)候才需要在運(yùn)行期修改方法實(shí)現(xiàn)。

14. 理解“類(lèi)對(duì)象”的用意

首先我們要知道蛋哭,OC的實(shí)例對(duì)象是指向某塊內(nèi)存數(shù)據(jù)的指針县习,所以在聲明變量時(shí),要用*號(hào)谆趾。同時(shí)我們知道OC中有一種通用對(duì)象類(lèi)型“id”(id本身已是一個(gè)指針)躁愿,所以我們?cè)谟谩癷d”聲明變量的時(shí)候可能和平常有點(diǎn)不同:

NSString *aString = @"some string";
id aString = @"some string";

上面兩種定義方式相比,語(yǔ)法意義相同沪蓬,區(qū)別在于彤钟,指定具體類(lèi)型后,當(dāng)實(shí)例調(diào)用方法的時(shí)候跷叉,編輯器會(huì)給我們提示逸雹。
下面看一下“id”類(lèi)型的定義:

typedef struct objc_object *id;

id其實(shí)是objc_object類(lèi)型的結(jié)構(gòu)體,而objc_object定義如下:

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

結(jié)構(gòu)體中是一個(gè)Class類(lèi)型的變量云挟,該變量定義對(duì)象所屬的類(lèi)梆砸。下面我們看一下Class類(lèi)型是個(gè)什么東西:

typedef struct objc_class *Class;
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

我們看到,這個(gè)結(jié)構(gòu)體存放類(lèi)的各種信息(元數(shù)據(jù)),例如類(lèi)有多少個(gè)實(shí)力變量园欣,類(lèi)名等等信息帖世。
通過(guò)上面的關(guān)系,我們知道在objc的runtime中沸枯,類(lèi)是用objc_class結(jié)構(gòu)體表示的日矫,對(duì)象是用objc_object結(jié)構(gòu)體表示的赂弓, 對(duì)象的isa用來(lái)標(biāo)示這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例。
這些源碼是屬于objc runtime的搬男,objc runtime的源代碼蘋(píng)果已經(jīng)開(kāi)源了拣展,你可以在這里下載到objc的runtime源代碼。
其實(shí)到這里大家可能會(huì)有一個(gè)疑問(wèn)缔逛,為什么objc_class結(jié)構(gòu)體里面也有一個(gè)isa,那么這個(gè)isa指向誰(shuí)呢姓惑?我們往下看褐奴,[NSObject class],這里我們調(diào)用了+ (Class)class這個(gè)類(lèi)方法于毙,我們?cè)匍_(kāi)發(fā)中經(jīng)常用到這個(gè)方法敦冬,它返回的是這個(gè)類(lèi)所屬的Class類(lèi)型。+ (Class)class類(lèi)方法的實(shí)現(xiàn)源碼是這樣的:

+ (Class)class { 
    return self; 
} 

為什么會(huì)返回self唯沮,self總是指的自身脖旱,而在這里沒(méi)有實(shí)例啊介蛉!這時(shí)候看開(kāi)發(fā)文檔我們會(huì)發(fā)現(xiàn)萌庆,實(shí)際上函數(shù)的返回值是一個(gè)類(lèi)對(duì)象class object,所以其本質(zhì)上還是一個(gè)對(duì)象而已币旧。既然是一個(gè)對(duì)象践险,它擁有一個(gè)self指針也就不奇怪了,所以對(duì)于像NSObject這樣的類(lèi)來(lái)說(shuō)吹菱,它其實(shí)代表的是一個(gè)類(lèi)對(duì)象巍虫,本質(zhì)上還是一個(gè)普通的實(shí)例對(duì)象,那么又會(huì)問(wèn)了鳍刷,這個(gè)類(lèi)對(duì)象是誰(shuí)的實(shí)例呢占遥?很遺憾,要找到這個(gè)問(wèn)題的答案输瓜,我們?cè)?objc runtime 這一層上已經(jīng)沒(méi)辦法辦到了瓦胎,我們需要到更低層,也就是 objc 語(yǔ)言層去尋找答案了前痘,但是 objc 語(yǔ)言層是不開(kāi)源的凛捏,如果想繼續(xù)學(xué)習(xí),大家可以在網(wǎng)上找模仿OC低層的代碼芹缔。
以上了解一下就好坯癣,我們只要知道類(lèi)的繼承體系就行了,下面用一個(gè)例子:有一個(gè)類(lèi)(暫且叫SomeClass)繼承于NSObject,那么這些類(lèi)和元類(lèi)的繼承關(guān)系是最欠,SomeClass實(shí)例有一個(gè)isa指針指向SomeClass類(lèi)示罗,SomeClass類(lèi)有一個(gè)isa指針指向SomeClass元類(lèi)惩猫,NSObject類(lèi)也有一個(gè)isa指針指向NSObject元類(lèi),SomeClass的父類(lèi)是NSObject蚜点,SomeClass元類(lèi)的父類(lèi)是NSObject元類(lèi)轧房,通過(guò)這種關(guān)系,我們?cè)陬?lèi)繼承體系中查詢(xún)類(lèi)型信息绍绘,用isMenberOfClass:判斷對(duì)象是否是某個(gè)特定類(lèi)的實(shí)例奶镶,用isKindOfClass:判斷對(duì)象是否為某類(lèi)或其派生類(lèi)的實(shí)例。因?yàn)镺C是動(dòng)態(tài)型語(yǔ)言的特性陪拘,上面兩個(gè)方法非常有用厂镇。
有時(shí)我們可以用比較類(lèi)對(duì)象是否等同的辦法來(lái)進(jìn)行比較,這時(shí)要用==操作符左刽,而不是用isEqual方法捺信,因?yàn)轭?lèi)對(duì)象是單利,在應(yīng)用程序中欠痴,每個(gè)類(lèi)的類(lèi)對(duì)象只有一個(gè)實(shí)例迄靠,也就是說(shuō)另外一種判斷對(duì)象是否為某類(lèi)實(shí)例的辦法是:

id object = /*...*/
if ([object class] == [SomeClass class]){
}

這一部分基本都是關(guān)于OC運(yùn)行時(shí)的知識(shí),可能我們平時(shí)寫(xiě)代碼的時(shí)候涉及很少喇辽,但是了解這些掌挚,對(duì)于我們的開(kāi)發(fā)是很有幫助的,OC運(yùn)行時(shí)是一個(gè)很強(qiáng)大的東西茵臭,有興趣的同學(xué)可以好好研究一下疫诽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市旦委,隨后出現(xiàn)的幾起案子奇徒,更是在濱河造成了極大的恐慌,老刑警劉巖缨硝,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摩钙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡查辩,警方通過(guò)查閱死者的電腦和手機(jī)胖笛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)宜岛,“玉大人长踊,你說(shuō)我怎么就攤上這事∑汲” “怎么了身弊?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我阱佛,道長(zhǎng)帖汞,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任凑术,我火速辦了婚禮翩蘸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淮逊。我一直安慰自己催首,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布壮莹。 她就那樣靜靜地躺著翅帜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪命满。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天绣版,我揣著相機(jī)與錄音胶台,去河邊找鬼。 笑死杂抽,一個(gè)胖子當(dāng)著我的面吹牛诈唬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缩麸,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼铸磅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了杭朱?” 一聲冷哼從身側(cè)響起阅仔,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弧械,沒(méi)想到半個(gè)月后八酒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刃唐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年羞迷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片画饥。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衔瓮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抖甘,到底是詐尸還是另有隱情热鞍,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站碍现,受9級(jí)特大地震影響幅疼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜昼接,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一爽篷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧慢睡,春花似錦逐工、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至髓涯,卻和暖如春袒啼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纬纪。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工蚓再, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人包各。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓摘仅,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親问畅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子娃属,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

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