iOS 緩存機(jī)制詳解

Untold numbers of developers have hacked together an awkward, fragile system for network caching functionality, all because they weren’t aware that NSURLCache could be setup in two lines and do it 100× better. Even more developers have never known the benefits of network caching, and never attempted a solution, causing their apps to make untold numbers of unnecessary requests to the server.
無(wú)數(shù)開(kāi)發(fā)者嘗試自己做一個(gè)丑陋而脆弱的系統(tǒng)來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)緩存的功能迟赃,殊不知NSURLCache只要兩行代碼就能搞定,并且好上100倍麦轰。甚至更多的開(kāi)發(fā)者根本不知道網(wǎng)絡(luò)緩存的好處雨膨,從來(lái)沒(méi)有嘗試過(guò)解決方案但骨,導(dǎo)致他們的App向服務(wù)器發(fā)出無(wú)數(shù)不必要的請(qǐng)求蜘犁。

iOS系統(tǒng)的緩存策略

????上面是引用Mattt大神在NSHipster介紹NSURLCache時(shí)的原話姚炕。

服務(wù)端的緩存策略

????先看看服務(wù)端的緩存策略征唬。當(dāng)?shù)谝淮握?qǐng)求后菩咨,客戶端會(huì)緩存數(shù)據(jù)吠式,當(dāng)有第二次請(qǐng)求的時(shí)候,客戶端會(huì)額外在請(qǐng)求頭加上If-Modified-Since或者If-None-Match抽米,If-Modified-Since會(huì)攜帶緩存的最后修改時(shí)間特占,服務(wù)端會(huì)把這個(gè)時(shí)間和實(shí)際文件的最后修改時(shí)間進(jìn)行比較。

  • 相同就返回狀態(tài)碼304云茸,且不返回?cái)?shù)據(jù)是目,客戶端拿出緩存數(shù)據(jù),渲染頁(yè)面
  • 不同就返回狀態(tài)碼200标捺,并且返回?cái)?shù)據(jù)懊纳,客戶端渲染頁(yè)面,并且更新緩存

????當(dāng)然類似的還有Cache-Control亡容、ExpiresEtag嗤疯,都是為了校驗(yàn)本地緩存文件和服務(wù)端是否一致,這里就帶過(guò)了闺兢。

NSURLCache

????NSURLCache是iOS系統(tǒng)提供的內(nèi)存以及磁盤(pán)的綜合緩存機(jī)制茂缚。NSURLCache對(duì)象被存儲(chǔ)沙盒中Library/cache目錄下。在我們只需要在didFinishLaunchingWithOptions函數(shù)里面加上下面的代碼,就可以滿足一般的緩存要求脚囊。(是的龟糕,搞定NSURLCache就是這么簡(jiǎn)單)

NSURLCache * sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
                                                            diskCapacity:100 * 1024 * 1024
                                                                diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];

????下面是幾個(gè)常用的API

 //設(shè)置內(nèi)存緩存的最大容量
[cache setMemoryCapacity:1024 * 1024 * 20];
    
//設(shè)置磁盤(pán)緩存的最大容量
[cache setDiskCapacity:1024 * 1024 * 100];
    
//獲取某個(gè)請(qǐng)求的緩存
[cache cachedResponseForRequest:request];
    
//清除某個(gè)請(qǐng)求的緩存
[cache removeCachedResponseForRequest:request];
    
//請(qǐng)求策略,設(shè)置了系統(tǒng)會(huì)自動(dòng)用NSURLCache進(jìn)行數(shù)據(jù)緩存
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;

iOS常用的緩存策略

????NSURLRequestCachePolicy是個(gè)枚舉悔耘,指的是不同的緩存策略讲岁,一共有7種,但是能用的只有4種淮逊。

typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
    //如果有協(xié)議催首,對(duì)于特定的URL請(qǐng)求扶踊,使用協(xié)議實(shí)現(xiàn)定義的緩存邏輯泄鹏。(默認(rèn)的緩存策略)
    NSURLRequestUseProtocolCachePolicy = 0,
    
    //請(qǐng)求僅從原始資源加載URL,不使用任何緩存
    NSURLRequestReloadIgnoringLocalCacheData = 1,
    
    //不僅忽略本地緩存秧耗,還要忽略協(xié)議緩存和其他緩存 (未實(shí)現(xiàn))
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,
    
    //被NSURLRequestReloadIgnoringLocalCacheData替代
    NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
    
    //無(wú)視緩存的有效期,有緩存就取緩存备籽,沒(méi)有緩存就會(huì)從原始地址加載
    NSURLRequestReturnCacheDataElseLoad = 2,
    
    //無(wú)視緩存的有效期,有緩存就取緩存分井,沒(méi)有緩存就視為失敗 (可以用于離線模式)
    NSURLRequestReturnCacheDataDontLoad = 3,
    
    //會(huì)從初始地址校驗(yàn)緩存的合法性车猬,合法就用緩存數(shù)據(jù),不合法從原始地址加載數(shù)據(jù) (未實(shí)現(xiàn))
    NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented
};

AFNetworking的緩存策略

????之前寫(xiě)了SDWebImage的源碼解析 里面介紹過(guò)SDWebImage的緩存策略尺锚,有兩條線根據(jù)時(shí)間和空間來(lái)管理緩存和AFNetworking很相似珠闰。AFNetworkingAFImageDownloader使用AFAutoPurgingImageCacheNSURLCache管理圖片緩存。

AFNetworking中的NSURLCache

????AFImageDownloader中設(shè)置NSURLCache瘫辩,低版本iOS版本中設(shè)置內(nèi)存容量和磁盤(pán)容量會(huì)閃退(這個(gè)我沒(méi)有考證伏嗜,iOS 7的手機(jī)還真沒(méi)有)

if ([[[UIDevice currentDevice] systemVersion] compare:@"8.2" options:NSNumericSearch] == NSOrderedAscending) {
    return [NSURLCache sharedURLCache];
}
return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
                                     diskCapacity:150 * 1024 * 1024
                                         diskPath:@"com.alamofire.imagedownloader"];

AFNetworking中的AFAutoPurgingImageCache

????AFAutoPurgingImageCache是專門(mén)用來(lái)圖片緩存的》パ幔可以看到內(nèi)部有三個(gè)屬性承绸,一個(gè)是用來(lái)裝載AFImageCache對(duì)象的字典容器,一個(gè)是可以用內(nèi)存空間大小挣轨、一個(gè)同步隊(duì)列军熏。AFAutoPurgingImageCache在初始化的時(shí)候,會(huì)注冊(cè)UIApplicationDidReceiveMemoryWarningNotification通知卷扮,收到內(nèi)存警告的時(shí)候會(huì)清除所有緩存荡澎。

 @interface AFAutoPurgingImageCache ()
 @property (nonatomic, strong) NSMutableDictionary <NSString* , AFCachedImage*> *cachedImages;
 @property (nonatomic, assign) UInt64 currentMemoryUsage;
 @property (nonatomic, strong) dispatch_queue_t synchronizationQueue;
 @end

????AFCachedImage是單個(gè)圖片緩存對(duì)象

 @property (nonatomic, strong) UIImage *image;
 
 //標(biāo)志符(這個(gè)值就是圖片的請(qǐng)路徑 request.URL.absoluteString)
 @property (nonatomic, strong) NSString *identifier;
 
 //圖片大小
 @property (nonatomic, assign) UInt64 totalBytes;
 
 //緩存日期
 @property (nonatomic, strong) NSDate *lastAccessDate;
 
 //當(dāng)前可用內(nèi)存空間大小
 @property (nonatomic, assign) UInt64 currentMemoryUsage;

????來(lái)看看AFCachedImage初始化的時(shí)候。iOS使用圖標(biāo)標(biāo)準(zhǔn)是ARGB_8888晤锹,即一像素占位4個(gè)字節(jié)摩幔。內(nèi)存大小 = 寬 * 高 * 每像素字節(jié)數(shù)。

-(instancetype)initWithImage:(UIImage *)image identifier:(NSString *)identifier {
    if (self = [self init]) {
        self.image = image;
        self.identifier = identifier;

        CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
        CGFloat bytesPerPixel = 4.0;
        CGFloat bytesPerSize = imageSize.width * imageSize.height;
        self.totalBytes = (UInt64)bytesPerPixel * (UInt64)bytesPerSize;
        self.lastAccessDate = [NSDate date];
    }
    return self;
}

