swift 圖片加載框架 Kingfisher

一拍谐、Kingfisher的架構(gòu)

閱讀他人優(yōu)秀代碼是一個(gè)提高自身代碼水平很好的方法辈双≡鹛停花了幾天的時(shí)間,看了Kingfisher的源代碼湃望,里面包含的很多知識(shí)點(diǎn)换衬,讓我受益匪淺。3.x版本相比與之前的版本证芭,一個(gè)重要的改變就是protocol的靈活運(yùn)用瞳浦,更加面向協(xié)議編程。當(dāng)然還有其他很多知識(shí)废士,比如多線程叫潦,枚舉,閉包官硝,Extension 等等應(yīng)用矗蕊。 Kingfisher中有Core,Extension氢架,Helpers 三個(gè)目錄結(jié)構(gòu) 共20個(gè)文件

Core
image.swift 文件內(nèi)部對(duì) UIImage 以及 NSData 進(jìn)行了拓展, 包含判定圖片類型拔妥、圖片解碼以及Gif數(shù)據(jù)處理等操作
Indicator.swift 圖片加載時(shí)loading指示
ImageCache.swift 主要負(fù)責(zé)將加載過的圖片緩存至本地。
ImageDownloader.swift 負(fù)責(zé)下載網(wǎng)絡(luò)圖片达箍。
ImagePrefetcher.swift 可用于提前指定一些圖片下載
ImageProcessor.swift 可用于將下載的數(shù)據(jù)合成圖片對(duì)象
CacheSerializer.swift 可用于圖像對(duì)象序列化成圖像數(shù)據(jù)存儲(chǔ)到磁盤緩存和從磁盤緩存將圖片數(shù)據(jù)反序列化成圖像對(duì)象没龙。
RequestModifier.swift 下載圖像請(qǐng)求修改器。
ImageTransition.swift 過渡動(dòng)畫效果 使用UIViewAnimationOptions動(dòng)畫效果
KingfisherManager.swift Kingfisher 管理控制類缎玫,擁有圖片下載及緩存功能
KingfisherOptionsInfo.swift 枚舉KingfisherOptionsInfoItem 配置 Kingfisher 行為的參數(shù)硬纤,包括 是否自定義緩存對(duì)象 是否自定義下載器 是否過渡動(dòng)畫 是否設(shè)置下載低優(yōu)先級(jí) 是否強(qiáng)制刷新 是否僅獲取緩存圖片 是否僅緩存至內(nèi)存、是否允許圖像后臺(tái)解碼等設(shè)置赃磨。
Filter.swift 圖像過濾器
Resource.swift 記錄了圖片的下載地址和緩存Key筝家。
Kingfisher.swift 添加KingfisherCompatible通用協(xié)議 kf新屬性

Extension
ImageView+Kingfisher.swift UIButton+Kingfisher.swift NSButton+Kingfisher對(duì) UIImageView UIButton NSButton 進(jìn)行了拓展 主要用于提供 Kingfisher 的外部接口。

Helpers
String+MD5.swift 負(fù)責(zé)圖片緩存時(shí)對(duì)文件名進(jìn)行MD5加密操作邻辉。
Box.swift 一個(gè)簡單泛型類
ThreadHelper.swift中的 dispatch_async_safely_main_queue 函數(shù)接受一個(gè)閉包 利用 NSThread.isMainThread 判斷并將其放置在主線程中執(zhí)行

二溪王、Kingfisher.swift

主要文件ImageView+Kingfisher,KingfisherManager,ImageCache,ImageDownloader,廢話不多說直接代碼學(xué)習(xí)

運(yùn)行demo 下面有這么一段代碼:

let url = URL(string:"https://raw.githubusercontent.com/onevcat/Kingfisher/master/images/kingfisher-\(indexPath.row + 1).jpg")!
(cell as! CollectionViewCell).cellImageView.kf.setImage(with: url,
                                       placeholder: nil,
                                       options: [.transition(.fade(1))],
                                       progressBlock: { receivedSize, totalSize in
                                        print("\(indexPath.row + 1): \(receivedSize)/\(totalSize)")
        },
                                       completionHandler: { image, error, cacheType, imageURL in
                                        print("\(indexPath.row + 1): Finished")
    })

