runtime筆記

OC是一門動態(tài)語言颁独,它將很多靜態(tài)語言在編譯和鏈接時期做的事情推遲到了運行時來處理奖磁,這就意味著它不僅需要一個編譯器渐裂,還需要一個運行時系統(tǒng)來執(zhí)行編譯的代碼(動態(tài)得創(chuàng)建類和對象矮锈、進行消息的傳遞和轉(zhuǎn)發(fā))霉翔。這個運行時系統(tǒng)就是Objc Runtime。Objc Runtime其實是一個Runtime庫苞笨,它基本上是由c語言和匯編編寫的债朵。

RunTime庫主要做下面幾件事:

1.封裝:在這個庫中子眶,對象可以用C語言中的結(jié)構(gòu)體表示,而方法可以用C函數(shù)來實現(xiàn)葱弟,另外再加上了一些額外的特性壹店。這些結(jié)構(gòu)體和函數(shù)被runtime函數(shù)封裝后,我們就可以在程序運行時創(chuàng)建芝加,檢查硅卢,修改類、對象和它們的方法了藏杖。

2.找出方法的最終執(zhí)行代碼:當(dāng)程序執(zhí)行[object doSomething]時将塑,會向消息接收者(object)發(fā)送一條消息(doSomething),runtime會根據(jù)消息接收者是否能響應(yīng)該消息而做出不同的反應(yīng)蝌麸。這將在后面詳細介紹点寥。

Objc從三種不同的層級上與Runtime系統(tǒng)進行交互,分別是通過Objective-C源代碼来吩、通過Foundation框架的NSObject類定義的方法敢辩、通過對runtime的函數(shù)直接調(diào)用。

1)Objective-C源代碼

大部分情況下你就只管寫你的Objc代碼就行弟疆,runtime 系統(tǒng)自動在幕后辛勤勞作著戚长。

還記得引言中舉的例子吧,消息的執(zhí)行會使用到一些編譯器為實現(xiàn)動態(tài)語言特性而創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)和函數(shù)怠苔,Objc中的類同廉、方法和協(xié)議等在 runtime 中都由一些數(shù)據(jù)結(jié)構(gòu)來定義,這些內(nèi)容在后面會講到柑司。(比如objc_msgSend函數(shù)及其參數(shù)列表中的id和SEL都是啥)

2)NSObject的方法

Cocoa 中大多數(shù)類都繼承于NSObject類迫肖,也就自然繼承了它的方法。最特殊的例外是NSProxy攒驰,它是個抽象超類蟆湖,它實現(xiàn)了一些消息轉(zhuǎn)發(fā)有關(guān)的方法,可以通過繼承它來實現(xiàn)一個其他類的替身類或是虛擬出一個不存在的類玻粪,說白了就是領(lǐng)導(dǎo)把自己展現(xiàn)給大家風(fēng)光無限隅津,但是把活兒都交給幕后小弟去干。

有的NSObject中的方法起到了抽象接口的作用奶段,比如description方法需要你重載它并為你定義的類提供描述內(nèi)容。NSObject還有些方法能在運行時獲得類的信息剥纷,并檢查一些特性痹籍,比如class返回對象的類;isKindOfClass:和isMemberOfClass:則檢查對象是否在指定的類繼承體系中晦鞋;respondsToSelector:檢查對象能否響應(yīng)指定的消息蹲缠;conformsToProtocol:檢查對象是否實現(xiàn)了指定協(xié)議類的方法棺克;methodForSelector:則返回指定方法實現(xiàn)的地址。

3)Runtime的函數(shù)

Runtime 系統(tǒng)是一個由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成线定,具有公共接口的動態(tài)共享庫娜谊。頭文件存放于/usr/include/objc目錄下。許多函數(shù)允許你用純C代碼來重復(fù)實現(xiàn) Objc 中同樣的功能斤讥。雖然有一些方法構(gòu)成了NSObject類的基礎(chǔ)纱皆,但是你在寫 Objc 代碼時一般不會直接用到這些函數(shù)的,除非是寫一些 Objc 與其他語言的橋接或是底層的debug工作芭商。在Objective-C Runtime Reference中有對 Runtime 函數(shù)的詳細文檔派草。