????來(lái)看看添加緩存的代碼抖甘,用了dispatch_barrier_async柵欄函數(shù)將添加操作和刪除緩存操作分割開(kāi)來(lái)热鞍。每添加一個(gè)緩存對(duì)象,都重新計(jì)算當(dāng)前緩存大小和可用空間大小。當(dāng)內(nèi)存超過(guò)設(shè)定值時(shí)薇宠,會(huì)按照日期的倒序來(lái)遍歷緩存圖片偷办,刪除最早日期的緩存,一直到滿足緩存空間為止澄港。

- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
    dispatch_barrier_async(self.synchronizationQueue, ^{
        AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];

        AFCachedImage *previousCachedImage = self.cachedImages[identifier];
        if (previousCachedImage != nil) {
            self.currentMemoryUsage -= previousCachedImage.totalBytes;
        }

        self.cachedImages[identifier] = cacheImage;
        self.currentMemoryUsage += cacheImage.totalBytes;
    });

    dispatch_barrier_async(self.synchronizationQueue, ^{
        if (self.currentMemoryUsage > self.memoryCapacity) {
            UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge;
            NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues];
            NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate"
                                                                           ascending:YES];
            [sortedImages sortUsingDescriptors:@[sortDescriptor]];

            UInt64 bytesPurged = 0;

            for (AFCachedImage *cachedImage in sortedImages) {
                [self.cachedImages removeObjectForKey:cachedImage.identifier];
                bytesPurged += cachedImage.totalBytes;
                if (bytesPurged >= bytesToPurge) {
                    break ;
                }
            }
            self.currentMemoryUsage -= bytesPurged;
        }
    });
}

YTKNetwork的緩存策略

????YTKNetwork是猿題庫(kù)技術(shù)團(tuán)隊(duì)開(kāi)源的一個(gè)網(wǎng)絡(luò)請(qǐng)求框架椒涯,內(nèi)部封裝了AFNetworking。它把每個(gè)請(qǐng)求實(shí)例化回梧,管理它的生命周期废岂,也可以管理多個(gè)請(qǐng)求。筆者在一個(gè)電商的PaaS項(xiàng)目中就是使用YTKNetwork狱意,它的特點(diǎn)還有支持請(qǐng)求結(jié)果緩存湖苞,支持批量請(qǐng)求,支持多請(qǐng)求依賴等详囤。

準(zhǔn)備請(qǐng)求之前

????先來(lái)看看請(qǐng)求基類YTKRequest在請(qǐng)求之前做了什么

- (void)start {
    
    //忽略緩存的標(biāo)志 手動(dòng)設(shè)置 是否利用緩存
    if (self.ignoreCache) {
        [self startWithoutCache];
        return;
    }

    // 還有未完成的請(qǐng)求 是否還有未完成的請(qǐng)求
    if (self.resumableDownloadPath) {
        [self startWithoutCache];
        return;
    }

    //加載緩存是否成功
    if (![self loadCacheWithError:nil]) {
        [self startWithoutCache];
        return;
    }

    _dataFromCache = YES;

    dispatch_async(dispatch_get_main_queue(), ^{
        
        //將請(qǐng)求數(shù)據(jù)寫(xiě)入文件
        [self requestCompletePreprocessor];
        [self requestCompleteFilter];
        
        //這個(gè)時(shí)候直接去相應(yīng) 請(qǐng)求成功的delegate和block 财骨,沒(méi)有發(fā)送請(qǐng)求
        YTKRequest *strongSelf = self;
        [strongSelf.delegate requestFinished:strongSelf];
        if (strongSelf.successCompletionBlock) {
            strongSelf.successCompletionBlock(strongSelf);
        }
        
        //將block置空
        [strongSelf clearCompletionBlock];
    });
}

緩存數(shù)據(jù)寫(xiě)入文件

- (void)requestCompletePreprocessor {
    [super requestCompletePreprocessor];

    if (self.writeCacheAsynchronously) {
        dispatch_async(ytkrequest_cache_writing_queue(), ^{
            [self saveResponseDataToCacheFile:[super responseData]];
        });
    } else {
        [self saveResponseDataToCacheFile:[super responseData]];
    }
}

