Objective-C try/catch異常處理機(jī)制原理(轉(zhuǎn))

Objective-C使用@try @catch @finally來捕獲并處理異常。處理異常需要用到NSException類蜓斧,它是所有異常的基類。你可以直接使用NSException類來捕獲異常随闺,也可以繼承一個(gè)新的類虾标。

Objective-C是C語言的擴(kuò)充,它的異常處理機(jī)制是通過C標(biāo)準(zhǔn)庫提供兩個(gè)特殊的函數(shù)setjmp()和longjmp()函數(shù)實(shí)現(xiàn)的辣垒。如果對C的異常處理機(jī)制和setjmp望侈、longjmp函數(shù)不了解的,建議先閱讀:C語言異常處理機(jī)制勋桶。

先來看看下面的例子:

復(fù)制代碼

import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{
@autoreleasepool
{
@try {
NSException *e = [NSException
exceptionWithName:@"FileNotFoundException"
reason:@"File Not Found on System"
userInfo:nil];
@throw e;
}
@catch (NSException *exception) {
if ([[exception name] isEqualToString:NSInvalidArgumentException]) {
NSLog(@"%@", exception);
} else {
@throw exception;
}
}
@finally {
NSLog(@"finally");
}
}
return 0;
}
復(fù)制代碼

例子很簡單脱衙,在@try中拋出一個(gè)自定義的FileNotFoundException類型的異常,然后在@catch中判斷捕獲的異常是不是NSInvalidArgumentException類型例驹,如果不是捐韩,將異常再次拋出。最后總是會執(zhí)行@finally語句鹃锈,一般異常處理的善后工作都放這里來做荤胁。

如何才能了解它內(nèi)部的工作流程,@try @catch @finally的定義無法查看屎债。幸運(yùn)的是我們可以通過Clang生成C的中間代碼來了解try/catch原理仅政。想了解Clang推薦閱讀:編譯器Clang介紹垢油。

以上面的代碼為例,使用文本編輯器將代碼保存到main.m文件中圆丹,文件名可隨便定義滩愁。打開終端輸入:clang -rewrite-objc main.m 命令編譯。

得到一份main.cpp文件:

復(fù)制代碼
struct objc_selector; struct objc_class;
struct __rw_objc_super { struct objc_object *object; struct objc_object *superClass; };

ifndef _REWRITER_typedef_Protocol

typedef struct objc_object Protocol;

define _REWRITER_typedef_Protocol

endif

define __OBJC_RW_DLLIMPORT extern

__OBJC_RW_DLLIMPORT struct objc_object *objc_msgSend(struct objc_object *, struct objc_selector *, ...);
__OBJC_RW_DLLIMPORT struct objc_object *objc_msgSendSuper(struct objc_super *, struct objc_selector *, ...);
__OBJC_RW_DLLIMPORT struct objc_object *objc_msgSend_stret(struct objc_object *, struct objc_selector *, ...);
__OBJC_RW_DLLIMPORT struct objc_object *objc_msgSendSuper_stret(struct objc_super *, struct objc_selector *, ...);
__OBJC_RW_DLLIMPORT double objc_msgSend_fpret(struct objc_object *, struct objc_selector *, ...);
__OBJC_RW_DLLIMPORT struct objc_object *objc_getClass(const char *);
__OBJC_RW_DLLIMPORT struct objc_class *class_getSuperclass(struct objc_class *);
__OBJC_RW_DLLIMPORT struct objc_object *objc_getMetaClass(const char *);
__OBJC_RW_DLLIMPORT void objc_exception_throw(struct objc_object *);
__OBJC_RW_DLLIMPORT void objc_exception_try_enter(void *);
__OBJC_RW_DLLIMPORT void objc_exception_try_exit(void *);
__OBJC_RW_DLLIMPORT struct objc_object *objc_exception_extract(void *);
__OBJC_RW_DLLIMPORT int objc_exception_match(struct objc_class *, struct objc_object *);
__OBJC_RW_DLLIMPORT void objc_sync_enter(struct objc_object *);
__OBJC_RW_DLLIMPORT void objc_sync_exit(struct objc_object *);
__OBJC_RW_DLLIMPORT Protocol *objc_getProtocol(const char *);