[receiver message]會被編譯器轉(zhuǎn)化為:

objc_msgSend(receiver, selector)

如果消息含有參數(shù),則為:

objc_msgSend(receiver, selector, arg1, arg2, ...)

如果消息的接收者能夠找到對應(yīng)的selector铛楣,那么就相當(dāng)于直接執(zhí)行了接收者這個對象的特定方法近迁;否則,消息要么被轉(zhuǎn)發(fā)簸州,或是臨時向接收者動態(tài)添加這個selector對應(yīng)的實現(xiàn)內(nèi)容鉴竭,要么就干脆玩完崩潰掉。

現(xiàn)在可以看出[receiver message]真的不是一個簡簡單單的方法調(diào)用岸浑。因為這只是在編譯階段確定了要向接收者發(fā)送message這條消息搏存,而receive將要如何響應(yīng)這條消息,那就要看運行時發(fā)生的情況來決定了助琐。

Runtime術(shù)語

objc_msgSend:方法的真身是這樣的:

id objc_msgSend (id self, SEL op, ... );

下面將會逐漸展開介紹一些術(shù)語祭埂,其實它們都對應(yīng)著數(shù)據(jù)結(jié)構(gòu)。

SEL

objc_msgSend函數(shù)第二個參數(shù)類型為SEL兵钮,它是selector在Objc中的表示類型(Swift中是Selector類)蛆橡。selector是方法選擇器,可以理解為區(qū)分方法的 ID掘譬,而這個 ID 的數(shù)據(jù)結(jié)構(gòu)是SEL:typedf struct objc_selector *SEL;
其實它就是個映射到方法的C字符串泰演,你可以用 Objc 編譯器命令@selector()或者 Runtime 系統(tǒng)的sel_registerName函數(shù)來獲得一個SEL類型的方法選擇器。

不同類中相同名字的方法所對應(yīng)的方法選擇器是相同的葱轩,即使方法名字相同而變量類型不同也會導(dǎo)致它們具有相同的方法選擇器睦焕,于是 Objc 中方法命名有時會帶上參數(shù)類型(NSNumber一堆抽象工廠方法拿走不謝),Cocoa 中有好多長長的方法哦靴拱。

id

objc_msgSend第一個參數(shù)類型為id垃喊,大家對它都不陌生,它是一個指向類實例的指針:

typedef struct objc_object * id;

那objc_object又是啥呢:

struct objc_object{ Class isa; };

objc_object結(jié)構(gòu)體包含一個isa指針袜炕,根據(jù)isa指針就可以順藤摸瓜找到對象所屬的類本谜。

PS:isa指針不總是指向?qū)嵗龑ο笏鶎俚念悾荒芤揽克鼇泶_定類型偎窘,而是應(yīng)該用class方法來確定實例對象的類乌助。因為KVO的實現(xiàn)機理就是將被觀察對象的isa指針指向一個中間類而不是真實的類溜在,這是一種叫做isa-swizzling的技術(shù),詳見官方文檔

Class

之所以說isa是指針是因為Class其實是一個指向objc_class結(jié)構(gòu)體的指針:

typedef struct objc_class * Class;

而objc_class就是我們摸到的那個瓜他托,里面的東西多著呢:


struct objc_class {

Class isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__

Class super_class OBJC2_UNAVAILABLE;

constchar*name OBJC2_UNAVAILABLE;

long version OBJC2_UNAVAILABLE;

long info OBJC2_UNAVAILABLE;

long instance_size OBJC2_UNAVAILABLE;

structobjc_ivar_list *ivars OBJC2_UNAVAILABLE;

structobjc_method_list **methodLists OBJC2_UNAVAILABLE;

structobjc_cache *cache OBJC2_UNAVAILABLE;

structobjc_protocol_list *protocols OBJC2_UNAVAILABLE;

#endif

} OBJC2_UNAVAILABLE;