????ytkrequest_cache_writing_queue是一個(gè)優(yōu)先級(jí)比較低的串行隊(duì)列,當(dāng)標(biāo)志dataFromCacheYES的時(shí)候藏姐,確定能拿到數(shù)據(jù)隆箩,在這個(gè)串行隊(duì)列中異步的寫(xiě)入文件。來(lái)看看寫(xiě)入緩存的具體操作羔杨。

- (void)saveResponseDataToCacheFile:(NSData *)data {
    if ([self cacheTimeInSeconds] > 0 && ![self isDataFromCache]) {
        if (data != nil) {
            @try {
                // New data will always overwrite old data.
                [data writeToFile:[self cacheFilePath] atomically:YES];

                YTKCacheMetadata *metadata = [[YTKCacheMetadata alloc] init];
                metadata.version = [self cacheVersion];
                metadata.sensitiveDataString = ((NSObject *)[self cacheSensitiveData]).description;
                metadata.stringEncoding = [YTKNetworkUtils stringEncodingWithRequest:self];
                metadata.creationDate = [NSDate date];
                metadata.appVersionString = [YTKNetworkUtils appVersionString];
                [NSKeyedArchiver archiveRootObject:metadata toFile:[self cacheMetadataFilePath]];
            } @catch (NSException *exception) {
                YTKLog(@"Save cache failed, reason = %@", exception.reason);
            }
        }
    }
}

????除了請(qǐng)求數(shù)據(jù)文件捌臊,YTK還會(huì)生成一個(gè)記錄緩存數(shù)據(jù)信息的元數(shù)據(jù)YTKCacheMetadata對(duì)象。YTKCacheMetadata記錄了緩存的版本號(hào)兜材、敏感信息理澎、緩存日期和App的版本號(hào)。

@property (nonatomic, assign) long long version;
@property (nonatomic, strong) NSString *sensitiveDataString;
@property (nonatomic, assign) NSStringEncoding stringEncoding;
@property (nonatomic, strong) NSDate *creationDate;
@property (nonatomic, strong) NSString *appVersionString;

????然后把請(qǐng)求方法护姆、請(qǐng)求域名矾端、請(qǐng)求URL和請(qǐng)求參數(shù)組成的字符串進(jìn)行一次MD5加密,作為緩存文件的名稱卵皂。YTKCacheMetadata和緩存文件同名秩铆,多了一個(gè).metadata的后綴作為區(qū)分。文件寫(xiě)入的路徑是沙盒中Library/LazyRequestCache目錄下灯变。

- (NSString *)cacheFileName {
    NSString *requestUrl = [self requestUrl];
    NSString *baseUrl = [YTKNetworkConfig sharedConfig].baseUrl;
    id argument = [self cacheFileNameFilterForRequestArgument:[self requestArgument]];
    NSString *requestInfo = [NSString stringWithFormat:@"Method:%ld Host:%@ Url:%@ Argument:%@",
                             (long)[self requestMethod], baseUrl, requestUrl, argument];
    NSString *cacheFileName = [YTKNetworkUtils md5StringFromString:requestInfo];
    return cacheFileName;
}
YTKNetwork緩存文件路徑.png

校驗(yàn)緩存

????回到start方法中殴玛,loadCacheWithError是校驗(yàn)緩存能不能成功加載出來(lái),loadCacheWithError中會(huì)調(diào)用validateCacheWithError來(lái)檢驗(yàn)緩存的合法性添祸,校驗(yàn)的依據(jù)正是YTKCacheMetadatacacheTimeInSeconds滚粟。要想使用緩存數(shù)據(jù),請(qǐng)求實(shí)例要重寫(xiě)cacheTimeInSeconds設(shè)置一個(gè)大于0的值刃泌,而且緩存還支持版本凡壤、App的版本署尤。在實(shí)際項(xiàng)目上應(yīng)用,get請(qǐng)求實(shí)例設(shè)置一個(gè)cacheTimeInSeconds就夠用了亚侠。

