從JSCore了解Hybrid開(kāi)發(fā)

Hybrid

前言

最近因?yàn)楣ぷ鞯脑蜓咏欤絹?lái)越多的動(dòng)態(tài)化開(kāi)發(fā)模式開(kāi)始在項(xiàng)目中實(shí)施址貌。為了對(duì)Hybrid的開(kāi)發(fā)有一個(gè)深入的了解脚翘,查閱了相關(guān)的博客和官方文檔之后,決定把學(xué)到的東西在這里做一個(gè)總結(jié)炎咖,方便日后查閱赃泡。好了,廢話不多說(shuō)乘盼,要研究Hybrid開(kāi)發(fā)升熊,其中必不可少的是要去了解JavaScriptCore(以下簡(jiǎn)稱JSCore)。那么我們就先從
JSCore入手绸栅,看看到底是怎么一個(gè)玩法级野。

引用文檔:


什么是JSCore

JSCoreWebKit默認(rèn)內(nèi)嵌的JS引擎。它建立起了Objective-CJavaScript兩門(mén)語(yǔ)言溝通的橋梁粹胯。iOS7之后蓖柔,蘋(píng)果對(duì)WebKit中的JSCore進(jìn)行了Objective-C的封裝,并提供給所有的iOS開(kāi)發(fā)者矛双。JSCore框架給Swift渊抽、OC以及C語(yǔ)言編寫(xiě)的App提供了調(diào)用JS程序的能力。同時(shí)我們也可以使用JSCore往JS環(huán)境中去插入一些自定義對(duì)象议忽。JSCore作為蘋(píng)果的瀏覽器引擎WebKit中重要組成部分,這個(gè)JS引擎已經(jīng)存在多年十减。

在業(yè)界中流行的動(dòng)態(tài)化開(kāi)發(fā)方案栈幸,如React NativeWeex等愤估。其核心模塊中必不可少的會(huì)用到JSCoreJSCore跟Google自己研發(fā)的瀏覽器引擎Chrome的V8一樣速址,都是為了解釋執(zhí)行JS的腳本玩焰。

JSCore的四個(gè)基本類

JSCore基本類

上圖是蘋(píng)果官網(wǎng)對(duì)JSCore的介紹。從圖中我們可以很清晰的看到芍锚,四個(gè)主要核心類分別就是:JSContext昔园、JSManagedValueJSValue并炮、JSVirtualMachine(以下簡(jiǎn)稱JSVM)默刚。那么我們接下來(lái)就來(lái)分別看看這些類是干嘛用的。

JSContext

一個(gè)JSContext表示了一次JS的執(zhí)行環(huán)境逃魄。我們可以通過(guò)創(chuàng)建一個(gè)JSContext去調(diào)用JS腳本荤西,訪問(wèn)一些JS定義的值和函數(shù),同時(shí)也提供了讓JS訪問(wèn)Native對(duì)象伍俘,方法的接口邪锌。

從字面上面來(lái)看,JSContext好像就是“上下文”的意思癌瘾。那么什么是上下文呢觅丰?

比如在一篇文章中,我們看到一句話:“他飛快的跑了出去妨退〔罢停”但是如果我們不看上下文的話,我們并不知道這句話究竟是什么意思:誰(shuí)跑了出去碧注?他是誰(shuí)嚣伐?他為什么要跑?
寫(xiě)計(jì)算機(jī)理解的程序語(yǔ)言跟寫(xiě)文章是相似的萍丐,我們運(yùn)行任何一段語(yǔ)句都需要有這樣一個(gè)“上下文”的存在轩端。比如之前外部變量的引入、全局變量逝变、函數(shù)的定義基茵、已經(jīng)分配的資源等等。有了這些信息壳影,我們才能準(zhǔn)確的執(zhí)行每一句代碼拱层。

所以說(shuō),JSContext也就是JS的執(zhí)行環(huán)境(也可以說(shuō)是執(zhí)行上下文)宴咧,所有的JS代碼都必須在一個(gè)JSContext中執(zhí)行根灯。如果我們要在WebView中去獲取JSContext,可以直接通過(guò)KVC的方式直接獲取。

    JSContext *context = [[JSContext alloc] init];
    [context evaluateScript:@"var a = 1;var b = 2;"];
    NSInteger sum = [[context evaluateScript:@"a + b"] toInt32];//sum=3