首先調(diào)用的UIImageView的kf屬性 之前是調(diào)用UIImageView的Extension中的kf_setImage ,現(xiàn)已棄用 那kf 屬性是如何實(shí)現(xiàn)的腮鞍?
下面是Kingfisher.swift源碼

自定義了不同平臺(tái)下的一些類型別名 swift中的typealias 相當(dāng)于OC中的typedef

if os(macOS)

import AppKit
public typealias Image = NSImage
public typealias Color = NSColor
public typealias ImageView = NSImageView
typealias Button = NSButton

else

import UIKit
public typealias Image = UIImage
public typealias Color = UIColor
#if !os(watchOS)
public typealias ImageView = UIImageView
typealias Button = UIButton
#endif

endif

申明了泛型類Kingfisher 實(shí)現(xiàn)了一個(gè)簡單構(gòu)造器,其中上面的cellImageView就是base屬性
public final class Kingfisher<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}

申明KingfisherCompatible協(xié)議 有一個(gè)可讀屬性kf 其類型是關(guān)聯(lián)類型
/**
A type that has Kingfisher extensions.
*/

public protocol KingfisherCompatible {
associatedtype CompatibleType
var kf: CompatibleType { get }
}
KingfisherCompatible協(xié)議的實(shí)現(xiàn) 屬性kf關(guān)聯(lián)Kingfisher類型 返回一個(gè)Kingfisher實(shí)例 base 參數(shù)就是傳入的self
public extension KingfisherCompatible {
public var kf: Kingfisher<Self> {
get { return Kingfisher(self) }
}
}
Image ImageView Button 遵守 KingfisherCompatible 協(xié)議 所以上邊的self參數(shù)就是遵守了協(xié)議的類型 因此base屬性即cellImageView
extension Image: KingfisherCompatible { }

if !os(watchOS)

extension ImageView: KingfisherCompatible {}
extension Button: KingfisherCompatible { }

endif

三莹菱、ImageView+Kingfisher

現(xiàn)在來說說setImage這個(gè)方法的實(shí)現(xiàn) 這個(gè)方法是在Kingfisher的Extension 中實(shí)現(xiàn) 并且要求Base屬于UIImageView類型 即where Base: ImageView 由于kf 屬性關(guān)聯(lián)了Kingfisher
所以可以調(diào)用(cell as! CollectionViewCell).cellImageView.kf.setImage
Extensions目錄下的三個(gè)文件都是類似實(shí)現(xiàn)的 這里就以ImageView+Kingfisher.swift為例
下面方法是外部使用Kingfisher最頻繁也是最重要的方法
第一個(gè)參數(shù)Resource是一個(gè)URL遵守的Protocol移国,一般傳入圖片的URL,不可為空
第二個(gè)參數(shù)placeholder是一個(gè)默認(rèn)的占位圖道伟,可為空
第三個(gè)參數(shù)KingfisherOptionsInfo 是個(gè)枚舉數(shù)組迹缀,配置Kingfisher下載圖片的一些操作行為
第四個(gè)參數(shù)DownloadProgressBlock是個(gè)下載進(jìn)度閉包,可以用于更新下載UI
第五個(gè)參數(shù)completionHandler是個(gè)下載完成閉包蜜徽,閉包參數(shù)包含圖片祝懂,錯(cuò)誤,緩存類型拘鞋,URL 信息