- (BOOL)validateCacheWithError:(NSError * _Nullable __autoreleasing *)error {
    // Date
    NSDate *creationDate = self.cacheMetadata.creationDate;
    NSTimeInterval duration = -[creationDate timeIntervalSinceNow];
    if (duration < 0 || duration > [self cacheTimeInSeconds]) {
        if (error) {
            *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorExpired userInfo:@{ NSLocalizedDescriptionKey:@"Cache expired"}];
        }
        return NO;
    }
    // Version
    long long cacheVersionFileContent = self.cacheMetadata.version;
    if (cacheVersionFileContent != [self cacheVersion]) {
        if (error) {
            *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorVersionMismatch userInfo:@{ NSLocalizedDescriptionKey:@"Cache version mismatch"}];
        }
        return NO;
    }
    // Sensitive data
    NSString *sensitiveDataString = self.cacheMetadata.sensitiveDataString;
    NSString *currentSensitiveDataString = ((NSObject *)[self cacheSensitiveData]).description;
    if (sensitiveDataString || currentSensitiveDataString) {
        // If one of the strings is nil, short-circuit evaluation will trigger
        if (sensitiveDataString.length != currentSensitiveDataString.length || ![sensitiveDataString isEqualToString:currentSensitiveDataString]) {
            if (error) {
                *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorSensitiveDataMismatch userInfo:@{ NSLocalizedDescriptionKey:@"Cache sensitive data mismatch"}];
            }
            return NO;
        }
    }
    // App version
    NSString *appVersionString = self.cacheMetadata.appVersionString;
    NSString *currentAppVersionString = [YTKNetworkUtils appVersionString];
    if (appVersionString || currentAppVersionString) {
        if (appVersionString.length != currentAppVersionString.length || ![appVersionString isEqualToString:currentAppVersionString]) {
            if (error) {
                *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorAppVersionMismatch userInfo:@{ NSLocalizedDescriptionKey:@"App version mismatch"}];
            }
            return NO;
        }
    }
    return YES;
}

清除緩存

????因?yàn)榫彺娴哪夸浭?code>Library/LazyRequestCache曹体,清除緩存就直接清空目錄下所有文件就可以了。調(diào)用[[YTKNetworkConfig sharedConfig] clearCacheDirPathFilter]就行硝烂。

結(jié)語(yǔ)

????緩存的本質(zhì)是用空間換取時(shí)間箕别。大學(xué)里面學(xué)過(guò)的《計(jì)算機(jī)組成原理》中就有介紹cache,除了磁盤(pán)和內(nèi)存,還有L1和L2滞谢,對(duì)于iOS開(kāi)發(fā)者來(lái)說(shuō)串稀,一般關(guān)注diskmemory就夠了。閱讀SDWebImage狮杨、AFNetworking母截、YTKNetwork的源碼后,可以看出他們都非常重視數(shù)據(jù)的多線程的讀寫(xiě)安全禾酱,在做深度優(yōu)化時(shí)候微酬,因地制宜绘趋,及時(shí)清理緩存文件颤陶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市陷遮,隨后出現(xiàn)的幾起案子滓走,更是在濱河造成了極大的恐慌,老刑警劉巖帽馋,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搅方,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡绽族,警方通過(guò)查閱死者的電腦和手機(jī)姨涡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吧慢,“玉大人涛漂,你說(shuō)我怎么就攤上這事检诗⌒僬蹋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵逢慌,是天一觀的道長(zhǎng)火架。 經(jīng)常有香客問(wèn)我炼列,道長(zhǎng),這世上最難降的妖魔是什么洞翩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任来屠,我火速辦了婚禮,結(jié)果婚禮上捆姜,老公的妹妹穿的比我還像新娘泥技。我一直安慰自己榕订,他們只是感情好劫恒,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布鸯檬。 她就那樣靜靜地躺著赖歌,像睡著了一般坎穿。 火紅的嫁衣襯著肌膚如雪孵延。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼箱舞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鹅经,可吹牛的內(nèi)容都是我干的蹦误。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缸剪!你這毒婦竟也來(lái)了唬渗?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤座菠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后屡限,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體额嘿,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡东帅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了氧敢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡越妈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情消痛,我是刑警寧澤且叁,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站秩伞,受9級(jí)特大地震影響逞带,放射性物質(zhì)發(fā)生泄漏欺矫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一展氓、第九天 我趴在偏房一處隱蔽的房頂上張望穆趴。 院中可真熱鬧,春花似錦遇汞、人聲如沸未妹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)络它。三九已至,卻和暖如春歪赢,著一層夾襖步出監(jiān)牢的瞬間化戳,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工埋凯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留点楼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓递鹉,卻偏偏與公主長(zhǎng)得像盟步,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子躏结,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

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