我們先創(chuàng)建一個(gè)JSContext的環(huán)境烙肺,然后直接通過(guò)evaluateScript方法就可以直接運(yùn)行一段寫(xiě)好的JS的代碼纳猪。然后返回值是通過(guò)JSValue(后面會(huì)有介紹)進(jìn)行包裝后返回。

上面提到了我們要獲取WebView中的JSContext桃笙,可以用KVC的方式氏堤。同樣的,我們要給JSContext塞全局對(duì)象和全局函數(shù)搏明,也可以使用KVC的方式:

    JSContext *context = [[JSContext alloc] init];
        context[@"globalFunc"] =  ^() {
        NSArray *args = [JSContext currentArguments];
        for (id obj in args) {
            NSLog(@"拿到了參數(shù):%@", obj);
        }
    };
    context[@"globalProp"] = @"全局變量字符串";
   [context evaluateScript:@"globalFunc(globalProp)"];//console輸出:“拿到了參數(shù):全局變量字符串”

在JSContext的API中鼠锈,有一個(gè)值得注意的只讀屬性 – JSValue類型的globalObject。它返回當(dāng)前執(zhí)行JSContext的全局對(duì)象星著,例如在WebKit中购笆,JSContext就會(huì)返回當(dāng)前的Window對(duì)象。而這個(gè)全局對(duì)象其實(shí)也是JSContext最核心的東西强饮,當(dāng)我們通過(guò)KVC方式與JSContext進(jìn)去取值賦值的時(shí)候由桌,實(shí)際上都是在跟這個(gè)全局對(duì)象做交互,幾乎所有的東西都在全局對(duì)象里邮丰,可以說(shuō)行您,JSContext只是globalObject的一層殼。

JSManagedValue

一個(gè) JSManagedValue 對(duì)象是用來(lái)包裝一個(gè) JSValue 對(duì)象的剪廉,JSManagedValue 對(duì)象通過(guò)添加“有條件的持有(conditional retain)”行為來(lái)實(shí)現(xiàn)自動(dòng)內(nèi)存管理娃循。一個(gè)managed value 的基本用法就是用來(lái)在一個(gè)要導(dǎo)出(exported)到 JavaScript 的 Objective-C 或者 Swift 對(duì)象中存儲(chǔ)一個(gè) JavaScript 值。

這里順便說(shuō)一下JS的GC機(jī)制
JS同樣也不需要我們?nèi)ナ謩?dòng)管理內(nèi)存斗蒋。JS的內(nèi)存管理使用的是GC機(jī)制捌斧。不同于OC的引用計(jì)數(shù),GC是由GCRoot(context)開(kāi)始維護(hù)的一條引用鏈泉沾,一旦引用鏈無(wú)法觸達(dá)某對(duì)象節(jié)點(diǎn)捞蚂,這個(gè)對(duì)象就會(huì)被回收掉。

JSValue

JSValue實(shí)例是一個(gè)指向JS值的引用指針跷究。我們可以使用JSValue類姓迅,在OC和JS的基礎(chǔ)數(shù)據(jù)類型之間相互轉(zhuǎn)換。同時(shí)我們也可以使用這個(gè)類俊马,去創(chuàng)建包裝了Native自定義類的JS對(duì)象丁存,或者是那些由Native方法或者Block提供實(shí)現(xiàn)JS方法的JS對(duì)象。

其實(shí)我們從上面的JSContext解釋里面能看到柴我,每個(gè)JSValue都存在于一個(gè)JSContext之中解寝,也就是說(shuō)這個(gè)context就是JSValue的作用域。JSCore幫我們用JSValue在底層自動(dòng)做了一個(gè)OC轉(zhuǎn)JS的類型轉(zhuǎn)換之后艘儒,我們就可以通過(guò)JSValue拿到JS執(zhí)行結(jié)果的返回值聋伦。

JSCore提供了10種類型轉(zhuǎn)換
Objective-C type JavaScript type
nil undefined
NSNull null
NSString string
NSNumber number,boolean
NSDictionary Object object
NSArray Array object
NSDate Date object
NSBlock Funtion object
id Wrapper object
Class Constructor object

同時(shí)還提供了對(duì)應(yīng)的互換API:

+ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context;
+ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context;
- (NSArray *)toArray;
- (NSDictionary *)toDictionary;
NSDictionary <-> Object

上面我們說(shuō)到JSContext的globalObject可以轉(zhuǎn)換成OC對(duì)象夫偶,然后轉(zhuǎn)成的OC對(duì)象是一個(gè)NSDictionary類型。其實(shí)嘉抓,在JS中索守,對(duì)象就是一個(gè)引用類型的實(shí)例晕窑,因?yàn)镴S中并不存在類的概念(ECMA把對(duì)象定義為:無(wú)序?qū)傩缘募弦制鋵傩钥梢园局怠?duì)象或者函數(shù))杨赤。于是我們可以發(fā)現(xiàn)JS中的對(duì)象就是無(wú)序的鍵值對(duì)敞斋,這就跟NSDictionary相差無(wú)幾了。

NSBlock <-> Funtion Object

在前面我們說(shuō)到疾牲,在JSContext賦值了一個(gè)”globalFunc”的Block植捎,并可以在JS代碼中當(dāng)成一個(gè)函數(shù)直接調(diào)用。我還可以使用”typeof”關(guān)鍵字來(lái)判斷globalFunc在JS中的類型:

    NSString *type = [[context evaluateScript:@"typeof globalFunc"] toString];//type的值為"function"

通過(guò)這個(gè)例子阳柔,我們也能發(fā)現(xiàn)傳入的Block對(duì)象在JS中已經(jīng)被轉(zhuǎn)成了”function”類型焰枢。”Function Object”這個(gè)概念對(duì)于我們寫(xiě)慣傳統(tǒng)面向?qū)ο笳Z(yǔ)言的開(kāi)發(fā)者來(lái)說(shuō)舌剂,可能會(huì)比較晦澀济锄。而實(shí)際上,JS這門(mén)語(yǔ)言霍转,除了基本類型以外荐绝,就是引用類型。函數(shù)實(shí)際上也是一個(gè)”Function”類型的對(duì)象避消,每個(gè)函數(shù)名實(shí)則是指向一個(gè)函數(shù)對(duì)象的引用低滩。比如我們可以這樣在JS中定義一個(gè)函數(shù):

var sum = function(num1,num2){
    return num1 + num2; 
}

同時(shí)我們還可以這樣定義一個(gè)函數(shù)(不推薦):

    var sum = new Function("num1","num2","return num1 + num2");

按照第二種寫(xiě)法,我們就能很直觀的理解到函數(shù)也是對(duì)象岩喷,它的構(gòu)造函數(shù)就是Function恕沫,函數(shù)名只是指向這個(gè)對(duì)象的指針。而NSBlock是一個(gè)包裹了函數(shù)指針的類纱意,JSCore把Function Object轉(zhuǎn)成NSBlock對(duì)象婶溯,可以說(shuō)是很合適的。

JSVirtualMachine

一個(gè)JSVirtualMachine(以下簡(jiǎn)稱JSVM)實(shí)例代表了一個(gè)自包含的JS運(yùn)行環(huán)境妇穴,或者是一系列JS運(yùn)行所需的資源爬虱。該類有兩個(gè)主要的使用用途:一是支持并發(fā)的JS調(diào)用,二是管理JS和Native之間橋?qū)ο蟮膬?nèi)存腾它。

JSVM是我們要學(xué)習(xí)的第一個(gè)概念跑筝。官方介紹JSVM為JavaScript的執(zhí)行提供底層資源,而從類名直譯過(guò)來(lái)瞒滴,一個(gè)JSVM就代表一個(gè)JS虛擬機(jī)曲梗,我們?cè)谏厦嬉蔡岬搅颂摂M機(jī)的概念赞警,那我們先討論一下什么是虛擬機(jī)。首先我們可以看看(可能是)最出名的虛擬機(jī)——JVM(Java虛擬機(jī))虏两。 JVM主要做兩個(gè)事情:

1愧旦、首先它要做的是把JavaC編譯器生成的ByteCode(ByteCode其實(shí)就是JVM的虛擬機(jī)器指令)生成每臺(tái)機(jī)器所需要的機(jī)器指令,讓Java程序可執(zhí)行(如下圖)定罢。
2笤虫、第二步,JVM負(fù)責(zé)整個(gè)Java程序運(yùn)行時(shí)所需要的內(nèi)存空間管理祖凫、GC以及Java程序與Native(即C,C++)之間的接口等等琼蚯。

