Mantle——iOS 模型 & 字典轉換框架

Mantle——iOS 模型 & 字典轉換框架

Mantle 是 iOS 和 Mac 平臺下基于 Objective-C 編寫的一個簡單高效的模型層框架戒傻。

典型的模型對象

通常情況下瓦戚,用 Objective-C 編寫模型對象的方式存在哪些問題?

讓我們用 GitHub API 進行演示纬凤。在 Objective-C 中捐寥,如何用一個模型來表示 GitHub
issue
笤昨?

typedef enum : NSUInteger {
    GHIssueStateOpen,
    GHIssueStateClosed
} GHIssueState;

@interface GHIssue : NSObject <NSCoding, NSCopying>

@property (nonatomic, copy, readonly) NSURL *URL;
@property (nonatomic, copy, readonly) NSURL *HTMLURL;
@property (nonatomic, copy, readonly) NSNumber *number;
@property (nonatomic, assign, readonly) GHIssueState state;
@property (nonatomic, copy, readonly) NSString *reporterLogin;
@property (nonatomic, copy, readonly) NSDate *updatedAt;
@property (nonatomic, strong, readonly) GHUser *assignee;
@property (nonatomic, copy, readonly) NSDate *retrievedAt;

@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *body;

- (id)initWithDictionary:(NSDictionary *)dictionary;

@end
@implementation GHIssue

+ (NSDateFormatter *)dateFormatter {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
    dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'Z'";
    return dateFormatter;
}

- (id)initWithDictionary:(NSDictionary *)dictionary {
    self = [self init];
    if (self == nil) return nil;

    _URL = [NSURL URLWithString:dictionary[@"url"]];
    _HTMLURL = [NSURL URLWithString:dictionary[@"html_url"]];
    _number = dictionary[@"number"];

    if ([dictionary[@"state"] isEqualToString:@"open"]) {
        _state = GHIssueStateOpen;
    } else if ([dictionary[@"state"] isEqualToString:@"closed"]) {
        _state = GHIssueStateClosed;
    }

    _title = [dictionary[@"title"] copy];
    _retrievedAt = [NSDate date];
    _body = [dictionary[@"body"] copy];
    _reporterLogin = [dictionary[@"user"][@"login"] copy];
    _assignee = [[GHUser alloc] initWithDictionary:dictionary[@"assignee"]];

    _updatedAt = [self.class.dateFormatter dateFromString:dictionary[@"updated_at"]];

    return self;
}

- (id)initWithCoder:(NSCoder *)coder {
    self = [self init];
    if (self == nil) return nil;

    _URL = [coder decodeObjectForKey:@"URL"];
    _HTMLURL = [coder decodeObjectForKey:@"HTMLURL"];
    _number = [coder decodeObjectForKey:@"number"];
    _state = [coder decodeUnsignedIntegerForKey:@"state"];
    _title = [coder decodeObjectForKey:@"title"];
    _retrievedAt = [NSDate date];
    _body = [coder decodeObjectForKey:@"body"];
    _reporterLogin = [coder decodeObjectForKey:@"reporterLogin"];
    _assignee = [coder decodeObjectForKey:@"assignee"];
    _updatedAt = [coder decodeObjectForKey:@"updatedAt"];

    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
    if (self.URL != nil) [coder encodeObject:self.URL forKey:@"URL"];
    if (self.HTMLURL != nil) [coder encodeObject:self.HTMLURL forKey:@"HTMLURL"];
    if (self.number != nil) [coder encodeObject:self.number forKey:@"number"];
    if (self.title != nil) [coder encodeObject:self.title forKey:@"title"];
    if (self.body != nil) [coder encodeObject:self.body forKey:@"body"];
    if (self.reporterLogin != nil) [coder encodeObject:self.reporterLogin forKey:@"reporterLogin"];
    if (self.assignee != nil) [coder encodeObject:self.assignee forKey:@"assignee"];
    if (self.updatedAt != nil) [coder encodeObject:self.updatedAt forKey:@"updatedAt"];

    [coder encodeUnsignedInteger:self.state forKey:@"state"];
}

- (id)copyWithZone:(NSZone *)zone {
    GHIssue *issue = [[self.class allocWithZone:zone] init];
    issue->_URL = self.URL;
    issue->_HTMLURL = self.HTMLURL;
    issue->_number = self.number;
    issue->_state = self.state;
    issue->_reporterLogin = self.reporterLogin;
    issue->_assignee = self.assignee;
    issue->_updatedAt = self.updatedAt;

    issue.title = self.title;
    issue->_retrievedAt = [NSDate date];
    issue.body = self.body;

    return issue;
}