extension Kingfisher where Base: ImageView {
/**
Set an image with a resource, a placeholder image, options, progress handler and completion handler.

 - parameter resource:          Resource object contains information such as `cacheKey` and `downloadURL`.
 - parameter placeholder:       A placeholder image when retrieving the image at URL.
 - parameter options:           A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
 - parameter progressBlock:     Called when the image downloading progress gets updated.
 - parameter completionHandler: Called when the image retrieved and set.
 
 - returns: A task represents the retrieving process.
 
 - note: Both the `progressBlock` and `completionHandler` will be invoked in main thread.
 The `CallbackDispatchQueue` specified in `optionsInfo` will not be used in callbacks of this method.
 */
@discardableResult 忽略返回值警告
public func setImage(with resource: Resource?,
                     placeholder: Image? = nil,
                     options: KingfisherOptionsInfo? = nil,
                     progressBlock: DownloadProgressBlock? = nil,
                     completionHandler: CompletionHandler? = nil) -> RetrieveImageTask
{
    當(dāng)傳入的resource為空時(shí) 使用guard語句提前退出 Resource是一個(gè)協(xié)議 URL遵守此協(xié)議 Resource有兩個(gè)屬性 cacheKey和downloadURL
    guard let resource = resource else {
        base.image = placeholder
        completionHandler?(nil, nil, .none, nil)
        return .empty
    }
    圖片加載過程中是否顯示placeholder
    var options = options ?? KingfisherEmptyOptionsInfo
    if !options.keepCurrentImageWhileLoading {
        base.image = placeholder
    }
    如果indicator存在砚蓬,開啟轉(zhuǎn)圈動(dòng)畫 indicator 通過屬性關(guān)聯(lián)存取
    let maybeIndicator = indicator
    maybeIndicator?.startAnimatingView()
    關(guān)聯(lián)屬性綁定下載的URL
    setWebURL(resource.downloadURL)

    默認(rèn)開啟加載所有GIF圖片數(shù)據(jù),顯示GIF 動(dòng)態(tài)圖片
    if base.shouldPreloadAllGIF() {
        options.append(.preloadAllGIFData)
    }
    調(diào)用KingfisherManager的方法來獲取圖片
    let task = KingfisherManager.shared.retrieveImage(
        with: resource,
        options: options,
        progressBlock: { receivedSize, totalSize in
            下載進(jìn)度回調(diào)
            if let progressBlock = progressBlock {
                progressBlock(receivedSize, totalSize)
            }
        },
        completionHandler: {[weak base] image, error, cacheType, imageURL in
            確保線程安全
            DispatchQueue.main.safeAsync {
                確保返回的圖片與URL對(duì)應(yīng)一致
                guard let strongBase = base, imageURL == self.webURL else {
                    return
                }
                self.setImageTask(nil)
                沒有圖片返回停止動(dòng)畫返回錯(cuò)誤
                guard let image = image else {
                    maybeIndicator?.stopAnimatingView()
                    completionHandler?(nil, error, cacheType, imageURL)
                    return
                }
                是否需要過渡動(dòng)畫 transitionItem 為 options中第一個(gè).transition
                需要過渡動(dòng)畫需要滿足以下情況
                1.transitionItem存在且不為.transition(.none)
                2.options.forceTransition存在 或者 cacheType == .none 
                guard let transitionItem = options.firstMatchIgnoringAssociatedValue(.transition(.none)),
                    case .transition(let transition) = transitionItem, ( options.forceTransition || cacheType == .none) else
                {
                    maybeIndicator?.stopAnimatingView()
                    strongBase.image = image
                    completionHandler?(image, error, cacheType, imageURL)
                    return
                }
                過渡動(dòng)畫
                #if !os(macOS)
                    UIView.transition(with: strongBase, duration: 0.0, options: [],
                                      animations: { maybeIndicator?.stopAnimatingView() },
                                      completion: { _ in
                                        UIView.transition(with: strongBase, duration: transition.duration,
                                                          options: [transition.animationOptions, .allowUserInteraction],
                                                          animations: {
                                                            // Set image property in the animation.
                                                            設(shè)置圖片盆色,如果是自定義動(dòng)畫 在定義動(dòng)畫回調(diào)中設(shè)置圖片灰蛙,代碼在ImageTransition.swift
                                                            transition.animations?(strongBase, image)
                                                          },
                                                          completion: { finished in
                                                            動(dòng)畫結(jié)束回調(diào)
                                                            transition.completion?(finished)
                                                            completionHandler?(image, error, cacheType, imageURL)
                                                          })
                                      })
                #endif
            }
        })
    setImageTask(task)

return task
}
/**
Cancel the image download task bounded to the image view if it is running.
Nothing will happen if the downloading has already finished.
*/
取消下載
public func cancelDownloadTask() {
imageTask?.downloadTask?.cancel()
}
}
ImageView+Kingfisher 中的WebUR indicatorType indicator imageTask 屬性均使用屬性關(guān)聯(lián)技術(shù)實(shí)現(xiàn)數(shù)據(jù)的存取