從功能上來(lái)看,一個(gè)高級(jí)語(yǔ)言虛擬機(jī)主要分為兩部分惠况,一個(gè)是解釋器部分遭庶,用來(lái)運(yùn)行高級(jí)語(yǔ)言編譯生成的ByteCode,還有一部分則是Runtime運(yùn)行時(shí)稠屠,用來(lái)負(fù)責(zé)運(yùn)行時(shí)的內(nèi)存空間開(kāi)辟峦睡、管理等等。實(shí)際上权埠,JSCore常常被認(rèn)為是一個(gè)JS語(yǔ)言的優(yōu)化虛擬機(jī)榨了,它做著JVM類似的事情,只是相比靜態(tài)編譯的Java弊知,它還多承擔(dān)了把JS源代碼編譯成字節(jié)碼的工作阻逮。

既然JSCore被認(rèn)為是一個(gè)虛擬機(jī),那JSVM又是什么秩彤?實(shí)際上叔扼,JSVM就是一個(gè)抽象的JS虛擬機(jī),讓開(kāi)發(fā)者可以直接操作漫雷。在App中瓜富,我們可以運(yùn)行多個(gè)JSVM來(lái)執(zhí)行不同的任務(wù)。而且每一個(gè)JSContext(下節(jié)介紹)都從屬于一個(gè)JSVM降盹。但是需要注意的是每個(gè)JSVM都有自己獨(dú)立的堆空間与柑,GC也只能處理JSVM內(nèi)部的對(duì)象(在下節(jié)會(huì)簡(jiǎn)單講解JS的GC機(jī)制)。所以說(shuō)蓄坏,不同的JSVM之間是無(wú)法傳遞值的价捧。

JSExport

實(shí)現(xiàn)JSExport協(xié)議可以開(kāi)放OC類和它們的實(shí)例方法,類方法涡戳,以及屬性給JS調(diào)用结蟋。

如果我們想在JS環(huán)境中使用OC中的類和對(duì)象,就需要他們實(shí)現(xiàn)JSExport協(xié)力來(lái)確定暴露給JS環(huán)境中的屬性和方法渔彰。

@protocol PersonProtocol <JSExport>
- (NSString *)stuFullInfo;//stuFullInfo用來(lái)拼接stuName和stuID嵌屎,并返回學(xué)生的全部信息
@end

@interface JSStudent : NSObject <PersonProtocol>
  
- (NSString *)sayStuFullInfo;//sayStuFullInfo方法

@property (nonatomic, copy) NSString *stuName;
@property (nonatomic, copy) NSString *stuID;

@end

然后我們把JSStudent的一個(gè)實(shí)例傳入JSContext推正,并且可以直接執(zhí)行stuFullInfo方法:

    JSStudent *student = [[JSStudent alloc] init];
    context[@"student"] = student;
    student.stuName = @"LiHeng Xue";
    student.stuID =@"ID0018888";
    [context evaluateScript:@"log(student.stuFullInfoe())"];//調(diào)Native方法,打印出student實(shí)例的學(xué)生的全部信息
    [context evaluateScript:@"student.sayStuFullInfo())"];//提示TypeError宝惰,'student.sayStuFullInfo' is undefined

在這里我們就能看得出來(lái)了植榕,只有在JSExport里面開(kāi)放出去的方法才能夠使用,如果沒(méi)有開(kāi)放出去尼夺,如上面的sayStuFullInfo方法尊残,直接調(diào)用的時(shí)候是會(huì)報(bào)類型錯(cuò)誤的。

總結(jié)一下JSCore

jscore其實(shí)就是給APP提供了一個(gè)js可以解釋執(zhí)行的運(yùn)行環(huán)境與資源汞斧。我們主要使用的是JSContext和JSValue這兩個(gè)類夜郁。JSContext提供互相調(diào)用的接口什燕,JSValue為這個(gè)互相調(diào)用提供數(shù)據(jù)類型的橋接轉(zhuǎn)換粘勒。讓JS可以執(zhí)行Native方法,并讓Native回調(diào)JS屎即,反之亦然庙睡。


JSCore怎么實(shí)現(xiàn)橋方法

