iOS源碼閱讀 —— MJExtension

MJExtension是一款開源的鸵隧,簡(jiǎn)單易用的字典與模型轉(zhuǎn)換框架拗馒。
常用的方法谣膳,主要是以下幾個(gè):

// JSON|字典 轉(zhuǎn) 模型
+ (instancetype)mj_objectWithKeyValues:(id)keyValues;

// 通過(guò) JSON|字典 為 模型賦值
- (instancetype)mj_setKeyValues:(id)keyValues;

// 模型轉(zhuǎn)JSON
- (NSMutableDictionary *)mj_keyValues;

// JSON數(shù)組轉(zhuǎn)模型數(shù)組
+ (NSMutableArray *)mj_objectArrayWithKeyValuesArray:(id)keyValuesArray;

功能

JSON|字典 轉(zhuǎn) 模型

+ mj_objectWithKeyValues:

+ mj_objectWithKeyValues: 是框架中最簡(jiǎn)單的JSON轉(zhuǎn)模型的方法荷鼠,通過(guò)直接調(diào)用類方法并傳入JSON數(shù)據(jù)即可快速實(shí)現(xiàn)轉(zhuǎn)換窖铡。而在+ mj_objectWithKeyValues:方法中,實(shí)際是調(diào)用了+ mj_objectWithKeyValues: context:方法衡载,參數(shù)中如果傳了contenxt搔耕,最終會(huì)返回CoreData模型;如果不傳痰娱,返回已賦值的模型弃榨。

+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
    // 獲得JSON對(duì)象
    keyValues = [keyValues mj_JSONObject];
    MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], nil, [self class], @"keyValues參數(shù)不是一個(gè)字典");
    
    // 判斷是否傳入 "contenxt" 參數(shù)
    if ([self isSubclassOfClass:[NSManagedObject class]] && context) {
        NSString *entityName = [NSStringFromClass(self) componentsSeparatedByString:@"."].lastObject;
        return [[NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context] mj_setKeyValues:keyValues context:context];
    }
    return [[[self alloc] init] mj_setKeyValues:keyValues];
}

在為實(shí)例賦值的方法中,由于- mj_setKeyValues:實(shí)際的實(shí)現(xiàn)是調(diào)用- mj_setKeyValues: context:猜揪。所以我們直接進(jìn)入- mj_setKeyValues: context:進(jìn)行分析惭墓。

首先坛梁,需要將傳入的keyValues處理成可用的JSON對(duì)象而姐,并獲取當(dāng)前類的類型,以及黑白名單屬性划咐。

// 獲得JSON對(duì)象
keyValues = [keyValues mj_JSONObject];
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues參數(shù)不是一個(gè)字典");

Class clazz = [self class];
NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];

緊接著拴念,調(diào)用類的擴(kuò)展方法+ mj_enumerateProperties:,獲取和遍歷類的屬性列表褐缠,通過(guò)block參數(shù)進(jìn)行回調(diào)政鼠,在回調(diào)的代碼塊中,對(duì)每個(gè)屬性進(jìn)行注意賦值队魏。