四 、KingfisherManager

該類是Kingfisher唯一的一個(gè)管理調(diào)度類傅事。這個(gè)類有下載和緩存兩大功能模塊 主要包含了兩個(gè)屬性 兩個(gè)方法
public var cache: ImageCache 圖片緩存屬性
public var downloader: ImageDownloader 圖片下載屬性
func downloadAndCacheImage 下載并且緩存圖片方法
func tryToRetrieveImageFromCache 獲取緩存圖片

在ImageView+Kingfisher中最后圖片的獲取就是由KingfisherManager的單例實(shí)現(xiàn)的retrieveImage

外部調(diào)用獲取圖片方法
func retrieveImage(with resource: Resource,
options: KingfisherOptionsInfo?,
progressBlock: DownloadProgressBlock?,
completionHandler: CompletionHandler?) -> RetrieveImageTask{
let task = RetrieveImageTask()
if let options = options, options.forceRefresh {
強(qiáng)制刷新 從網(wǎng)絡(luò)獲取圖片
_ = downloadAndCacheImage(
with: resource.downloadURL,
forKey: resource.cacheKey,
retrieveImageTask: task,
progressBlock: progressBlock,
completionHandler: completionHandler,
options: options)
} else {
從緩存獲取圖片
tryToRetrieveImageFromCache(
forKey: resource.cacheKey,
with: resource.downloadURL,
retrieveImageTask: task,
progressBlock: progressBlock,
completionHandler: completionHandler,
options: options)
}
return task
}
下載并且緩存圖片的方法
func downloadAndCacheImage(with url: URL,
forKey key: String,
retrieveImageTask: RetrieveImageTask,
progressBlock: DownloadProgressBlock?,
completionHandler: CompletionHandler?,
options: KingfisherOptionsInfo?) -> RetrieveImageDownloadTask?
{
獲取下載器 并開啟下載
let options = options ?? KingfisherEmptyOptionsInfo
let downloader = options.downloader
return downloader.downloadImage(with: url, retrieveImageTask: retrieveImageTask, options: options,
progressBlock: { receivedSize, totalSize in
progressBlock?(receivedSize, totalSize)
},
completionHandler: { image, error, imageURL, originalData in

            let targetCache = options.targetCache
            if let error = error, error.code == KingfisherError.notModified.rawValue {
                // Not modified. Try to find the image from cache.
                // (The image should be in cache. It should be guaranteed by the framework users.)
               如果有錯(cuò)誤并且沒有修改過URL 返回緩存圖片
                targetCache.retrieveImage(forKey: key, options: options, completionHandler: { (cacheImage, cacheType) -> () in
                    completionHandler?(cacheImage, nil, cacheType, url)
                })
                return
            }
            緩存圖片 
            if let image = image, let originalData = originalData {
                targetCache.store(image,
                                  original: originalData,
                                  forKey: key,
                                  processorIdentifier:options.processor.identifier,
                                  cacheSerializer: options.cacheSerializer,
                                  toDisk: !options.cacheMemoryOnly,
                                  completionHandler: nil)
            }

            completionHandler?(image, error, .none, url)

        })
}