ifndef __FASTENUMERATIONSTATE

struct __objcFastEnumerationState {
unsigned long state;
void **itemsPtr;
unsigned long *mutationsPtr;
unsigned long extra[5];
};
__OBJC_RW_DLLIMPORT void objc_enumerationMutation(struct objc_object *);

define __FASTENUMERATIONSTATE

endif

ifndef __NSCONSTANTSTRINGIMPL

struct __NSConstantStringImpl {
int *isa;
int flags;
char *str;
long length;
};

ifdef CF_EXPORT_CONSTANT_STRING

extern "C" __declspec(dllexport) int __CFConstantStringClassReference[];

else

__OBJC_RW_DLLIMPORT int __CFConstantStringClassReference[];

endif

define __NSCONSTANTSTRINGIMPL

endif

ifndef BLOCK_IMPL

define BLOCK_IMPL

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// Runtime copy/destroy helper functions (from Block_private.h)

ifdef __OBJC_EXPORT_BLOCKS

extern "C" __declspec(dllexport) void _Block_object_assign(void *, const void *, const int);
extern "C" __declspec(dllexport) void _Block_object_dispose(const void *, const int);
extern "C" __declspec(dllexport) void *_NSConcreteGlobalBlock[32];
extern "C" __declspec(dllexport) void *_NSConcreteStackBlock[32];

else

__OBJC_RW_DLLIMPORT void _Block_object_assign(void *, const void *, const int);
__OBJC_RW_DLLIMPORT void _Block_object_dispose(const void *, const int);
__OBJC_RW_DLLIMPORT void *_NSConcreteGlobalBlock[32];
__OBJC_RW_DLLIMPORT void *_NSConcreteStackBlock[32];

endif

endif

define __block

define __weak

define OFFSETOFIVAR(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)

static __NSConstantStringImpl __NSConstantStringImpl_main_m_0 attribute ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"FileNotFoundException",21};
static __NSConstantStringImpl __NSConstantStringImpl_main_m_1 attribute ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"File Not Found on System",24};
static __NSConstantStringImpl __NSConstantStringImpl_main_m_2 attribute ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"%@",2};
static __NSConstantStringImpl __NSConstantStringImpl_main_m_3 attribute ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"finally",7};
//
// main.c
// TestBlock
//
// Created by xxxx on 13-6-2.
// Copyright (c) 2013 xxxx. All rights reserved.
//

include <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{
@autoreleasepool
{
/* @try scope begin /
{
struct _objc_exception_data
{
int buf[18/
32-bit i386*/];
char *pointers[4];
} _stack;

        id volatile _rethrow = 0;
        objc_exception_try_enter(&_stack);
        
        if (!_setjmp(_stack.buf)) /* @try block continue */
        {
            NSException *e = ((NSException *(*)(id, SEL, NSString *, NSString *, NSDictionary *))(void *)objc_msgSend)(objc_getClass("NSException"), sel_registerName("exceptionWithName:reason:userInfo:"), (NSString *)&__NSConstantStringImpl_main_m_0, (NSString *)&__NSConstantStringImpl_main_m_1, (NSDictionary *)((void *)0));
            
            objc_exception_throw(e);
            
        } /* @catch begin */ else {
            
            id _caught = objc_exception_extract(&_stack);
            objc_exception_try_enter (&_stack);
            
            if (_setjmp(_stack.buf))
                _rethrow = objc_exception_extract(&_stack);
            
            else { /* @catch continue */
                if (objc_exception_match((struct objc_class *)objc_getClass("NSException"), (struct objc_object *)_caught)) {
                    NSException *exception = _caught;
                    
                    if (((BOOL (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)exception, sel_registerName("name")), sel_registerName("isEqualToString:"), (NSString *)NSInvalidArgumentException)) {
                        
                        NSLog((NSString *)&__NSConstantStringImpl_main_m_2, exception);
                    } else {
                        
                        objc_exception_throw( exception);
                    }
                    
                } /* last catch end */ else {
                    _rethrow = _caught;
                    objc_exception_try_exit(&_stack);
                }
            } /* @catch end */
        }
        /* @finally */
        {
            if (!_rethrow) objc_exception_try_exit(&_stack);

            NSLog((NSString *)&__NSConstantStringImpl_main_m_3);
            
            if (_rethrow) objc_exception_throw(_rethrow);
        }
        
    } /* @try scope end */

}
return 0;

}
復(fù)制代碼