可以看到運行時一個類還關(guān)聯(lián)了它的超類指針掖肋,類名,成員變量赏参,方法志笼,緩存,還有附屬的協(xié)議登刺。

PS:OBJC2_UNAVAILABLE之類的宏定義是蘋果在 Objc 中對系統(tǒng)運行版本進行約束的黑魔法籽腕,為的是兼容非Objective-C 2.0的遺留邏輯,但我們?nèi)阅軓闹蝎@得一些有價值的信息纸俭,有興趣的可以查看源代碼皇耗。

Objective-C 2.0 的頭文件雖然沒暴露出objc_class結(jié)構(gòu)體更詳細的設(shè)計,我們依然可以從Objective-C 1.0 的定義中小窺端倪:

在objc_class結(jié)構(gòu)體中:ivars是objc_ivar_list指針揍很;methodLists是指向objc_method_list指針的指針郎楼。也就是說可以動態(tài)修改*methodLists的值來添加成員方法,這也是Category實現(xiàn)的原理窒悔,同樣解釋了Category不能添加屬性的原因呜袁。而最新版的 Runtime 源碼對這一塊的描述已經(jīng)有很大變化,可以參考下美團技術(shù)團隊的深入理解Objective-C:Category简珠。

PS:任性的話可以在Category中添加@dynamic的屬性阶界,并利用運行期動態(tài)提供存取方法或干脆動態(tài)轉(zhuǎn)發(fā);或者干脆使用關(guān)聯(lián)度對象(AssociatedObject)

其中objc_ivar_list和objc_method_list分別是成員變量列表和方法列表:

struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}   
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}      

如果你C語言不是特別好聋庵,可以直接理解為objc_ivar_list結(jié)構(gòu)體存儲著objc_ivar數(shù)組列表膘融,而objc_ivar結(jié)構(gòu)體存儲了類的單個成員變量的信息;同理objc_method_list結(jié)構(gòu)體存儲著objc_method數(shù)組列表祭玉,而objc_method結(jié)構(gòu)體存儲了類的某個方法的信息氧映。

最后要提到的還有一個objc_cache,顧名思義它是緩存脱货,它在objc_class的作用很重要岛都,在后面會講到。

不知道你是否注意到了objc_class中也有一個isa對象振峻,這是因為一個 ObjC 類本身同時也是一個對象臼疫,為了處理類和對象的關(guān)系,runtime 庫創(chuàng)建了一種叫做元類 (Meta Class) 的東西扣孟,類對象所屬類型就叫做元類烫堤,它用來表述類對象本身所具備的元數(shù)據(jù)。類方法就定義于此處,因為這些方法可以理解成類對象的實例方法塔逃。每個類僅有一個類對象,而每個類對象僅有一個與之相關(guān)的元類料仗。當(dāng)你發(fā)出一個類似[NSObject alloc]的消息時湾盗,你事實上是把這個消息發(fā)給了一個類對象 (Class Object) ,這個類對象必須是一個元類的實例立轧,而這個元類同時也是一個根元類 (root meta class) 的實例格粪。所有的元類最終都指向根元類為其超類。所有的元類的方法列表都有能夠響應(yīng)消息的類方法氛改。所以當(dāng)[NSObject alloc]這條消息發(fā)給類對象的時候帐萎,objc_msgSend()會去它的元類里面去查找能夠響應(yīng)消息的方法,如果找到了胜卤,然后對這個類對象執(zhí)行方法調(diào)用疆导。

Method

Method是一種代表類中某個方法的類型。

typedef struct objc_method *Method;

objc_method存儲了方法名葛躏,方法類型和方法實現(xiàn):

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}      
  • 方法名類型是SEL
  • 方法類型method_types 是個char指針澈段,其實存儲著方法的參數(shù)類型和返回值類型
  • method_imp是一個IMP類型的函數(shù)指針:
typedef id (*IMP)(id, SEL, ...); 

它指向了方法的實現(xiàn)。

Ivar

Ivar是代表了類中的實例變量的類型:
typedef struct objc_ivar *Ivar;
objc_ivar的定義如下:

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}   