優(yōu)先從緩存獲取圖片,如緩存中沒有峡扩,在從網(wǎng)絡(luò)獲取圖片
func tryToRetrieveImageFromCache(forKey key: String,
with url: URL,
retrieveImageTask: RetrieveImageTask,
progressBlock: DownloadProgressBlock?,
completionHandler: CompletionHandler?,
options: KingfisherOptionsInfo?)
{
打破下面diskTask內(nèi)部閉包保持的循環(huán)引用蹭越,完成之后取消磁盤任務(wù)引用,避免循環(huán)引用教届,釋放內(nèi)存
let diskTaskCompletionHandler: CompletionHandler = { (image, error, cacheType, imageURL) -> () in
// Break retain cycle created inside diskTask closure below
retrieveImageTask.diskRetrieveTask = nil
completionHandler?(image, error, cacheType, imageURL)
}

    let targetCache = options?.targetCache ?? cache
    let diskTask = targetCache.retrieveImage(forKey: key, options: options,
        completionHandler: { image, cacheType in
            if image != nil {
                 成功返回圖片
                diskTaskCompletionHandler(image, nil, cacheType, url)
            } else if let options = options, options.onlyFromCache {
                返回失敗 并且設(shè)置只從緩存獲取圖片 返回沒有緩存錯(cuò)誤
                let error = NSError(domain: KingfisherErrorDomain, code: KingfisherError.notCached.rawValue, userInfo: nil)
                diskTaskCompletionHandler(nil, error, .none, url)
            } else {
                返回失敗 再從網(wǎng)絡(luò)下載圖片
                self.downloadAndCacheImage(
                    with: url,
                    forKey: key,
                    retrieveImageTask: retrieveImageTask,
                    progressBlock: progressBlock,
                    completionHandler: diskTaskCompletionHandler,
                    options: options)
            }
        }
    )
    retrieveImageTask.diskRetrieveTask = diskTask
}

五响鹃、 KingfisherOptionsInfo

上面代碼多次用到options這個(gè)參數(shù),它的參數(shù)類型是KingfisherOptionsInfo是一個(gè)類型別名
public typealias KingfisherOptionsInfo = [KingfisherOptionsInfoItem]
KingfisherOptionsInfoItem 是一個(gè)枚舉 配置 Kingfisher所有功能行為 下面是詳細(xì)中文注釋