- (NSUInteger)hash {
    return self.number.hash;
}

- (BOOL)isEqual:(GHIssue *)issue {
    if (![issue isKindOfClass:GHIssue.class]) return NO;

    return [self.number isEqual:issue.number] && [self.title isEqual:issue.title] && [self.body isEqual:issue.body];
}

@end

哇,這么簡單的事情就編寫了很多樣板代碼握恳!而且瞒窒,即使如此,此示例仍無法解決一些問題:

  • 無法使用服務器的新數(shù)據(jù)更新 GHIssue 對象乡洼。
  • 無法反過來將 GHIssue 對象轉換回 JSON 模型崇裁。
  • GHIssueState 不應原樣編碼。如果這個枚舉類型將來發(fā)生了變更束昵,則現(xiàn)有的歸檔會崩潰(無法向下兼容)拔稳。
  • 如果 GHIssue 的接口未來發(fā)生變化,則現(xiàn)有的歸檔會崩潰(無法向下兼容)锹雏。

為什么不使用 Core Data?

Core Data 很好地解決了某些問題巴比。如果你需要對數(shù)據(jù)執(zhí)行復雜的查詢,處理具有大量關系的巨大對象圖或支持撤消和重做礁遵,那么 Core Data 是一個很好的選擇轻绞。

但是,它確實也有一些痛點:

  • 仍然需要編寫很多樣板代碼佣耐。管理對象減少了上面看到的一些樣板代碼政勃,但是 Core Data 有很多自己的東西。正確設置 Core Data 堆棧(持久性存儲和持久性存儲協(xié)調器)并執(zhí)行提取操作可能也需要編寫不少代碼兼砖。
  • 它很難正確工作奸远。即使是經驗豐富的開發(fā)人員既棺,在使用 Core Data 時也會犯錯,并且該框架也讓人難以忍受懒叛。

如果你只是想嘗試訪問 JSON 對象援制,Core Data 可能需要耗費很多功夫而收效甚微(投入大于收益,不劃算)芍瑞。

盡管如此,如果你已經在應用程序中使用或想要使用 Core Data褐墅,Mantle 仍然可以是 API 和模型對象之間的便捷轉換層拆檬。

MTLModel

使用 MTLModel。這是繼承自 MTLModel 對象的 GHIssue 對象示例:

typedef enum : NSUInteger {
    GHIssueStateOpen,
    GHIssueStateClosed
} GHIssueState;

// !!!: 必須遵守 <MTLJSONSerializing> 協(xié)議
@interface GHIssue : MTLModel <MTLJSONSerializing>

@property (nonatomic, copy, readonly) NSURL *URL;     // URL 類型
@property (nonatomic, copy, readonly) NSURL *HTMLURL; // URL 類型
@property (nonatomic, copy, readonly) NSNumber *number;
@property (nonatomic, assign, readonly) GHIssueState state; // 枚舉類型
@property (nonatomic, copy, readonly) NSString *reporterLogin;
@property (nonatomic, strong, readonly) GHUser *assignee; // 該屬性指向 GHUser 對象實例
@property (nonatomic, copy, readonly) NSDate *updatedAt;  // JSON 日期字符串妥凳,轉換為 NSDate

@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *body;

@property (nonatomic, copy, readonly) NSDate *retrievedAt;

@end
@implementation GHIssue

+ (NSDateFormatter *)dateFormatter {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
    dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'Z'";
    return dateFormatter;
}

// 模型和 JSON 的自定義映射
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
        @"URL"           : @"url",
        @"HTMLURL"       : @"html_url",
        @"number"        : @"number",
        @"state"         : @"state",
        @"reporterLogin" : @"user.login",
        @"assignee"      : @"assignee",
        @"updatedAt"     : @"updated_at"
    };
}

// 自定義 JSON 模型轉換竟贯,URL -> NSURL
+ (NSValueTransformer *)URLJSONTransformer {
    return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
}

// 自定義 JSON 模型轉換,URL -> NSURL
+ (NSValueTransformer *)HTMLURLJSONTransformer {
    return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
}

// 自定義 JSON 模型轉換逝钥,JSON 字符串 -> 枚舉類型
+ (NSValueTransformer *)stateJSONTransformer {
    return [NSValueTransformer mtl_valueMappingTransformerWithDictionary:@{
        @"open": @(GHIssueStateOpen),
        @"closed": @(GHIssueStateClosed)
    }];
}

// assignee 屬性是一個 GHUser 對象實例
+ (NSValueTransformer *)assigneeJSONTransformer {
    return [MTLJSONAdapter dictionaryTransformerWithModelClass:GHUser.class];
}