IMP

IMP的定義是:

typedef id (*IMP)(id, SEL, ...); 

它就是一個指向函數(shù)的指針舰攒,這是由編譯器生成的败富,當(dāng)你發(fā)起一個ObjC消息之后,最終他會執(zhí)行的那段代碼摩窃,就是有這個函數(shù)指針指定的兽叮。

Cache

Cache的定義如下:
typedef struct objc_cache *Cache
objc_cache的定義如下:

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

[obj message]會被編譯成objc_msgSend(obj, message),這個函數(shù)要做的事情是:

  1. 首先,通過obj的isa指針找到他的class猾愿;
  2. 在class里的method list找message鹦聪;
    3.如果class中沒有找到message,繼續(xù)在他的superclass中找匪蟀;
    4.一旦找到message這個函數(shù)椎麦,就去執(zhí)行他的實現(xiàn)IMP
    但這種實現(xiàn)有個問題,效率低材彪,一個class往往只有20%的函數(shù)會被經(jīng)常調(diào)用观挎。每個消息都要便利一次objc_method_list并不合理。如果把經(jīng)常調(diào)用的函數(shù)緩存下來段化,那么可以大大提高函數(shù)查詢的效率嘁捷。這也就是objc_class中一個重要成員objc_cache做的事情。在找到message之后显熏,把message的method_name作為key雄嚣,method_imp作為value給結(jié)合起來。當(dāng)再次受到message消息時,可以直接在cache找缓升,避免遍歷objc_method_list鼓鲁。

Property

typedef struct objc_property *objc_property_t;

 /**獲取Person類中的屬性**/  
   unsigned int outCount;
    //返回了一個包含objc_property_t的數(shù)組,最后必須要釋放這個數(shù)組使用free()
    objc_property_t *properties = class_copyPropertyList([Person class], &outCount);
    for (unsigned int i = 0; i<outCount; i++) {
        objc_property_t property = properties[i];
        //獲取屬性名稱
        const char * propertyName = property_getName(property);
        //返回了C字符串包含了屬性名稱和@encode類型字符串
        const char * propertyAttr = property_getAttributes(property);
        fprintf(stdout, "%s %s\n",propertyName, propertyAttr);
    }
      free(properties);

使用class_copyPropertyList只能獲取類的屬性,而不包含成員變量港谊,但此時獲取的屬性名是不包含下劃線的骇吭。

消息轉(zhuǎn)發(fā)

[obj message]會被編譯成objc_msgSend(obj, message),如果obj的isa指針指向的class及其superclass沒有找到message,通常會拋出unrecognized selector sent to … 的異常歧寺,但在拋出異常前燥狰,runtime會給你三次拯救的機會:
1.method resolution
2.fast forwarding
3.normal forwarding

Method Resolution

NSObject在運行時會調(diào)用+ (BOOL)resolveClassMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel,讓你有機會提供一個函數(shù)式先來添加方法。

void message(id obj, SEL _cmd)
 {
    NSLog(@"Doing thing");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
  if(aSEL == @selector(message)){
  class_addMethod([self class], see, (IMP)message, "v@:");
  return YES; 
  }
  return [super resolveInstanceMethod];
}

Core Data 有用到這個方法斜筐。NSManagedObjects 中 properties 的 getter 和 setter 就是在運行時動態(tài)添加的龙致。
如果 resolve 方法返回 NO ,運行時就會移到下一步:消息轉(zhuǎn)發(fā)(Message Forwarding)顷链。
iOS4.3加入很多新的runtime方法目代,主要是以imp為前綴的方法,比如imp_implementationWithBlock()用block快速創(chuàng)建一個imp嗤练。

Fast forwarding

如果目標對象實現(xiàn)了- (id)forwardingTargetForSelector:(SEL)aSelector像啼,runtime就會調(diào)用這個方法,把消息轉(zhuǎn)發(fā)給其他對象潭苞。

- (id)forwardingTargetForSelector:(SEL)aSelector{
   if(aSelector == @selector(message)){
    return alternateObject;
   } 
 return [super forwardingTargetForSelector:aSelector];
}