public enum KingfisherOptionsInfoItem {

這個(gè)成員的關(guān)聯(lián)值是一個(gè)ImageCache對(duì)象案训。 Kingfisher使用指定的緩存對(duì)象處理 相關(guān)業(yè)務(wù),包括試圖檢索緩存圖像和存儲(chǔ)下載的圖片买置。
case targetCache(ImageCache)

這個(gè)成員的關(guān)聯(lián)值應(yīng)該是一個(gè)ImageDownloader對(duì)象。Kingfisher將使用這個(gè)下載器下載的圖片强霎。
case downloader(ImageDownloader)

如果從網(wǎng)絡(luò)下載的圖片 Kingfisher將使用“ImageTransition這個(gè)枚舉動(dòng)畫忿项。從內(nèi)存或磁盤緩存時(shí)默認(rèn)過渡不會(huì)發(fā)生。如果需要,設(shè)置ForceTransition
case transition(ImageTransition)

有關(guān)“浮動(dòng)”值將被設(shè)置為圖像下載任務(wù)的優(yōu)先級(jí)城舞。值在0.0 ~ 1.0之間轩触。如果沒有設(shè)置這個(gè)選項(xiàng),默認(rèn)值(“NSURLSessionTaskPriorityDefault”)將被使用。
case downloadPriority(Float)

如果設(shè)置,將忽略緩存,開啟一個(gè)下載任務(wù)的資源
case forceRefresh

如果設(shè)置 即使緩存的圖片也將開啟過渡動(dòng)畫
case forceTransition

如果設(shè)置家夺,Kingfisher只會(huì)在內(nèi)存中緩存值而不是磁盤
case cacheMemoryOnly

如果設(shè)置 Kingfisher只會(huì)從緩存中加載圖片
case onlyFromCache

在使用之前在后臺(tái)線程解碼圖像
case backgroundDecode

當(dāng)從緩存檢索圖像時(shí) 這個(gè)成員的關(guān)聯(lián)值將被用作目標(biāo)隊(duì)列的調(diào)度時(shí)回調(diào)脱柱。如果沒 有設(shè)置, Kingfisher將使用主要quese回調(diào)
case callbackDispatchQueue(DispatchQueue?)

將檢索到的圖片數(shù)據(jù)轉(zhuǎn)換成一個(gè)圖時(shí) 這個(gè)成員變量將被用作圖片縮放因子。圖像分辨率,而不是屏幕尺寸拉馋。你可能處理時(shí)需要指定正確的縮放因子@2x或@3x Retina圖像榨为。
case scaleFactor(CGFloat)

是否所有的GIF應(yīng)該加載數(shù)據(jù)惨好。默認(rèn)false,只顯示GIF中第一張圖片随闺。如果true,所有的GIF數(shù)據(jù)將被加載到內(nèi)存中進(jìn)行解碼日川。這個(gè)選項(xiàng)主要是用于內(nèi)部的兼容性。你不應(yīng)該把直接設(shè)置它板壮《好“AnimatedImageView”不會(huì)預(yù)加載所有數(shù)據(jù),而一個(gè)正常的圖像視圖(“UIImageView”或“NSImageView”)將加載所有數(shù)據(jù)。選擇使用相應(yīng)的圖像視圖類型而不是設(shè)置這個(gè)選項(xiàng)绰精。
case preloadAllGIFData

發(fā)送請(qǐng)求之前用于改變請(qǐng)求撒璧。這是最后的機(jī)會(huì)你可以修改請(qǐng)求。您可以修改請(qǐng)求一些定制的目的,如添加身份驗(yàn)證令牌頭,進(jìn)行基本的HTTP身份驗(yàn)證或類似的url映射笨使。原始請(qǐng)求默認(rèn)情況下將沒有任何修改
case requestModifier(ImageDownloadRequestModifier)

下載完成時(shí),處理器會(huì)將下載的數(shù)據(jù)轉(zhuǎn)換為一個(gè)圖像卿樱。如果緩存連接到下載器(當(dāng)你正在使用KingfisherManager或圖像擴(kuò)展方法),轉(zhuǎn)換后的圖像也將被緩存
case processor(ImageProcessor)

提供一個(gè)CacheSerializer 可用于圖像對(duì)象序列化成圖像數(shù)據(jù)存儲(chǔ)到磁盤緩存和從磁盤緩存將圖片數(shù)據(jù)反序列化成圖像對(duì)象
case cacheSerializer(CacheSerializer)

保持現(xiàn)有的圖像同時(shí)設(shè)置另一個(gè)圖像圖像視圖。通過設(shè)置這個(gè)選項(xiàng),imageview的placeholder參數(shù)將被忽略和當(dāng)前圖像保持同時(shí)加載新圖片
case keepCurrentImageWhileLoading

}
下面是自定義<== 運(yùn)算符 比較兩個(gè)KingfisherOptionsInfoItem 是否相等 相等返回true 否則返回false

precedencegroup ItemComparisonPrecedence {
associativity: none
higherThan: LogicalConjunctionPrecedence
}

infix operator <== : ItemComparisonPrecedence

// This operator returns true if two KingfisherOptionsInfoItem enum is the same, without considering the associated values.
func <== (lhs: KingfisherOptionsInfoItem, rhs: KingfisherOptionsInfoItem) -> Bool {
switch (lhs, rhs) {
case (.targetCache(), .targetCache()): return true
case (.downloader(), .downloader()): return true
case (.transition(), .transition()): return true
case (.downloadPriority(), .downloadPriority()): return true
case (.forceRefresh, .forceRefresh): return true
case (.forceTransition, .forceTransition): return true
case (.cacheMemoryOnly, .cacheMemoryOnly): return true
case (.onlyFromCache, .onlyFromCache): return true
case (.backgroundDecode, .backgroundDecode): return true
case (.callbackDispatchQueue(), .callbackDispatchQueue()): return true
case (.scaleFactor(), .scaleFactor()): return true
case (.preloadAllGIFData, .preloadAllGIFData): return true
case (.requestModifier(), .requestModifier()): return true
case (.processor(), .processor()): return true
case (.cacheSerializer(), .cacheSerializer()): return true
case (.keepCurrentImageWhileLoading, .keepCurrentImageWhileLoading): return true
default: return false
}
}
下面是對(duì)CollectionType的一個(gè)擴(kuò)展 返回匹配的第一個(gè)相同枚舉值 上面過渡動(dòng)畫就有用到