ok,在這里我們看完了JSCore的一些基本原理技俐,那么我們就要再來(lái)看看JSCore是怎么實(shí)現(xiàn)橋方法的呢乘陪?

市面上常見(jiàn)的橋方法調(diào)用有兩種:

  • 通過(guò)UIWebView的delegate方法:shouldStartLoadWithRequest來(lái)處理橋接JS請(qǐng)求。JSRequest會(huì)帶上methodName雕擂,通過(guò)WebViewBridge類調(diào)用該method啡邑。執(zhí)行完之后,會(huì)使用WebView來(lái)執(zhí)行JS的回調(diào)方法井赌,當(dāng)然實(shí)際上也是調(diào)用的WebView中的JSContext來(lái)執(zhí)行JS谤逼,完成整個(gè)調(diào)用回調(diào)流程。
  • 通過(guò)UIWebView的delegate方法:在webViewDidFinishLoadwebViewDidFinishLoad里通過(guò)KVC的方式獲取UIWebView的JSContext仇穗,然后通過(guò)這個(gè)JSContext設(shè)置已經(jīng)準(zhǔn)備好的橋方法供JS環(huán)境調(diào)用流部。

這里面使用的最廣泛的就是一個(gè)開(kāi)源庫(kù):WebViewJavaScriptBridge

WebViewJavaScript的解讀纹坐,請(qǐng)看我的下一篇帖子(下一篇帖子還沒(méi)有寫(xiě)完??)枝冀。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市耘子,隨后出現(xiàn)的幾起案子果漾,更是在濱河造成了極大的恐慌,老刑警劉巖谷誓,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绒障,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡片林,警方通過(guò)查閱死者的電腦和手機(jī)端盆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)怀骤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人焕妙,你說(shuō)我怎么就攤上這事蒋伦。” “怎么了焚鹊?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵痕届,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我末患,道長(zhǎng)研叫,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任璧针,我火速辦了婚禮嚷炉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘探橱。我一直安慰自己申屹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布隧膏。 她就那樣靜靜地躺著哗讥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胞枕。 梳的紋絲不亂的頭發(fā)上杆煞,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音腐泻,去河邊找鬼决乎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛贫悄,可吹牛的內(nèi)容都是我干的瑞驱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼窄坦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼唤反!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起鸭津,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤彤侍,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后逆趋,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體盏阶,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年闻书,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了名斟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脑慧。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖砰盐,靈堂內(nèi)的尸體忽然破棺而出闷袒,到底是詐尸還是另有隱情,我是刑警寧澤岩梳,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布囊骤,位于F島的核電站,受9級(jí)特大地震影響冀值,放射性物質(zhì)發(fā)生泄漏也物。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一列疗、第九天 我趴在偏房一處隱蔽的房頂上張望滑蚯。 院中可真熱鬧,春花似錦作彤、人聲如沸膘魄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至浙踢,卻和暖如春绢慢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洛波。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工胰舆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蹬挤。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓缚窿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親焰扳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子倦零,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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

  • 注:本文copy自http://www.reibang.com/p/ac534f508fb0,純屬當(dāng)筆記使用吨悍。 概...
    BookKeeping閱讀 731評(píng)論 1 3
  • 一、JavaScript 1. JavaScript干啥的? 2. JavaScript起源與歷史 3. Java...
    蠻大人_3b6c閱讀 356評(píng)論 0 0
  • 一. JavaScriptCore 簡(jiǎn)介 1.1 JavaScriptCore 和 JavaScriptCore ...
    GShining閱讀 857評(píng)論 0 0
  • 今天询微,看電視株依,愛(ài)情進(jìn)化論。 說(shuō)實(shí)話躏仇,剛開(kāi)始恋脚,女二的出現(xiàn)腺办,讓我很討厭這個(gè)人,這個(gè)女生糟描,因?yàn)閯e人說(shuō)的話菇晃,她聽(tīng)不懂,就像...
    一只娜離子閱讀 185評(píng)論 0 0
  • 早安蚓挤,2019-03-28 7時(shí)00分磺送。 天氣陰,空氣良灿意。 昨晚到機(jī)場(chǎng)接人估灿,折騰回來(lái)已是凌晨1點(diǎn)多了。早晨起床就稍...
    平凡生命閱讀 226評(píng)論 2 3