文件中信息量太大辫封,咱們只看main函數(shù)部分惊楼,下面的代碼把main函數(shù)的代碼作了注釋說明:

復(fù)制代碼

include <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{
@autoreleasepool
{
/**
* try/catch的作用域從這里開始
/
/
@try scope begin /
{
/
*
* 首先定義一個(gè)_objc_exception_data類型的結(jié)構(gòu)體,用來保存異辰斩铮現(xiàn)場的數(shù)據(jù)檀咙。
/
struct _objc_exception_data
{
/
*
* buf變量就是c語言中的jmp_buf
* jmp_buf的定義可在setjmp.h文件中找到:
*
* #define _JBLEN (10 + 16 + 2)
* #define _JBLEN_MAX _JBLEN
*
* typedef int jmp_buf[_JBLEN];
/
int buf[18/
32-bit i386*/];

            /**
             * pointers[0]用來存儲通過@throw拋出的異常對象,
             * pointers[1]存儲下一個(gè)_stack數(shù)據(jù)璃诀。
             */
            char *pointers[4];
        } _stack;
        
        /**
         * _rethrow保存可能在@catch中再次拋出的異常對象弧可。
         */
        id volatile _rethrow = 0;
        
        /**
         * 因?yàn)楫惓L幚碇С智短祝琠stack會被存儲在一個(gè)全局的棧中劣欢,這個(gè)棧用單鏈表的存儲結(jié)構(gòu)表示棕诵。
         * objc_exception_try_enter函數(shù)將_stack壓棧。
         */
        objc_exception_try_enter(&_stack);
        
        /**
         * _setjmp是C的函數(shù)凿将,用于保存當(dāng)前程序現(xiàn)場校套。
         * _setjmp需要傳入一個(gè)jmp_buf參數(shù),保存當(dāng)前需要用到的寄存器的值牧抵。
         * _setjmp()它能返回兩次笛匙,第一次是初始化時(shí),返回0犀变,第二次遇到_longjmp()函數(shù)調(diào)用會返回妹孙,返回值由_longjmp的第二個(gè)參數(shù)決定。
         * 如果對_setjmp()和_longjmp()概念不太了解的获枝,請參考C語言的異常處理機(jī)制蠢正。
         *
         * 下面_setjmp()初始化返回0,然后執(zhí)行if{}中也就是@try{}中的代碼省店。
         */
        if (!_setjmp(_stack.buf)) /* @try block continue */
        {
            /**
             * 創(chuàng)建一個(gè)NSException對象嚣崭,對應(yīng)代碼:
             * 
             *             NSException *e = [NSException
             *                               exceptionWithName:@"FileNotFoundException"
             *                               reason:@"File Not Found on System"
             *                               userInfo:nil];
             */
            NSException *e = ((NSException *(*)(id, SEL, NSString *, NSString *, NSDictionary *))(void *)objc_msgSend)(objc_getClass("NSException"), sel_registerName("exceptionWithName:reason:userInfo:"), (NSString *)&__NSConstantStringImpl_main_m_0, (NSString *)&__NSConstantStringImpl_main_m_1, (NSDictionary *)((void *)0));
            
            /**
             * 拋出異常對象,對應(yīng)代碼:@throw e;
             * 
             * objc_exception_throw函數(shù)實(shí)現(xiàn)步驟如下:
             * 1. 把e對象保存到_stack->pointers[0]中使其在@catch{}中能被捕獲懦傍。
             * 2. 將_stack從全局棧中彈出雹舀。
             * 3. 調(diào)用_longjmp()跳轉(zhuǎn)到前面if語句中的_setjmp()位置。_longjmp()使得_setjmp()函數(shù)第二次返回谎脯,
             * 返回值為1葱跋,所以會執(zhí)行else{}中也就是@catch{}中的代碼持寄。
             */
            objc_exception_throw(e);
            
        } /* @catch begin */ else {
            
            /**
             * objc_exception_extract函數(shù)從_stack->pointers[0]中取得上面拋出的異常對象源梭。
             */
            id _caught = objc_exception_extract(&_stack);
            
            /**
             * 這里為何再次調(diào)用objc_exception_try_enter對_stack壓棧娱俺?先保留這個(gè)疑問,繼續(xù)看下面的代碼废麻。
             */
            objc_exception_try_enter (&_stack);
            
            /**
             * 在@catch中設(shè)置一個(gè)跳轉(zhuǎn)位置
             */
            if (_setjmp(_stack.buf))
                
                /**
                 * 如果@catch{}中再次拋出異常荠卷,在這里捕獲。
                 */
                _rethrow = objc_exception_extract(&_stack);
            
            else { /* @catch continue */
                
                /**
                 * objc_exception_match函數(shù)判斷_caught對象是否是需要捕獲的目標(biāo)對象烛愧。對應(yīng)代碼:
                 * 
                 * @catch (NSException *exception) {
                 */
                if (objc_exception_match((struct objc_class *)objc_getClass("NSException"), (struct objc_object *)_caught)) {
                    NSException *exception = _caught;
                    
                    /**
                     * 比較捕獲的異常是不是NSInvalidArgumentException類型油宜。對應(yīng)代碼:
                     *
                     * if ([[exception name] isEqualToString:NSInvalidArgumentException]) {
                     *      NSLog(@"%@", exception);
                     *
                     */ 
                    if (((BOOL (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)exception, sel_registerName("name")), sel_registerName("isEqualToString:"), (NSString *)NSInvalidArgumentException)) {
                        
                        NSLog((NSString *)&__NSConstantStringImpl_main_m_2, exception);
                    } else {
                        
                        /**
                         * 拋出異常對象,然后跳轉(zhuǎn)到前面@catch中的if語句中的_setjmp()位置怜姿。
                         * 這就解釋了前面為什么要在@catch中再次將_stack壓棧和調(diào)用_setjmp()的原因慎冤。
                         * 在當(dāng)前@catch中,如果不設(shè)置一個(gè)跳轉(zhuǎn)點(diǎn)來捕獲@catch中拋出的異常沧卢,那么程序就直接跳轉(zhuǎn)到全局棧的下一個(gè)@catch中蚁堤,而下面的@finally{}代碼就無法執(zhí)行。
                         * 在@catch中設(shè)置跳轉(zhuǎn)點(diǎn)就是為了最后總能執(zhí)行@finally中的代碼但狭。
                         */
                        objc_exception_throw( exception);
                    }
                    
                } /* last catch end */ else {
                    
                    /**
                     * 如果異常對象沒被處理披诗,先將其保存到_rethrow變量。
                     * objc_exception_try_exit函數(shù)將_stack從全局棧中彈出立磁。
                     */
                    _rethrow = _caught;
                    objc_exception_try_exit(&_stack);
                }
            } /* @catch end */
        }
        /* @finally */
        {
            if (!_rethrow) objc_exception_try_exit(&_stack);

            NSLog((NSString *)&__NSConstantStringImpl_main_m_3);
            
            /**
             * _rethrow是前面@catch中沒有被處理的或被捕獲的異常對象呈队,
             * 最后,_rethrow異常對象被拋到全局棧的下一個(gè)@catch中唱歧。
             */
            if (_rethrow) objc_exception_throw(_rethrow);
        }
        
    } /* @try scope end */

}
return 0;

}
復(fù)制代碼