public extension Collection where Iterator.Element == KingfisherOptionsInfoItem {
func firstMatchIgnoringAssociatedValue(_ target: Iterator.Element) -> Iterator.Element? {
return index { $0 <== target }.flatMap { self[$0] }
}

func removeAllMatchesIgnoringAssociatedValue(_ target: Iterator.Element) -> [Iterator.Element] {
    return self.filter { !($0 <== target) }
}

}
在KingfisherOptionsInfo中有很多的類似的屬性get方法 如下是關(guān)于圖片編碼的硫椰,默認(rèn)返回DefaultCacheSerializer.default繁调。如果要自定義圖片編碼,可以添加自定義CacheSerializer 到Options數(shù)組

public var cacheSerializer: CacheSerializer {
if let item = firstMatchIgnoringAssociatedValue(.cacheSerializer(DefaultCacheSerializer.default)),
case .cacheSerializer(let cacheSerializer) = item
{
return cacheSerializer
}
return DefaultCacheSerializer.default
}

作者:一口氣全念完
鏈接:http://www.reibang.com/p/a47fefeed7f0
來源:簡書
著作權(quán)歸作者所有靶草。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)蹄胰,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奕翔,一起剝皮案震驚了整個(gè)濱河市裕寨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌派继,老刑警劉巖宾袜,帶你破解...
    沈念sama閱讀 223,207評(píng)論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異驾窟,居然都是意外死亡庆猫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門绅络,熙熙樓的掌柜王于貴愁眉苦臉地迎上來月培,“玉大人,你說我怎么就攤上這事恩急〗谑樱” “怎么了?”我有些...
    開封第一講書人閱讀 170,031評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵假栓,是天一觀的道長寻行。 經(jīng)常有香客問我,道長匾荆,這世上最難降的妖魔是什么拌蜘? 我笑而不...
    開封第一講書人閱讀 60,334評(píng)論 1 300
  • 正文 為了忘掉前任杆烁,我火速辦了婚禮,結(jié)果婚禮上简卧,老公的妹妹穿的比我還像新娘兔魂。我一直安慰自己,他們只是感情好举娩,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,322評(píng)論 6 398
  • 文/花漫 我一把揭開白布析校。 她就那樣靜靜地躺著,像睡著了一般铜涉。 火紅的嫁衣襯著肌膚如雪智玻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,895評(píng)論 1 314
  • 那天芙代,我揣著相機(jī)與錄音吊奢,去河邊找鬼。 笑死纹烹,一個(gè)胖子當(dāng)著我的面吹牛页滚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铺呵,決...
    沈念sama閱讀 41,300評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼裹驰,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了片挂?” 一聲冷哼從身側(cè)響起幻林,我...
    開封第一講書人閱讀 40,264評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宴卖,沒想到半個(gè)月后滋将,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邻悬,經(jīng)...
    沈念sama閱讀 46,784評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡症昏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,870評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了父丰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肝谭。...
    茶點(diǎn)故事閱讀 40,989評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蛾扇,靈堂內(nèi)的尸體忽然破棺而出攘烛,到底是詐尸還是另有隱情,我是刑警寧澤镀首,帶...
    沈念sama閱讀 36,649評(píng)論 5 351
  • 正文 年R本政府宣布坟漱,位于F島的核電站,受9級(jí)特大地震影響更哄,放射性物質(zhì)發(fā)生泄漏芋齿。R本人自食惡果不足惜腥寇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,331評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望觅捆。 院中可真熱鬧赦役,春花似錦、人聲如沸栅炒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,814評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赢赊。三九已至乙漓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間域携,已是汗流浹背簇秒。 一陣腳步聲響...
    開封第一講書人閱讀 33,940評(píng)論 1 275
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秀鞭,地道東北人趋观。 一個(gè)月前我還...
    沈念sama閱讀 49,452評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像锋边,于是被迫代替她去往敵國和親皱坛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,995評(píng)論 2 361