// 自定義 JSON 模型轉換屑那,JSON 字符串 -> NSDate
+ (NSValueTransformer *)updatedAtJSONTransformer {
    return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *dateString, BOOL *success, NSError *__autoreleasing *error) {
        // 自定義 JSON 轉模型方式
        return [self.dateFormatter dateFromString:dateString];
    } reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) {
        // 自定義模型轉 JSON 方式
        return [self.dateFormatter stringFromDate:date];
    }];
}

- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error {
    self = [super initWithDictionary:dictionaryValue error:error];
    if (self == nil) return nil;

    // 存儲需要在初始化時由本地確定的值
    _retrievedAt = [NSDate date];

    return self;
}

@end

此版本中明顯沒有 <NSCoding>
<NSCopying>艘款,-isEqual:持际,和 -hash 的方法實現(xiàn)。通過檢查子類中的 @property 屬性聲明哗咆,MTLModel 可以為所有這些方法提供默認實現(xiàn)蜘欲。

原始示例中的問題也都被修復了:

無法使用服務器中的新數(shù)據(jù)更新 GHIssue 對象。

MTLModel 擴展了一個的 -mergeValuesForKeys: FromModel:方法晌柬,可以與其他任何實現(xiàn)了<MTLModel> 協(xié)議的模型對象集成姥份。

無法將 GHIssue 模型轉換回 JSON 對象。

這就是反向轉換器真正派上用場的地方年碘。
+[MTLJSONAdapter JSONDictionaryFromModel:error:] 可以把任何遵守 <MTLJSONSerializing> 協(xié)議的模型對象轉換回 JSON 字典澈歉。
+[MTLJSONAdapter JSONArrayFromModels:error:] 是同樣的,但是它是將包含模型對象的數(shù)組轉換為 JSON 數(shù)組屿衅。

如果 GHIssue 的接口發(fā)生變化埃难,則現(xiàn)有存檔可能會無法工作。

MTLModel 會自動保存用于歸檔的模型對象的版本傲诵。當解檔時凯砍,如果覆寫了 -decodeValueForKey:withCoder:modelVersion: 方法,它會被自動調用拴竹,從而為你提供方便的掛鉤(hook)來升級舊數(shù)據(jù)悟衩。

MTLJSONSerializing - 模型和 JSON 的相互轉換

為了將模型對象從 JSON 序列化或序列化為 JSON,你需要在自定義的 MTLModel 子類對象中聲明該子類對象遵守<MTLJSONSerializing> 協(xié)議栓拜。這樣就可以使用 MTLJSONAdapter 將模型對象從 JSON 轉換回來:

// JSON -> Model
NSError *error = nil;
XYUser *user = [MTLJSONAdapter modelOfClass:XYUser.class fromJSONDictionary:JSONDictionary error:&error];
// Model -> JSON
NSError *error = nil;
NSDictionary *JSONDictionary = [MTLJSONAdapter JSONDictionaryFromModel:user error:&error];

+JSONKeyPathsByPropertyKey - 實現(xiàn)模型和 JSON 的自定義映射

此方法返回的 NSDictionary 字典用于指定如何將模型對象的屬性映射到 JSON 的鍵上座泳。

@interface XYUser : MTLModel <MTLJSONSerializing>

@property (readonly, nonatomic, copy) NSString *name;
@property (readonly, nonatomic, strong) NSDate *createdAt;

@property (readonly, nonatomic, assign, getter = isMeUser) BOOL meUser;
@property (readonly, nonatomic, strong) XYHelper *helper;

@end

@implementation XYUser

// 模型和 JSON 的自定義映射
// 將模型對象的屬性名稱與 JSON 對象的 key 名稱進行映射惠昔。
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
        @"name": @"name",
        @"createdAt": @"created_at"
    };
}

- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error {
    self = [super initWithDictionary:dictionaryValue error:error];
    if (self == nil) return nil;

    _helper = [XYHelper helperWithName:self.name createdAt:self.createdAt];

    return self;
}

@end

在此示例中,XYUser 類聲明了 Mantle 需要以不同方式處理的四個屬性:

  • name 屬性被映射到了 JSON 中相同名稱的鍵上挑势。
  • createdAt 屬性映射到了其等效的 snack 語法格式的鍵上镇防。
  • meUser 屬性沒有序列化為 JSON。
  • JSON 反序列化后潮饱,helper 屬性會在本地被初始化来氧。

如果模型的父類還遵守了 <MTLJSONSerializing> 協(xié)議,則使用 -[NSDictionary mtl_dictionaryByAddingEntriesFromDictionary:] 來合并其映射香拉。