以上代碼中還涉及了objc_exception_try_enter宪摧、objc_exception_extract、objc_exception_throw颅崩、objc_exception_try_exit等函數(shù)绍刮,這些函數(shù)可以在蘋果開源的objc4的objc-exception.mm文件中找到,objc4源碼可在這里下載挨摸。下面代碼只顯示部分方便閱讀:

復(fù)制代碼
typedef struct {
int version;
void (throw_exc)(id); // version 0
void (
try_enter)(void ); // version 0
void (
try_exit)(void ); // version 0
id (
extract)(void ); // version 0
int (
match)(Class, id); // version 0
} objc_exception_functions_t;

static objc_exception_functions_t xtab;

// forward declaration
static void set_default_handlers();

/*

  • Exported functions
    */

// get table; version tells how many
void objc_exception_get_functions(objc_exception_functions_t *table) {
// only version 0 supported at this point
if (table && table->version == 0)
*table = xtab;
}

// set table
void objc_exception_set_functions(objc_exception_functions_t *table) {
// only version 0 supported at this point
if (table && table->version == 0)
xtab = *table;
}

/*

  • The following functions are
  • synthesized by the compiler upon encountering language constructs
    */

void objc_exception_throw(id exception) {
if (!xtab.throw_exc) {
set_default_handlers();
}

if (PrintExceptionThrow) {
    _objc_inform("EXCEPTIONS: throwing %p (%s)", 
                 exception, object_getClassName(exception));
    void* callstack[500];
    int frameCount = backtrace(callstack, 500);
    backtrace_symbols_fd(callstack, frameCount, fileno(stderr));
}

OBJC_RUNTIME_OBJC_EXCEPTION_THROW(exception);  // dtrace probe to log throw activity.
xtab.throw_exc(exception);
_objc_fatal("objc_exception_throw failed");

}