核心代碼:

    //通過(guò)封裝的方法回調(diào)一個(gè)通過(guò)運(yùn)行時(shí)編寫的公般,用于返回屬性列表的方法万搔。
    [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
        @try {
            // 0.檢測(cè)是否被忽略
            if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
            if ([ignoredPropertyNames containsObject:property.name]) return;
            
            // 1.取出屬性值
            id value;
            NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
            for (NSArray *propertyKeys in propertyKeyses) {
                value = keyValues;
                for (MJPropertyKey *propertyKey in propertyKeys) {
                    value = [propertyKey valueInObject:value];
                }
                if (value) break;
            }
            
            // 值的過(guò)濾
            id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];
            if (newValue != value) { // 有過(guò)濾后的新值
                [property setValue:newValue forObject:self];
                return;
            }
            
            // 如果沒(méi)有值,就直接返回
            if (!value || value == [NSNull null]) return;
            
            // 2.復(fù)雜處理
            MJPropertyType *type = property.type; // 數(shù)據(jù)類型類
            Class propertyClass = type.typeClass; // 對(duì)象類型
            Class objectClass = [property objectClassInArrayForClass:[self class]]; // 數(shù)組中的模型類型
            
            // 不可變 -> 可變處理
            if (propertyClass == [NSMutableArray class] && [value isKindOfClass:[NSArray class]]) {
                value = [NSMutableArray arrayWithArray:value];
            } else if (propertyClass == [NSMutableDictionary class] && [value isKindOfClass:[NSDictionary class]]) {
                value = [NSMutableDictionary dictionaryWithDictionary:value];
            } else if (propertyClass == [NSMutableString class] && [value isKindOfClass:[NSString class]]) {
                value = [NSMutableString stringWithString:value];
            } else if (propertyClass == [NSMutableData class] && [value isKindOfClass:[NSData class]]) {
                value = [NSMutableData dataWithData:value];
            }
            
            if (!type.isFromFoundation && propertyClass) { // 模型屬性
                // 既不是基礎(chǔ)類型官帘,也不是NS類型瞬雹。即:基本數(shù)據(jù)類型
                value = [propertyClass mj_objectWithKeyValues:value context:context];
            } else if (objectClass) {
                if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
                    // string array -> url array
                    NSMutableArray *urlArray = [NSMutableArray array];
                    for (NSString *string in value) {
                        if (![string isKindOfClass:[NSString class]]) continue;
                        [urlArray addObject:string.mj_url];
                    }
                    value = urlArray;
                } else { // 字典數(shù)組-->模型數(shù)組
                    value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context];
                }
            } else if (propertyClass == [NSString class]) {
                if ([value isKindOfClass:[NSNumber class]]) {
                    // NSNumber -> NSString
                    value = [value description];
                } else if ([value isKindOfClass:[NSURL class]]) {
                    // NSURL -> NSString
                    value = [value absoluteString];
                }
            } else if ([value isKindOfClass:[NSString class]]) {
                if (propertyClass == [NSURL class]) {
                    // NSString -> NSURL
                    // 字符串轉(zhuǎn)碼
                    value = [value mj_url];
                } else if (type.isNumberType) {
                    NSString *oldValue = value;
                    
                    // NSString -> NSDecimalNumber, 使用 DecimalNumber 來(lái)轉(zhuǎn)換數(shù)字, 避免丟失精度以及溢出
                    NSDecimalNumber *decimalValue = [NSDecimalNumber decimalNumberWithString:oldValue
                                                                                      locale:numberLocale];
                    
                    // 檢查特殊情況
                    if (decimalValue == NSDecimalNumber.notANumber) {
                        value = @(0);
                    }else if (propertyClass != [NSDecimalNumber class]) {
                        value = [decimalValue mj_standardValueWithTypeCode:type.code];
                    } else {
                        value = decimalValue;
                    }
                    
                    // 如果是BOOL
                    if (type.isBoolType) {
                        // 字符串轉(zhuǎn)BOOL(字符串沒(méi)有charValue方法)
                        // 系統(tǒng)會(huì)調(diào)用字符串的charValue轉(zhuǎn)為BOOL類型
                        NSString *lower = [oldValue lowercaseString];
                        if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"true"]) {
                            value = @YES;
                        } else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]) {
                            value = @NO;
                        }
                    }
                }
            } else if ([value isKindOfClass:[NSNumber class]] && propertyClass == [NSDecimalNumber class]){
                // 過(guò)濾 NSDecimalNumber類型
                if (![value isKindOfClass:[NSDecimalNumber class]]) {
                    value = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
                }
            }
            
            // 經(jīng)過(guò)轉(zhuǎn)換后, 最終檢查 value 與 property 是否匹配
            if (propertyClass && ![value isKindOfClass:propertyClass]) {
                value = nil;
            }
            
            // 3.賦值(KVC)
            [property setValue:value forObject:self];
        } @catch (NSException *exception) {
            MJExtensionBuildError([self class], exception.reason);
            MJExtensionLog(@"%@", exception);
        }
    }];

從代碼中,可以直觀看出刽虹,賦值操作主要分為步驟4個(gè)步驟酗捌。

0.檢測(cè)是否被忽略

// 白名單
if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
// 黑名單
if ([ignoredPropertyNames containsObject:property.name]) return;

判斷黑白名單中是否包含相應(yīng)的屬性名稱。

1.取出屬性值

