AlamofireImage 源碼閱讀

在AlamofireImage中一共就只有5個(gè)類加一些擴(kuò)展

// 錯誤處理類,繼承自Error限煞,主要有requestCancelled(請求取消)、imageSerializationFailed(請求失敗)兩種錯誤
AFIError
// 定義圖片對象,主要用來適配mac(NSImage)和ios(UIImage)平臺
Image
// 圖片內(nèi)存緩存對象
ImageCache
// 圖片下載對象(下載基于Alamofire)
ImageDownloader
// 圖片濾鏡對象(CoreGraphics切圓角振劳,CoreImage濾鏡)
ImageFilter

一、圖片加載過程

AlamofireImage中的擴(kuò)展定義了很多快速對UI控件設(shè)置圖片的方法油狂,我挑其中一個(gè)來詳解AlamofireImage是怎樣將圖片加載到視圖上的

// 該方法是UIImageView的一個(gè)擴(kuò)展方法,其它控件的擴(kuò)展方法都差不多一樣
public func af_setImage(
        withURLRequest urlRequest: URLRequestConvertible,
        placeholderImage: UIImage? = nil,
        filter: ImageFilter? = nil,
        progress: ImageDownloader.ProgressHandler? = nil,
        progressQueue: DispatchQueue = DispatchQueue.main,
        imageTransition: ImageTransition = .noTransition,
        runImageTransitionIfCached: Bool = false,
        completion: ((DataResponse<UIImage>) -> Void)? = nil)
    {
        /* 1.判斷ImageView是否正在下載該url圖片
         注:Alamofire通過runtime將正在下載圖片的請求對象RequestReceipt綁定到af_activeRequestReceipt屬性弱贼, 在請求完成后再將af_activeRequestReceipt置為nil吮旅,
         所以如果ImageView正在下載圖片味咳,而你此時(shí)再次請求下載圖片時(shí)槽驶,它會進(jìn)行判斷,
         如果兩次的url相同罕拂,則不需要再次下載了聂受,所有這里會立即返回錯誤:"取消了一次請求"
        */
        guard !isURLRequestURLEqualToActiveRequestURL(urlRequest) else {
            let error = AFIError.requestCancelled
            let response = DataResponse<UIImage>(request: nil, response: nil, data: nil, result: .failure(error))
            completion?(response)
            return
        }

        // 2.取消之前的所有請求
        af_cancelImageRequest()

        // 3.獲取一個(gè)下載器
        let imageDownloader = af_imageDownloader ?? UIImageView.af_sharedImageDownloader

        // 4.獲取下載器的緩存對象
        let imageCache = imageDownloader.imageCache

        // 5.如果存在緩存
        if  let request = urlRequest.urlRequest,
            let image = imageCache?.image(for: request, withIdentifier: filter?.identifier)
        {
            // 構(gòu)建一個(gè)返回對象
            let response = DataResponse<UIImage>(request: request, response: nil, data: nil, result: .success(image))
            if runImageTransitionIfCached {
                // 如果有圖片加載動畫蛋济,加載圖片并執(zhí)行動畫渡处,并執(zhí)行完成回調(diào)閉包
                let tinyDelay = DispatchTime.now() + Double(Int64(0.001 * Float(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
                DispatchQueue.main.asyncAfter(deadline: tinyDelay) {
                    self.run(imageTransition, with: image)
                    completion?(response)
                }
            } else {
                // 如果沒有圖片加載動畫祟辟,直接加載圖片旧困,并執(zhí)行完成回調(diào)閉包
                self.image = image
                completion?(response)
            }
            return
        }

        // 6.如果沒有緩存,則先加載占位圖
        if let placeholderImage = placeholderImage { self.image = placeholderImage }

        // 生成唯一的下載標(biāo)識符僚纷,以檢查下載在請求中是否發(fā)生了更改
        let downloadID = UUID().uuidString

        // 7.開始下載圖片
        let requestReceipt = imageDownloader.download(
            urlRequest,
            receiptID: downloadID,
            filter: filter,
            progress: progress,
            progressQueue: progressQueue,
            completion: { [weak self] response in
                // 如果ImageView當(dāng)前下載圖片的url和返回的url相同怖竭,并且下載的標(biāo)識符也相同痊臭,說明已經(jīng)下載過了广匙,所以這里直接返回完成鸦致,不需要再做其它了
                guard let strongSelf = self,
                          strongSelf.isURLRequestURLEqualToActiveRequestURL(response.request) &&
                          strongSelf.af_activeRequestReceipt?.receiptID == downloadID else {
                    completion?(response)
                    return
                }

                // 8.下載圖片成功蹋凝,加載圖片
                if let image = response.result.value {
                    strongSelf.run(imageTransition, with: image)
                }

                // 9.清除當(dāng)前的下載票據(jù)鳍寂,回調(diào)加載下載完成
                strongSelf.af_activeRequestReceipt = nil
                completion?(response)
            }
        )

        // 存儲ImageView正在請求的信息
        af_activeRequestReceipt = requestReceipt
    }

下面是下載類核心方法和注解

@discardableResult
    open func download(
        _ urlRequest: URLRequestConvertible,
        receiptID: String = UUID().uuidString,
        filter: ImageFilter? = nil,
        progress: ProgressHandler? = nil,
        progressQueue: DispatchQueue = DispatchQueue.main,
        completion: CompletionHandler?)
        -> RequestReceipt?
    {
        var request: DataRequest!
        
        // 異步加載圖片
        synchronizationQueue.sync {
            // 再次判斷該請求是否正在請求捍壤,如果是鞍爱,則在responseHandlers屬性中添加本次的回調(diào)閉包(多個(gè)view同時(shí)加載同一張圖片的情況)
            // 注:ImageDownloader在responseHandlers屬性中盗扇,存儲正在下載的請求疗隶,以防止相同的請求多次發(fā)出斑鼻,
            // 這樣不僅浪費(fèi)流量還會影響加載速度坚弱,如果有多個(gè)相同的請求時(shí)荒叶,我們只會發(fā)出一個(gè)拒垃,在完成后一起回調(diào)
            let urlID = ImageDownloader.urlIdentifier(for: urlRequest)
            if let responseHandler = self.responseHandlers[urlID] {
                responseHandler.operations.append((receiptID: receiptID, filter: filter, completion: completion))
                request = responseHandler.request
                return
            }

            // (內(nèi)存緩存)如果允許緩存悼瓮,再次嘗試從緩存加載圖像
            if let request = urlRequest.urlRequest {
                switch request.cachePolicy {
                case .useProtocolCachePolicy, .returnCacheDataElseLoad, .returnCacheDataDontLoad:
                    if let image = self.imageCache?.image(for: request, withIdentifier: filter?.identifier) {
                        DispatchQueue.main.async {
                            let response = DataResponse<Image>(
                                request: urlRequest.urlRequest,
                                response: nil,
                                data: nil,
                                result: .success(image)
                            )
                            completion?(response)
                        }
                        return
                    }
                default:
                    break
                }
            }

            // 創(chuàng)建下載器
            request = self.sessionManager.request(urlRequest)
            if let credential = self.credential {
                request.authenticate(usingCredential: credential)
            }

            request.validate()
            if let progress = progress {
                request.downloadProgress(queue: progressQueue, closure: progress)
            }

            // 生成唯一的下載標(biāo)識符横堡,以檢查下載過程中請求是否發(fā)生了更改
            let handlerID = UUID().uuidString

            // 開始請求(這其中默認(rèn)會去獲取NSURLCache的緩存,內(nèi)存緩存和磁盤緩存均有)
            request.response(queue: self.responseQueue, responseSerializer: imageResponseSerializer, completionHandler: { [weak self] response in
                    guard let strongSelf = self, let request = response.request else { return }

                    defer {
                        // 減少當(dāng)前需要下載的任務(wù)數(shù)
                        strongSelf.safelyDecrementActiveRequestCount()
                        // 如果還有食听,則開始下一個(gè)下載
                        strongSelf.safelyStartNextRequestIfNecessary()
                    }

                    // 將該請求從正在下載任務(wù)中移除
                    let handler = strongSelf.safelyFetchResponseHandler(withURLIdentifier: urlID)
                    guard handler?.handlerID == handlerID else { return }
                    guard let responseHandler = strongSelf.safelyRemoveResponseHandler(withURLIdentifier: urlID) else {
                        return
                    }

                    switch response.result {
                    case .success(let image):
                        var filteredImages: [String: Image] = [:]

                        // 下載完成
                        for (_, filter, completion) in responseHandler.operations {
                            var filteredImage: Image

                            if let filter = filter {
                                if let alreadyFilteredImage = filteredImages[filter.identifier] {
                                    filteredImage = alreadyFilteredImage
                                } else {
                                    filteredImage = filter.filter(image)
                                    filteredImages[filter.identifier] = filteredImage
                                }
                            } else {
                                filteredImage = image
                            }

                            // 內(nèi)存緩存圖片
                            strongSelf.imageCache?.add(filteredImage, for: request, withIdentifier: filter?.identifier)

                            // 返回Image
                            DispatchQueue.main.async {
                                let response = DataResponse<Image>(
                                    request: response.request,
                                    response: response.response,
                                    data: response.data,
                                    result: .success(filteredImage),
                                    timeline: response.timeline
                                )
                                completion?(response)
                            }
                        }
                    case .failure:
                        // 下載失敗
                        for (_, _, completion) in responseHandler.operations {
                            DispatchQueue.main.async { completion?(response) }
                        }
                    }
                }
            )

            // 存儲正在下載的任務(wù)
            let responseHandler = ResponseHandler(
                request: request,
                handlerID: handlerID,
                receiptID: receiptID,
                filter: filter,
                completion: completion
            )
            self.responseHandlers[urlID] = responseHandler

            // 開始任務(wù)或者加載到任務(wù)隊(duì)列中
            if self.isActiveRequestCountBelowMaximumLimit() {
                self.start(request)
            } else {
                self.enqueue(request)
            }
        }
        if let request = request {
            return RequestReceipt(request: request, receiptID: receiptID)
        }
        return nil
    }

總結(jié)下載過程
1.在內(nèi)存緩存對象(ImageCache)中獲取緩存迹蛤,如果有則返回圖片
2.在NSURLCache中獲取緩存(內(nèi)存緩存+磁盤緩存),如果有則返回圖片
3.開始網(wǎng)絡(luò)下載圖片陋桂,成功后返回圖片
4.緩存圖片
5.檢查是否使用濾鏡嗜历、加載動畫等加載圖片

二秸脱、緩存實(shí)現(xiàn)

1.ImageCache摊唇,實(shí)際上它只是一個(gè)協(xié)議巷查,真正緩存的對象是CachedImage

class CachedImage {
    // 圖片
    let image: Image
    // 標(biāo)識符
    let identifier: String
    // 圖片大小
    let totalBytes: UInt64
    // 最后修改時(shí)間
    var lastAccessDate: Date 
}

在ImageCache協(xié)議中有如下幾個(gè)方法

/// 通過標(biāo)識符添加一個(gè)緩存圖片
func add(_ image: Image, withIdentifier identifier: String)

/// 通過標(biāo)識符移除一個(gè)圖片
func removeImage(withIdentifier identifier: String) -> Bool

/// 移除所有圖片
func removeAllImages() -> Bool

/// 通過標(biāo)識符獲取圖片
func image(withIdentifier identifier: String) -> Image?

實(shí)際對圖片存儲的類是AutoPurgingImageCache,它實(shí)現(xiàn)了ImageCache協(xié)議崇败,下再來看一下它的一些屬性

// 最大內(nèi)存容量
open let memoryCapacity: UInt64
// 當(dāng)內(nèi)存容量達(dá)到最大值后室,清除后的剩余內(nèi)存(當(dāng)內(nèi)存達(dá)到最大值時(shí):清除部分 = memoryCapacity - preferredMemoryUsageAfterPurge)
open let preferredMemoryUsageAfterPurge: UInt64
// 操作線程
private let synchronizationQueue: DispatchQueue
// 緩存(說明AlamofireImage的圖片緩存是一個(gè)字典)
private var cachedImages: [String: CachedImage]
// 當(dāng)前內(nèi)存
private var currentMemoryUsage: UInt64

下面是添加緩存的核心函數(shù)

open func add(_ image: Image, withIdentifier identifier: String) {
        synchronizationQueue.async(flags: [.barrier]) {
            // 緩存圖片岸霹,并計(jì)算當(dāng)前的內(nèi)存緩存大小
            let cachedImage = CachedImage(image, identifier: identifier)
            if let previousCachedImage = self.cachedImages[identifier] {
                self.currentMemoryUsage -= previousCachedImage.totalBytes
            }
            self.cachedImages[identifier] = cachedImage
            self.currentMemoryUsage += cachedImage.totalBytes
        }
        // 這里的barrier贡避,保證了當(dāng)上面的圖片緩存完成之后才會執(zhí)行下面的代碼
        synchronizationQueue.async(flags: [.barrier]) {
            // 當(dāng)前緩存大于最大緩存時(shí)
            if self.currentMemoryUsage > self.memoryCapacity {
                // 計(jì)算需要清除的緩存大小
                let bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge
                // 對圖片進(jìn)行時(shí)間排序,我們需要清除較早的一些圖片
                var sortedImages = self.cachedImages.map { $1 }
                sortedImages.sort {
                    let date1 = $0.lastAccessDate
                    let date2 = $1.lastAccessDate
                    return date1.timeIntervalSince(date2) < 0.0
                }
                // 開始清除緩存
                var bytesPurged = UInt64(0)
                for cachedImage in sortedImages {
                    self.cachedImages.removeValue(forKey: cachedImage.identifier)
                    bytesPurged += cachedImage.totalBytes
                    if bytesPurged >= bytesToPurge {
                        break
                    }
                }
                // 清除緩存完成掖蛤,設(shè)置當(dāng)前緩存的大小
                self.currentMemoryUsage -= bytesPurged
            }
        }
    }

2.NSURLCache坠七,它我這里就不具體講了,有興趣的可以看這篇文章 http://nshipster.cn/nsurlcache/

三蝇恶、加載動畫和濾鏡

對于這一部分內(nèi)容惶桐,我自己也沒有使用過姚糊,所以下面只貼出源碼加注釋贸辈,有興趣的讀者可以自己去研究

1.動畫

// 加載動畫(是用UIView的動畫來實(shí)現(xiàn)的)
public func run(_ imageTransition: ImageTransition, with image: Image) {
    UIView.transition(
        with: self,
        duration: imageTransition.duration,
        options: imageTransition.animationOptions,
        animations: { imageTransition.animations(self, image) },
        completion: imageTransition.completion
    )
}
public enum ImageTransition {
    // 所有可選的動畫類型
    // 沒有動畫
    case noTransition
    // 淡入淡出
    case crossDissolve(TimeInterval)
    // 向下翻頁
    case curlDown(TimeInterval)
    // 向上翻頁
    case curlUp(TimeInterval)
    // 從下肠槽、左擎淤、右、上翻出
    case flipFromBottom(TimeInterval)
    case flipFromLeft(TimeInterval)
    case flipFromRight(TimeInterval)
    case flipFromTop(TimeInterval)
    // 自定義動畫
    case custom(
        duration: TimeInterval,
        animationOptions: UIViewAnimationOptions,
        animations: (UIImageView, Image) -> Void,
        completion: ((Bool) -> Void)?
    )
}

2.濾鏡

// 將圖片變化到指定大小
public struct ScaledToSizeFilter: ImageFilter, Sizable {
    /// 變換后的大小
    public let size: CGSize
    public init(size: CGSize) {
        self.size = size
    }
    /// 實(shí)現(xiàn)函數(shù)
    public var filter: (Image) -> Image {
        return { image in
            return image.af_imageScaled(to: self.size)
        }
    }
}

/// 拉伸適應(yīng)
public struct AspectScaledToFitSizeFilter: ImageFilter, Sizable {
    public let size: CGSize
    public init(size: CGSize) {
        self.size = size
    }
    /// 實(shí)現(xiàn)函數(shù)
    public var filter: (Image) -> Image {
        return { image in
            return image.af_imageAspectScaled(toFit: self.size)
        }
    }
}

/// 拉伸鋪滿
public struct AspectScaledToFillSizeFilter: ImageFilter, Sizable {
    public let size: CGSize
    public init(size: CGSize) {
        self.size = size
    }
    /// 實(shí)現(xiàn)函數(shù)
    public var filter: (Image) -> Image {
        return { image in
            return image.af_imageAspectScaled(toFill: self.size)
        }
    }
}

/// 圓角
public struct RoundedCornersFilter: ImageFilter, Roundable {
    /// 圓角大小 
    public let radius: CGFloat
    /// 圓角的大小需不需要進(jìn)行縮放radius / scale
    public let divideRadiusByImageScale: Bool
    public init(radius: CGFloat, divideRadiusByImageScale: Bool = false) {
        self.radius = radius
        self.divideRadiusByImageScale = divideRadiusByImageScale
    }
    /// 實(shí)現(xiàn)函數(shù)
    public var filter: (Image) -> Image {
        return { image in
            return image.af_imageRounded(
                withCornerRadius: self.radius,
                divideRadiusByImageScale: self.divideRadiusByImageScale
            )
        }
    }
}

/// 圓形圖片
public struct CircleFilter: ImageFilter {
    public init() {}
    /// 實(shí)現(xiàn)函數(shù)
    public var filter: (Image) -> Image {
        return { image in
            return image.af_imageRoundedIntoCircle()
        }
    }
}

// CoreImage 濾鏡
public protocol CoreImageFilter: ImageFilter {
    /// 濾鏡名稱
    var filterName: String { get }
    /// 濾鏡參數(shù)
    var parameters: [String: Any] { get }
}

/// 高斯模糊濾鏡
public struct BlurFilter: ImageFilter, CoreImageFilter {
    /// 高斯濾鏡名稱
    public let filterName = "CIGaussianBlur"
    /// 參數(shù)
    public let parameters: [String: Any]
    public init(blurRadius: UInt = 10) {
        self.parameters = ["inputRadius": blurRadius]
    }
}

3.CoreImage 濾鏡具體實(shí)現(xiàn)的核心函數(shù)

extension UIImage {
    public func af_imageFiltered(withCoreImageFilter name: String, parameters: [String: Any]? = nil) -> UIImage? {
        var image: CoreImage.CIImage? = ciImage

        if image == nil, let CGImage = self.cgImage {
            image = CoreImage.CIImage(cgImage: CGImage)
        }

        guard let coreImage = image else { return nil }

        let context = CIContext(options: [kCIContextPriorityRequestLow: true])

        var parameters: [String: Any] = parameters ?? [:]
        parameters[kCIInputImageKey] = coreImage

        guard let filter = CIFilter(name: name, withInputParameters: parameters) else { return nil }
        guard let outputImage = filter.outputImage else { return nil }

        let cgImageRef = context.createCGImage(outputImage, from: outputImage.extent)

        return UIImage(cgImage: cgImageRef!, scale: scale, orientation: imageOrientation)
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秸仙,一起剝皮案震驚了整個(gè)濱河市嘴拢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寂纪,老刑警劉巖席吴,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捞蛋,居然都是意外死亡襟交,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門贞言,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事捶闸《夷担” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵墩朦,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任坤溃,我火速辦了婚禮,結(jié)果婚禮上吠裆,老公的妹妹穿的比我還像新娘抠蚣。我一直安慰自己柄冲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布低千。 她就那樣靜靜地躺著矾芙,像睡著了一般壹无。 火紅的嫁衣襯著肌膚如雪岖是。 梳的紋絲不亂的頭發(fā)上烈疚,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音绳慎,去河邊找鬼磨确。 笑死,一個(gè)胖子當(dāng)著我的面吹牛亥曹,可吹牛的內(nèi)容都是我干的骗炉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乍丈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起镜雨,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤奴璃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后间聊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體攒盈,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年哎榴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了型豁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片僵蛛。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖偷遗,靈堂內(nèi)的尸體忽然破棺而出墩瞳,到底是詐尸還是另有隱情驼壶,我是刑警寧澤氏豌,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站热凹,受9級特大地震影響泵喘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜般妙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一纪铺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碟渺,春花似錦鲜锚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绒极,卻和暖如春骏令,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背垄提。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工榔袋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铡俐。 一個(gè)月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓凰兑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親审丘。 傳聞我的和親對象是個(gè)殘疾皇子吏够,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,321評論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,089評論 1 32
  • 圖片下載的這些回調(diào)信息存儲在SDWebImageDownloader類的URLOperations屬性中,該屬性是...
    怎樣m閱讀 2,361評論 0 1
  • 上周去見了一位朋友备恤,L小姐稿饰。 我整整坐了兩個(gè)多小時(shí)的地鐵才到她住處附近。見到她時(shí)露泊,天已經(jīng)黑了喉镰,等了她一會,她才看著...
    叫我豌豆公主閱讀 739評論 3 1
  • 關(guān)注“尹娃娃”惭笑,那里有很棒的小說侣姆,不去你會后悔的生真!
    lililili李閱讀 204評論 0 1