void objc_exception_try_enter(void *localExceptionData) {
if (!xtab.throw_exc) {
set_default_handlers();
}
xtab.try_enter(localExceptionData);
}

void objc_exception_try_exit(void *localExceptionData) {
if (!xtab.throw_exc) {
set_default_handlers();
}
xtab.try_exit(localExceptionData);
}

id objc_exception_extract(void *localExceptionData) {
if (!xtab.throw_exc) {
set_default_handlers();
}
return xtab.extract(localExceptionData);
}

int objc_exception_match(Class exceptionClass, id exception) {
if (!xtab.throw_exc) {
set_default_handlers();
}
return xtab.match(exceptionClass, exception);
}

// quick and dirty exception handling code
// default implementation - mostly a toy for use outside/before Foundation
// provides its implementation
// Perhaps the default implementation should just complain loudly and quit

extern void _objc_inform(const char *fmt, ...);

typedef struct { jmp_buf buf; void *pointers[4]; } LocalData_t;

typedef struct _threadChain {
LocalData_t *topHandler;
objc_thread_t perThreadID;
struct _threadChain *next;
}
ThreadChainLink_t;

static ThreadChainLink_t ThreadChainLink;

static ThreadChainLink_t *getChainLink() {
// follow links until thread_self() found (someday) XXX
objc_thread_t self = thread_self();
ThreadChainLink_t *walker = &ThreadChainLink;
while (walker->perThreadID != self) {
if (walker->next != NULL) {
walker = walker->next;
continue;
}
// create a new one
// XXX not thread safe (!)
// XXX Also, we don't register to deallocate on thread death
walker->next = (ThreadChainLink_t *)malloc(sizeof(ThreadChainLink_t));
walker = walker->next;
walker->next = NULL;
walker->topHandler = NULL;
walker->perThreadID = self;
}
return walker;
}

static void default_try_enter(void *localExceptionData) {
LocalData_t *data = (LocalData_t *)localExceptionData;
ThreadChainLink_t *chainLink = getChainLink();
data->pointers[1] = chainLink->topHandler;
chainLink->topHandler = data;
if (PrintExceptions) _objc_inform("EXCEPTIONS: entered try block %p\n", chainLink->topHandler);
}

static void default_throw(id value) {
ThreadChainLink_t *chainLink = getChainLink();
LocalData_t *led;
if (value == nil) {
if (PrintExceptions) _objc_inform("EXCEPTIONS: objc_exception_throw with nil value\n");
return;
}
if (chainLink == NULL) {
if (PrintExceptions) _objc_inform("EXCEPTIONS: No handler in place!\n");
return;
}
if (PrintExceptions) _objc_inform("EXCEPTIONS: exception thrown, going to handler block %p\n", chainLink->topHandler);
led = chainLink->topHandler;
chainLink->topHandler = (LocalData_t *)
led->pointers[1]; // pop top handler
led->pointers[0] = value; // store exception that is thrown

if TARGET_OS_WIN32

longjmp(led->buf, 1);

else

_longjmp(led->buf, 1);

endif

}