id value;
NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
for (NSArray *propertyKeys in propertyKeyses) {
    value = keyValues;
    for (MJPropertyKey *propertyKey in propertyKeys) {
        value = [propertyKey valueInObject:value];
    }
    if (value) break;
}

// 值的過(guò)濾
id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];
if (newValue != value) { // 有過(guò)濾后的新值
    [property setValue:newValue forObject:self];
    return;
}

// 如果沒(méi)有值涌哲,就直接返回
if (!value || value == [NSNull null]) return;

因?yàn)橥粋€(gè)成員屬性胖缤,父類和子類的行為可能不一致(originKey、propertyKeys阀圾、objectClassInArray)哪廓,所以其鍵值可能是一個(gè)數(shù)組,通過(guò)循環(huán)這個(gè)數(shù)組嘗試獲取值稍刀。
對(duì)值得過(guò)濾撩独,指的是使用者通過(guò)實(shí)現(xiàn)- (id)mj_newValueFromOldValue: property:方法,對(duì)結(jié)果進(jìn)行進(jìn)一步的處理(比如字符串日期處理為NSDate账月、字符串nil處理為@"")综膀。

2.復(fù)雜處理

MJPropertyType *type = property.type; // 數(shù)據(jù)類型的信息
Class propertyClass = type.typeClass; // 屬性的類型
Class objectClass = [property objectClassInArrayForClass:[self class]]; // 數(shù)組中模型的類型

// 不可變 -> 可變處理
if (propertyClass == [NSMutableArray class] && [value isKindOfClass:[NSArray class]]) {
    value = [NSMutableArray arrayWithArray:value];
} else if (propertyClass == [NSMutableDictionary class] && [value isKindOfClass:[NSDictionary class]]) {
    value = [NSMutableDictionary dictionaryWithDictionary:value];
} else if (propertyClass == [NSMutableString class] && [value isKindOfClass:[NSString class]]) {
    value = [NSMutableString stringWithString:value];
} else if (propertyClass == [NSMutableData class] && [value isKindOfClass:[NSData class]]) {
    value = [NSMutableData dataWithData:value];
}

if (!type.isFromFoundation && propertyClass) { // 模型屬性
    // 既不是基礎(chǔ)類型,也不是NS類型局齿。即:基本數(shù)據(jù)類型
    value = [propertyClass mj_objectWithKeyValues:value context:context];
} else if (objectClass) {
    if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
        // string array -> url array
        NSMutableArray *urlArray = [NSMutableArray array];
        for (NSString *string in value) {
            if (![string isKindOfClass:[NSString class]]) continue;
            [urlArray addObject:string.mj_url];
        }
        value = urlArray;
    } else { // 字典數(shù)組-->模型數(shù)組
        value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context];
    }
} else if (propertyClass == [NSString class]) {


} else if ([value isKindOfClass:[NSString class]]) {

} else if ([value isKindOfClass:[NSNumber class]] && propertyClass == [NSDecimalNumber class]){

}

復(fù)雜處理剧劝,主要是對(duì)屬性值的類型進(jìn)行判斷,屬性值的類型只要分為:模型屬性(自定義類)抓歼、數(shù)組屬性和其他屬性(NS類型)讥此。
模型屬性的value,需要通過(guò)繼續(xù)調(diào)用- mj_objectWithKeyValues:value context:方法谣妻,將字典轉(zhuǎn)換成模型萄喳。
數(shù)組屬性的值,則需要根據(jù)數(shù)組中模型的類型蹋半,進(jìn)行循環(huán)轉(zhuǎn)換他巨。
其他情況的值,可以通過(guò)簡(jiǎn)單的轉(zhuǎn)化或者直接使用减江。

3.賦值

至此染突,屬性信息和值都有了。

// 經(jīng)過(guò)轉(zhuǎn)換后, 最終檢查 value 與 property 是否匹配
if (propertyClass && ![value isKindOfClass:propertyClass]) {
    value = nil;
}
[property setValue:value forObject:self];

在確定 value的值 與 property得類型 確實(shí)匹配后辈灼,通過(guò)KVC進(jìn)行賦值份企。

/**
 *  設(shè)置成員變量的值
 */
- (void)setValue:(id)value forObject:(id)object
{
    if (self.type.KVCDisabled || value == nil) return;
    [object setValue:value forKey:self.name];
}