如果你想將模型類的所有屬性映射到它們自己啦扬,則可以使用+[NSDictionary mtl_identityPropertyMapWithModel:] 輔助方法。

使用 +[MTLJSONAdapter modelOfClass:fromJSONDictionary:error:] 方法反序列化 JSON 時凫碌,與屬性名稱不對應或具有顯式映射的 JSON 將被忽略:

NSDictionary *JSONDictionary = @{
    @"name": @"john",
    @"created_at": @"2013/07/02 16:40:00 +0000",
    @"plan": @"lite"
};

NSError *error = nil;
XYUser *user = [MTLJSONAdapter modelOfClass:XYUser.class
                         fromJSONDictionary:JSONDictionary
                                      error:&error];
/**
 <XYUser: 0x280d99170> {
    helper = <XYHelper: 0x2803c99e0> {
    name = john,
    createdAt = 2013-07-02 16:40:00 +0000
}
*/

該示例中扑毡, plan 字段將會被忽略,因為它既不匹配 XYUser 的屬性名稱盛险,也不映射到+JSONKeyPathsByPropertyKey 中瞄摊。

+JSONTransformerForKey: - 對 JSON 和模型不同類型手動進行映射

從 JSON 反序列化時,實現(xiàn)這個 <MTLJSONSerializing> 協(xié)議中可選的方法以將屬性轉換為其他類型苦掘。

??

將 JSON 對象轉換為模型對象時换帜,如果 JSON 對象的數(shù)據(jù)類型和模型對象的數(shù)據(jù)類型不一致,或者無法實現(xiàn)自動轉換時鸟蜡,需要通過以下的方法進行手動轉換膜赃。

+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key;

此方法支持批量的自定義映射!通過判斷屬性名 key 的不同揉忘,可以實現(xiàn)多個屬性的自定義映射操作跳座。

// 注意:該方法中的局部參數(shù) key 指的是「模型對象」中的屬性名稱。
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key {
    if ([key isEqualToString:@"createdAt"]) {
        // 當處理 createdAt 屬性的映射時泣矛,執(zhí)行自定義轉換
        return [NSValueTransformer valueTransformerForName:XYDateValueTransformerName];
    }

    return nil;
}

key 是應用于模型對象的屬性名疲眷;不是原始的 JSON 中的鍵。如果你使用 +JSONKeyPathsByPropertyKey 轉換時您朽,請記住這一點狂丝。

為了更加方便,如果你實現(xiàn)了 +<key>JSONTransformer 方法哗总,那么 MTLJSONAdapter 將改用該方法的結果几颜。例如,JSON 中通常表示為字符串的日期可以轉換為 NSDate讯屈,如下所示:

// 自定義 JSON 模型轉換蛋哭,JSON 字符串 -> NSDate
+ (NSValueTransformer *)updatedAtJSONTransformer {
    return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *dateString, BOOL *success, NSError *__autoreleasing *error) {
        return [self.dateFormatter dateFromString:dateString];
    } reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) {
        return [self.dateFormatter stringFromDate:date];
    }];
}

如果轉換器是可逆的,則在將對象序列化為 JSON 時也將使用它涮母。

??

也就是說谆趾,屬性的自定義轉換支付兩種方法躁愿,一種是:

+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key;

它支持批量的自定義映射操作。

還有一種是單個屬性的自定義映射方法沪蓬,即:

+<key>JSONTransformer;

這邊的 <key> 是模型對象中屬性的名字彤钟。以上面的 GHIssue 例子來說,GHIssue 對象中的第一個屬性 URLNSURL 類型的屬性跷叉,而 JSON 模型返回的 URL 鏈接是一個字符串類型逸雹,它們之間的數(shù)據(jù)類型不一致,因此這個屬性無法實現(xiàn)自動轉換云挟,需要手動實現(xiàn)峡眶,即:

// 自定義 JSON 模型轉換,URL -> NSURL
// 這個方法中的 <key> 就是 URL植锉,即模型中的 URL 屬性。
+ (NSValueTransformer *)URLJSONTransformer {
    return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
}

也就是說每個單獨實現(xiàn)的自定義轉換方法名是通過模型屬性名與 JSONTransformer 拼接而來的峭拘。

另外俊庇,這個 “拼接形式” 的自定義模型轉換方法的優(yōu)先級比 JSONTransformerForKey: 要高!也就是說鸡挠,如果兩個方法中都實現(xiàn)了某一個屬性的自定義 JSON 模型轉換辉饱,則以 +<key>JSONTransformer; 方法的實現(xiàn)為準!

+classForParsingJSONDictionary:

如果你使用了類簇拣展,請實現(xiàn)此可選方法彭沼,classForParsingJSONDictionary 可以讓你選擇使用哪一個類進行 JSON 反序列化。

@interface XYMessage : MTLModel

@end

@interface XYTextMessage: XYMessage

@property (readonly, nonatomic, copy) NSString *body;

@end

@interface XYPictureMessage : XYMessage

@property (readonly, nonatomic, strong) NSURL *imageURL;

@end

@implementation XYMessage

+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary {
    if (JSONDictionary[@"image_url"] != nil) {
        return XYPictureMessage.class;
    }

    if (JSONDictionary[@"body"] != nil) {
        return XYTextMessage.class;
    }

    NSAssert(NO, @"No matching class for the JSON dictionary '%@'.", JSONDictionary);
    return self;
}

@end

然后备埃,MTLJSONAdapter 會根據(jù)你傳入的 JSON 字典自動選擇類:

NSDictionary *textMessage = @{
    @"id": @1,
    @"body": @"Hello World!"
};

NSDictionary *pictureMessage = @{
    @"id": @2,
    @"image_url": @"http://example.com/lolcat.gif"
};

XYTextMessage *messageA = [MTLJSONAdapter modelOfClass:XYMessage.class fromJSONDictionary:textMessage error:NULL];

XYPictureMessage *messageB = [MTLJSONAdapter modelOfClass:XYMessage.class fromJSONDictionary:pictureMessage error:NULL];

Persistence 持久化存儲

Mantle 不會自動為你保留對象姓惑。但是,MTLModel 默認實現(xiàn)了 NSCoding 協(xié)議按脚,可以利用 NSKeyedArchiver 方便的對對象進行歸檔和解檔于毙。

如果你需要更強大的功能,或者想要避免一次將整個模型保留在內存中辅搬,那么 Core Data 可能是更好的選擇。

最低系統(tǒng)要求

Mantle supports the following platform deployment targets:

  • macOS 10.10+
  • iOS 8.0+
  • tvOS 9.0+
  • watchOS 2.0+

導入 Mantle

手動導入

To add Mantle to your application:

  1. Add the Mantle repository as a submodule of your application's repository.
  2. Run git submodule update --init --recursive from within the Mantle folder.
  3. Drag and drop Mantle.xcodeproj into your application's Xcode project.
  4. On the "General" tab of your application target, add Mantle.framework to the "Embedded Binaries".

If you’re instead developing Mantle on its own, use the Mantle.xcworkspace file.

Carthage 方式

Simply add Mantle to your Cartfile:

github "Mantle/Mantle"

CocoaPods 方式

Add Mantle to your Podfile under the build target they want it used in:

target 'MyAppOrFramework' do
  pod 'Mantle'
end

Then run a pod install within Terminal or the CocoaPods app.

License

Mantle is released under the MIT license. See
LICENSE.md.

More Info

Have a question? Please open an issue!

參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末堪遂,一起剝皮案震驚了整個濱河市介蛉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌溶褪,老刑警劉巖币旧,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異竿滨,居然都是意外死亡佳恬,警方通過查閱死者的電腦和手機捏境,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毁葱,“玉大人垫言,你說我怎么就攤上這事∏憬耍” “怎么了筷频?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長前痘。 經常有香客問我凛捏,道長,這世上最難降的妖魔是什么芹缔? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任坯癣,我火速辦了婚禮,結果婚禮上最欠,老公的妹妹穿的比我還像新娘示罗。我一直安慰自己,他們只是感情好芝硬,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布蚜点。 她就那樣靜靜地躺著,像睡著了一般拌阴。 火紅的嫁衣襯著肌膚如雪绍绘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天迟赃,我揣著相機與錄音陪拘,去河邊找鬼。 笑死纤壁,一個胖子當著我的面吹牛藻丢,可吹牛的內容都是我干的。 我是一名探鬼主播摄乒,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼悠反,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了馍佑?” 一聲冷哼從身側響起斋否,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拭荤,沒想到半個月后茵臭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡舅世,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年旦委,在試婚紗的時候發(fā)現(xiàn)自己被綠了奇徒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡缨硝,死狀恐怖摩钙,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情查辩,我是刑警寧澤胖笛,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站宜岛,受9級特大地震影響长踊,放射性物質發(fā)生泄漏。R本人自食惡果不足惜萍倡,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一身弊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧列敲,春花似錦佑刷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涨冀。三九已至填硕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鹿鳖,已是汗流浹背扁眯。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留翅帜,地道東北人姻檀。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像涝滴,于是被迫代替她去往敵國和親绣版。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355