static void default_try_exit(void *led) {
ThreadChainLink_t *chainLink = getChainLink();
if (!chainLink || led != chainLink->topHandler) {
if (PrintExceptions) _objc_inform("EXCEPTIONS: *** mismatched try block exit handlers\n");
return;
}
if (PrintExceptions) _objc_inform("EXCEPTIONS: removing try block handler %p\n", chainLink->topHandler);
chainLink->topHandler = (LocalData_t *)
chainLink->topHandler->pointers[1]; // pop top handler
}

static id default_extract(void *localExceptionData) {
LocalData_t *led = (LocalData_t *)localExceptionData;
return (id)led->pointers[0];
}

static int default_match(Class exceptionClass, id exception) {
//return [exception isKindOfClass:exceptionClass];
Class cls;
for (cls = _object_getClass(exception); nil != cls; cls = _class_getSuperclass(cls))
if (cls == exceptionClass) return 1;
return 0;
}

static void set_default_handlers() {
objc_exception_functions_t default_functions = {
0, default_throw, default_try_enter, default_try_exit, default_extract, default_match };

// should this always print?
if (PrintExceptions) _objc_inform("EXCEPTIONS: *** Setting default (non-Foundation) exception mechanism\n");
objc_exception_set_functions(&default_functions);

}

void exception_init(void)
{
// nothing to do
}

void _destroyAltHandlerList(struct alt_handler_list *list)
{
// nothing to do
}

// !OBJC2

else

// OBJC2
復(fù)制代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末孩革,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子得运,更是在濱河造成了極大的恐慌膝蜈,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熔掺,死亡現(xiàn)場離奇詭異饱搏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)置逻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門推沸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事鬓催》嗡兀” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵宇驾,是天一觀的道長倍靡。 經(jīng)常有香客問我,道長课舍,這世上最難降的妖魔是什么塌西? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮筝尾,結(jié)果婚禮上捡需,老公的妹妹穿的比我還像新娘。我一直安慰自己筹淫,他們只是感情好栖忠,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贸街,像睡著了一般庵寞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上薛匪,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天捐川,我揣著相機(jī)與錄音,去河邊找鬼逸尖。 笑死古沥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的娇跟。 我是一名探鬼主播岩齿,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼苞俘!你這毒婦竟也來了盹沈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤吃谣,失蹤者是張志新(化名)和其女友劉穎乞封,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岗憋,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肃晚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仔戈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片关串。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拧廊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晋修,到底是詐尸還是另有隱情吧碾,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布飞蚓,位于F島的核電站滤港,受9級特大地震影響廊蜒,放射性物質(zhì)發(fā)生泄漏趴拧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一山叮、第九天 我趴在偏房一處隱蔽的房頂上張望著榴。 院中可真熱鬧,春花似錦屁倔、人聲如沸脑又。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽问麸。三九已至,卻和暖如春钞翔,著一層夾襖步出監(jiān)牢的瞬間严卖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工布轿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哮笆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓汰扭,卻偏偏與公主長得像稠肘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子萝毛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉项阴,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,686評論 0 9
  • 消息發(fā)送和轉(zhuǎn)發(fā)流程可以概括為:消息發(fā)送(Messaging)是 Runtime 通過 selector 快速查找 ...
    lylaut閱讀 1,825評論 2 3
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法笆包,內(nèi)部類的語法鲁冯,繼承相關(guān)的語法,異常的語法色查,線程的語...
    子非魚_t_閱讀 31,598評論 18 399
  • 接著上節(jié) condition_varible 薯演,本節(jié)主要介紹future的內(nèi)容,練習(xí)代碼地址秧了。本文參考http:/...
    jorion閱讀 14,776評論 1 5
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言跨扮,那么這個(gè)「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,182評論 0 7