只要這個方法返回的不是nil或self忽冻,整個消息發(fā)送的過程就會被重啟,發(fā)送的對象會變成你返回的那個對象此疹,否則僧诚,就會繼續(xù)Normal Forwarding。
這里的fast是為了區(qū)別Normal Forwarding轉(zhuǎn)發(fā)機制蝗碎,因為這一步不會創(chuàng)建任何新的對象湖笨,但下一步轉(zhuǎn)發(fā)會創(chuàng)建一個NSInvocation對象,所以相對更快蹦骑。

Normal forwarding

這是runtime最后一次給你挽救的機會慈省,首先runtime會調(diào)用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法獲得函數(shù)的參數(shù)和返回值類型,如果返回值是nil眠菇,runtime會調(diào)用- (void)doesNotRecognizeSelector:(SEL)aSelector的方法边败,程序就會掛掉;如果返回了一個函數(shù)簽名,runtime就會創(chuàng)建一個NSInvocation對象并發(fā)送- (void)forwardInvocation:(NSInvocation *)anInvocation消息捎废。
NSInvocation實際上就是對一個消息的描述笑窜,包擴了selector以及參數(shù)等信息,所以可以在- (void)forwardInvocation:(NSInvocation *)anInvocation里修改傳進來的NSInvocation對象登疗,然后發(fā)送- (void)invokeWithTarget:(id)target消息給他排截,穿進去一個新的目標嫌蚤。

- (void)forwardInvocation:(NSInvocation *)invocation{
 SEL sel = invocation.selector; 
 if([alternateObject respondsToSelector:sel]) { 
 [invocation invokeWithTarget:alternateObject]; 
 } else {
  [self doesNotRecognizeSelector:sel]; 
 }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市断傲,隨后出現(xiàn)的幾起案子脱吱,更是在濱河造成了極大的恐慌,老刑警劉巖认罩,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件急凰,死亡現(xiàn)場離奇詭異,居然都是意外死亡猜年,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門疾忍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乔外,“玉大人,你說我怎么就攤上這事一罩⊙钣祝” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵聂渊,是天一觀的道長差购。 經(jīng)常有香客問我,道長汉嗽,這世上最難降的妖魔是什么欲逃? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮饼暑,結(jié)果婚禮上稳析,老公的妹妹穿的比我還像新娘。我一直安慰自己弓叛,他們只是感情好彰居,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撰筷,像睡著了一般陈惰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上毕籽,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天抬闯,我揣著相機與錄音,去河邊找鬼关筒。 笑死画髓,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的平委。 我是一名探鬼主播奈虾,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了肉微?” 一聲冷哼從身側(cè)響起匾鸥,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碉纳,沒想到半個月后勿负,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡劳曹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年奴愉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铁孵。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡锭硼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蜕劝,到底是詐尸還是另有隱情檀头,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布岖沛,位于F島的核電站暑始,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏婴削。R本人自食惡果不足惜廊镜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唉俗。 院中可真熱鬧期升,春花似錦、人聲如沸互躬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吼渡。三九已至容为,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寺酪,已是汗流浹背坎背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留寄雀,地道東北人得滤。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像盒犹,于是被迫代替她去往敵國和親懂更。 傳聞我的和親對象是個殘疾皇子眨业,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,682評論 0 9
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言沮协,那么這個「動態(tài)」表現(xiàn)在哪呢龄捡?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,176評論 0 7
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,544評論 33 466
  • 感恩好朋友邀請我們?nèi)タ措娪埃屛覀冃那橛鋹偡潘尚那榭对荨8卸麟娪霸汗ぷ魅藛T為大家清理衛(wèi)生讓我們有個潔凈的環(huán)境享受電影帶...
    念秀閱讀 143評論 0 1
  • 今天沒有閱讀與跑步聘殖。 原計劃是睡醒就去跑步至少一小時目標。 看手機和看視頻又是到四點半行瑞,發(fā)現(xiàn)外面大雨奸腺,就一切外出所...
    卡卡22閱讀 216評論 0 0