到此,JSON轉(zhuǎn)模型的工作就完成了巡莹。

模型 轉(zhuǎn) JSON|字典

- mj_keyValues

模型轉(zhuǎn)JSON的方法主要有:

// 轉(zhuǎn)換并返回模型中所有屬性的鍵值對(duì)
- (NSMutableDictionary *)mj_keyValues;

/**
 @para keys 需要返回的特定鍵的數(shù)組 
 @return 特定關(guān)鍵詞的鍵值對(duì)
 */
- (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys;

/**
 @para ignoredKeys 需要忽略的特定鍵的數(shù)組 
 @return 除特定關(guān)鍵詞的其他有效鍵值對(duì)
 */
- (NSMutableDictionary *)mj_keyValuesWithIgnoredKeys:(NSArray *)ignoredKeys;

以上方法統(tǒng)一調(diào)用了- mj_keyValuesWithKeys:ignoredKeys:司志,讓我們直接進(jìn)入這個(gè)方法一探究竟甜紫。
- mj_keyValuesWithKeys:ignoredKeys:方法與JSON轉(zhuǎn)模型的核心邏輯是極其相似的,即通過(guò)遍歷類的所有屬性骂远,進(jìn)行相關(guān)操作棵介,這里我們直接進(jìn)入代碼塊,進(jìn)行分析吧史。

0.檢測(cè)是否被忽略

// 白名單
if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
// 黑名單
if ([ignoredPropertyNames containsObject:property.name]) return;
// 只需要返回的特定鍵
if (keys.count && ![keys containsObject:property.name]) return;
// 需要被忽略的特定鍵
if ([ignoredKeys containsObject:property.name]) return;

返回的結(jié)果邮辽,不僅可以對(duì)黑白名單中的屬性進(jìn)行篩選,還可以根據(jù)具體場(chǎng)景設(shè)置需要返回和忽略的特定鍵值贸营。

1.取出屬性值

使用KVC取值吨述。

id value = [property valueForObject:self];

/**
 *  獲得成員變量的值
 */
- (id)valueForObject:(id)object
{
    if (self.type.KVCDisabled) return [NSNull null];
    
    id value = [object valueForKey:self.name];
    
    // 32位BOOL類型轉(zhuǎn)換json后成Int類型
    /** https://github.com/CoderMJLee/MJExtension/issues/545 */
    // 32 bit device OR 32 bit Simulator
#if defined(__arm__) || (TARGET_OS_SIMULATOR && !__LP64__)
    if (self.type.isBoolType) {
        value = @([(NSNumber *)value boolValue]);
    }
#endif
    
    return value;
}

2.模型屬性和數(shù)組的處理

如果當(dāng)前的屬性屬于模型類型或數(shù)組,則需要對(duì) value 進(jìn)行遞歸調(diào)用 - mj_keyValues 方法钞脂,直至最終得到非模型和非數(shù)組的數(shù)據(jù)類型揣云。

MJPropertyType *type = property.type;
Class propertyClass = type.typeClass;
if (!type.isFromFoundation && propertyClass) {
    value = [value mj_keyValues];
} else if ([value isKindOfClass:[NSArray class]]) {
    // 3.處理數(shù)組里面有模型的情況
    value = [NSObject mj_keyValuesArrayWithObjectArray:value];
} else if (propertyClass == [NSURL class]) {
    value = [value absoluteString];
}

3.賦值

在對(duì)結(jié)果keyValues進(jìn)行賦值之前,需要先判斷創(chuàng)建鍵值時(shí)冰啃,是否引用了替換鍵 —— 也就是在+ mj_replacedKeyFromPropertyName方法中返回的自定義映射表邓夕。
對(duì)于沒(méi)有引用替換鍵的值,可以直接賦值阎毅。

keyValues[property.name] = value;

對(duì)于引用了替換鍵的值焚刚,需要獲取原始的key,最終結(jié)果也將返回最原始的JSON或字典扇调。

// 獲取原始key
NSArray *propertyKeys = [[property propertyKeysForClass:clazz] firstObject];
NSUInteger keyCount = propertyKeys.count;
// 創(chuàng)建字典
__block id innerContainer = keyValues;
[propertyKeys enumerateObjectsUsingBlock:^(MJPropertyKey *propertyKey, NSUInteger idx, BOOL *stop) {
    // 下一個(gè)屬性
    MJPropertyKey *nextPropertyKey = nil;
    if (idx != keyCount - 1) {
        nextPropertyKey = propertyKeys[idx + 1];
    }

    if (nextPropertyKey) { // 不是最后一個(gè)key
        // 當(dāng)前propertyKey對(duì)應(yīng)的字典或者數(shù)組
        id tempInnerContainer = [propertyKey valueInObject:innerContainer];
        if (tempInnerContainer == nil || [tempInnerContainer isKindOfClass:[NSNull class]]) {
            if (nextPropertyKey.type == MJPropertyKeyTypeDictionary) {
                tempInnerContainer = [NSMutableDictionary dictionary];
            } else {
                tempInnerContainer = [NSMutableArray array];
            }
            if (propertyKey.type == MJPropertyKeyTypeDictionary) {
                innerContainer[propertyKey.name] = tempInnerContainer;
            } else {
                innerContainer[propertyKey.name.intValue] = tempInnerContainer;
            }
        }

        if ([tempInnerContainer isKindOfClass:[NSMutableArray class]]) {
            NSMutableArray *tempInnerContainerArray = tempInnerContainer;
            int index = nextPropertyKey.name.intValue;
            while (tempInnerContainerArray.count < index + 1) {
                [tempInnerContainerArray addObject:[NSNull null]];
            }
        }

        innerContainer = tempInnerContainer;
    } else { // 最后一個(gè)key
        if (propertyKey.type == MJPropertyKeyTypeDictionary) {
            innerContainer[propertyKey.name] = value;
        } else {
            innerContainer[propertyKey.name.intValue] = value;
        }
    }
}];

總結(jié)

核心代碼:

  • JSON|字典轉(zhuǎn)模型的各類方法矿咕,最終都會(huì)調(diào)用- mj_setKeyValues:(id)keyValues context:
  • 模型轉(zhuǎn)JSON|字典的各類方法,最終都會(huì)調(diào)用- (NSMutableDictionary *)mj_keyValuesWithKeys: ignoredKeys:

性能方面:

  • 使用runtime動(dòng)態(tài)生成類的屬性信息狼钮,并通過(guò)緩存機(jī)制進(jìn)行性能提優(yōu)碳柱。

容錯(cuò)方面

  • 在JSON|字典轉(zhuǎn)模型最后賦值之前,會(huì)對(duì)值和屬性的類型進(jìn)行一致性的判斷熬芜。如果不匹配莲镣,value會(huì)被置為nil,避免潛在的Crash風(fēng)險(xiǎn)涎拉。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瑞侮,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子曼库,更是在濱河造成了極大的恐慌区岗,老刑警劉巖略板,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毁枯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡叮称,警方通過(guò)查閱死者的電腦和手機(jī)种玛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門藐鹤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人赂韵,你說(shuō)我怎么就攤上這事娱节。” “怎么了祭示?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵肄满,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我质涛,道長(zhǎng)稠歉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任汇陆,我火速辦了婚禮怒炸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘毡代。我一直安慰自己阅羹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布教寂。 她就那樣靜靜地躺著捏鱼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪酪耕。 梳的紋絲不亂的頭發(fā)上穷躁,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音因妇,去河邊找鬼问潭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛婚被,可吹牛的內(nèi)容都是我干的狡忙。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼址芯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼灾茁!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起谷炸,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤北专,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后旬陡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拓颓,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年描孟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驶睦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砰左。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖场航,靈堂內(nèi)的尸體忽然破棺而出缠导,到底是詐尸還是另有隱情,我是刑警寧澤溉痢,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布僻造,位于F島的核電站,受9級(jí)特大地震影響孩饼,放射性物質(zhì)發(fā)生泄漏嫡意。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一捣辆、第九天 我趴在偏房一處隱蔽的房頂上張望蔬螟。 院中可真熱鬧,春花似錦汽畴、人聲如沸旧巾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鲁猩。三九已至,卻和暖如春罢坝,著一層夾襖步出監(jiān)牢的瞬間廓握,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工嘁酿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留隙券,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓闹司,卻偏偏與公主長(zhǎng)得像娱仔,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子游桩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361