復(fù)制大神的runtime文章

iOS開發(fā)-- Runtime的幾個小例子

字數(shù)2756閱讀1867評論22喜歡88

一、什么是runtime(也就是所謂的“運行時”,因為是在運行時實現(xiàn)的。)

1.runtime是一套底層的c語言API(包括很多強大實用的c語言類型,c語言函數(shù));? [runtime運行系統(tǒng)]

2.實際上,平時我們編寫的oc代碼,底層都是基于runtime實現(xiàn)的;? ? ? ? ? ? ? ? ? ? ? ? ? ? [OC語言的動態(tài)性]

運行時系統(tǒng) (runtime system),對于C語言,函數(shù)的調(diào)用在編譯的時候會決定調(diào)用哪個函數(shù)。對于OC的函數(shù),屬于動態(tài)調(diào)用過程,在編譯的時候并不能決定真正調(diào)用哪個函數(shù),只有在真正運行的時候才會根據(jù)函數(shù)的名稱找到對應(yīng)的函數(shù)來調(diào)用。runtime就是OC辛苦的幕后工作人員。(編譯器會自動幫助我們編譯成runtime代碼。

動態(tài)特性:使得它在語言層面上支持程序的可擴展性。只有在程序運行時,才會去確定對象的類型,并調(diào)用類與對象相應(yīng)的方法。利用runtime機制讓我們可以在程序運行時動態(tài)修改類的具體實現(xiàn)、包括類中的所有私有屬性、方法。這也是本文runtime例子的出發(fā)點。

我們所敲入的代碼轉(zhuǎn)化為運行時的runtime函數(shù)代碼,最終在程序運行時轉(zhuǎn)成了底層的runtime的c語言代碼;

舉例

當某個對象使用語法[receiver message]來調(diào)用某個方法時,其實[receiver message]被編譯器轉(zhuǎn)化為:

idobjc_msgSend (idself, SEL op, ... );

也就是說,我們平時編寫的oc代碼,方法調(diào)用的本質(zhì),就是在編譯階段,編譯器轉(zhuǎn)化為向?qū)ο蟀l(fā)送消息。

【本次開發(fā)環(huán)境: Xcode:7.2? ? iOS Simulator:iphone6? ? By: 啊左

本文Demo下載鏈接:runtime-Demo

二、runtime的幾種使用方法

我們通過繼承于NSObject的person類,來對runtime進行學習。

本文共有6個關(guān)于runtime機制方法的小例子,分別是:

1.獲取person類的所有變量;

2.獲取person類的所有方法;

3.改變person類的私有變量name的值;

4.為person的category類增加一個新屬性;

5.為person類添加一個方法;

6.交換person類的2個方法的功能;

(個人習慣,喜歡為6個例子添加按鈕各自的行為方法,并分別執(zhí)行相應(yīng)的行為,以此看清各個runtime函數(shù)的具體功能所帶來的效果。)

首先,創(chuàng)建新的項目,并在項目中新建一個普通的OC類:person類(繼承于NSObject),為了避免后面與其他方法函數(shù)搞混,我們把完整的person類編寫齊全,用于后面使用runtime的幾種方法:

person.h如下:

#import@interfaceperson:NSObject@property(nonatomic,assign)intage;//屬性變量-(void)func1;-(void)func2;@end

person.m如下:

#import"person.h"@implementationperson{NSString*name;//實例變量}//初始化person屬性-(instancetype)init{self= [superinit];if(self) {? name =@"Tom";self.age=12; }returnself;}//person的2個普通方法-(void)func1{NSLog(@"執(zhí)行func1方法。");}-(void)func2{NSLog(@"執(zhí)行func2方法。");}//輸出person對象時的方法:-(NSString*)description{return[NSStringstringWithFormat:@"name:%@ age:%d",name,self.age];}@end

從person類的描述中,我們可以看到person類含有一個可供外類使用的共有屬性age,以及一個外界不可以訪問私有屬性name,但是,有木有想過,其實在外類,name也是可以訪問的。OC里面,通過runtime系統(tǒng),蘋果允許不受這些私有屬性的限制,對私有屬性私有方法等進行訪問、添加、修改、甚至替換系統(tǒng)的方法。

那么,為項目的故事板添加6個按鈕;

在使用runtime的地方,我們都需要包含頭文件:

#import//(在需要使用runtime的實現(xiàn)文件.m中包含即可.)

1.獲取person類的所有變量

將第一個按鈕關(guān)聯(lián)到ViewController.h,添加行為并命名其方法為:“getAllVariable”:

- (IBAction)getAllVariable:(UIButton*)sender;//獲取所有變量

在ViewController.m中的實現(xiàn)如下:

/*1.獲取person所有的成員變量*/- (IBAction)getAllVariable:(UIButton*)sender {unsignedintcount =0;//獲取類的一個包含所有變量的列表,IVar是runtime聲明的一個宏,是實例變量的意思.Ivar *allVariables = class_copyIvarList([person class], &count);for(inti =0;i

點擊按鈕后,得到的輸出如下:(i表示類型為int)

2016-05-1817:17:10.502runtime運行時[10164:452725] (Name: name) ----- (Type:@"NSString")2016-05-1817:17:10.503runtime運行時[10164:452725] (Name: _age) ----- (Type:i)

分析Ivar,一個指向objc_ivar結(jié)構(gòu)體指針,包含了變量名、變量類型等信息。

可以看到,私有屬性name能夠訪問到了。 在有些項目中,為了對某些私有屬性進行隱藏,某些.h文件中沒有出現(xiàn)相應(yīng)的顯式創(chuàng)建,而是如上面的person類中,在.m中進行私有創(chuàng)建,但是我們可以通過runtime這個有效的方法,訪問到所有包括這些隱藏的私有變量。

拓展

class_copyIvarList能夠獲取一個含有類中所有成員變量的列表,列表中包括屬性變量和實例變量。需要注意的是,如果如本例中,age返回的是"_age",但是如果在person.m中加入:@synthesize age;

那么控制臺第二行返回的是"(Name: age) ----- (Type:i) ;"

(因為@property是生成了"_age",而@synthesize是執(zhí)行了"@synthesize age = _age;",關(guān)于OC屬性變量與實例變量的區(qū)別、@property、@synthesize的作用等具體的知識,有興趣的童鞋可以自行了解。)

如果單單需要獲取屬性列表的話,可以使用函數(shù):class_copyPropertyList();只是返回的屬性變量僅僅是“age”,做為實例變量的name是不被獲取的。

class_copyIvarList()函數(shù)則能夠返回實例變量和屬性變量的所有成員變量。

2.獲取person類的所有方法

將第二個按鈕關(guān)聯(lián)到ViewController.h,添加行為并命名其方法為:“getAllMethod”:

- (IBAction)getAllMethod:(UIButton*)sender;//獲取所有方法

在ViewController.m中的實現(xiàn)如下:

/*2.獲取person所有方法*/- (IBAction)getAllMethod:(UIButton*)sender {unsignedintcount;//獲取方法列表,所有在.m文件顯式實現(xiàn)的方法都會被找到,包括setter+getter方法;Method *allMethods = class_copyMethodList([person class], &count);for(inti =0;i

點擊按鈕后,控制臺輸出:

2016-05-1917:05:19.880runtime運行時[14054:678124] (Method:func1)2016-05-1917:05:19.881runtime運行時[14054:678124] (Method:func2)2016-05-1917:05:19.881runtime運行時[14054:678124] (Method:setAge:)2016-05-1917:05:19.881runtime運行時[14054:678124] (Method:age)2016-05-1917:05:19.881runtime運行時[14054:678124] (Method:.cxx_destruct)2016-05-1917:05:19.882runtime運行時[14054:678124] (Method:description)2016-05-1917:05:19.882runtime運行時[14054:678124] (Method:init)

控制臺輸出了包括setget等方法名稱。【備注:.cxx_destruct方法是關(guān)于系統(tǒng)自動內(nèi)存釋放工作的一個隱藏的函數(shù),當ARC下,且本類擁有實例變量時,才會出現(xiàn);】

分析Method是一個指向objc_method結(jié)構(gòu)體指針,表示對類中的某個方法的描述。在API中的定義:typedef struct objc_method Method;

而objc_method結(jié)構(gòu)體如下:

truct objc_method { SEL method_name OBJC2_UNAVAILABLE;char*method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE;}

method_name :方法選擇器@selector(),類型為SEL。 相同名字的方法下,即使在不同類中定義,它們的方法選擇器也相同。

method_types:方法類型,是個char指針,存儲著方法的參數(shù)類型和返回值類型。

method_imp:指向方法的具體實現(xiàn)的指針,數(shù)據(jù)類型為IMP,本質(zhì)上是一個函數(shù)指針。 在第五個按鈕行為“增加一個方法”部分會提到。

SEL:數(shù)據(jù)類型,表示方法選擇器,可以理解為對方法的一種包裝。在每個方法都有一個與之對應(yīng)的SEL類型的數(shù)據(jù),根據(jù)一個SEL數(shù)據(jù)“@selector(方法名)”就可以找到對應(yīng)的方法地址,進而調(diào)用方法。

因此可以通過:獲取Method結(jié)構(gòu)體->得到SEL選擇器名稱->得到對應(yīng)的方法名,這樣的方式,便于認識OC中關(guān)于方法的定義。

3.改變person類的私有變量name的值.

將第三個按鈕關(guān)聯(lián)到ViewController.h,添加行為并命名其方法為:“changeVariable”:

- (IBAction)changeVariable:(UIButton*)sender;//改變其中name變量

在ViewController.m中創(chuàng)建一個person對象,記得初始化

@implementationViewController{? person *per;//創(chuàng)建一個person實例}- (void)viewDidLoad {? [superviewDidLoad];? per = [[person alloc]init];//記得要初始化...不然后果自己嘗試下}

在ViewController.m中的實現(xiàn)如下:

/*3.改變person的name變量屬性*/- (IBAction)changeVariable:(UIButton*)sender {NSLog(@"改變前的person:%@",per);unsignedintcount =0;Ivar *allList = class_copyIvarList([person class], &count); Ivar ivv = allList[0];//從第一個方法getAllVariable中輸出的控制臺信息,我們可以看到name為第一個實例屬性。object_setIvar(per, ivv,@"Mike");//name屬性Tom被強制改為Mike。NSLog(@"改變之后的person:%@",per);}

點擊按鈕后,控制臺輸出:

2016-05-1922:45:05.125runtime運行時[1957:34730] 改變前的person:name:Tom age:122016-05-1922:45:05.126runtime運行時[1957:34730] 改變之后的person:name:Mike age:12

4.為person的category類增加一個新屬性:

如何在不改動某個類的前提下,添加一個新的屬性呢?

答:可以利用runtime為分類添加新屬性。

在iOS中,category,也就是分類,是不可以為本類添加新的屬性的,但是在runtime中我們可以使用對象關(guān)聯(lián),為person類進行分類的新屬性創(chuàng)建:

①新建一個新的OC類:

命名為:PersonCategory,點擊next

在出現(xiàn)的新類“person+PersonCategory.h”中,添加“height”:

#import"person.h"@interfaceperson(PersonCategory)@property(nonatomic,assign)floatheight;//新屬性@end

person+PersonCategory.m”類的代碼如下:

#import"person+PersonCategory.h"#import//runtime API的使用需要包含此頭文件constchar* str ="myKey";//做為key,字符常量 必須是C語言字符串;@implementationperson(PersonCategory)-(void)setHeight:(float)height{NSNumber*num = [NSNumbernumberWithFloat:height];/*

第一個參數(shù)是需要添加屬性的對象;

第二個參數(shù)是屬性的key;

第三個參數(shù)是屬性的值,類型必須為id,所以此處height先轉(zhuǎn)為NSNumber類型;

第四個參數(shù)是使用策略,是一個枚舉值,類似@property屬性創(chuàng)建時設(shè)置的關(guān)鍵字,可從命名看出各枚舉的意義;

*/objc_setAssociatedObject(idobject,constvoid*key,idvalue, objc_AssociationPolicy policy);}//提取屬性的值:-(float)height{NSNumber*number = objc_getAssociatedObject(self, str);return[number floatValue];}@end

接下來,我們可以在ViewController.m中對person的一個對象進行height的訪問了,

將第四個按鈕關(guān)聯(lián)到ViewController.h添加行為并命名其方法為:“addVariable:”(記得:#import "person+PersonCategory.h"

-(IBAction)addVariable:(UIButton *)sender;

在ViewController.m中的實現(xiàn)如下:

/* 4.添加新的屬性*/- (IBAction)addVariable:(UIButton*)sender { per.height=12;//給新屬性height賦值NSLog(@"%f",[per height]);//訪問新屬性值}

點擊按鈕、再點擊按鈕、獲取類的屬性、方法。

2016-05-2015:39:54.432runtime運行時[4605:178974]12.0000002016-05-2015:39:56.295runtime運行時[4605:178974] (Name: name) ----- (Type:@"NSString")2016-05-2015:39:56.296runtime運行時[4605:178974] (Name: _age) ----- (Type:i)2016-05-2015:39:57.195runtime運行時[4605:178974] (Method:func1)2016-05-2015:39:57.196runtime運行時[4605:178974] (Method:func2)2016-05-2015:39:57.196runtime運行時[4605:178974] (Method:setAge:)2016-05-2015:39:57.196runtime運行時[4605:178974] (Method:age)2016-05-2015:39:57.196runtime運行時[4605:178974] (Method:.cxx_destruct)2016-05-2015:39:57.197runtime運行時[4605:178974] (Method:description)2016-05-2015:39:57.197runtime運行時[4605:178974] (Method:init)2016-05-2015:39:57.197runtime運行時[4605:178974] (Method:height)2016-05-2015:39:57.197runtime運行時[4605:178974] (Method:setHeight:)

分析:可以看到分類的新屬性可以在per對象中對新屬性height進行訪問賦值。

獲取到person類屬性時,依然沒有height的存在,但是卻有height和setHeight這兩個方法;因為在分類中,即使使用@property定義了,也只是生成set+get方法,而不會生成_變量名,分類中是不允許定義變量的。

使用runtime中objc_setAssociatedObject()objc_getAssociatedObject()方法,本質(zhì)上只是為對象per添加了對height的屬性關(guān)聯(lián),但是達到了新屬性的作用;

使用場景:假設(shè)imageCategory是UIImage類的分類,在實際開發(fā)中,我們使用UIImage下載圖片或者操作過程需要增加一個URL保存一段地址,以備后期使用。這時可以嘗試在分類中動態(tài)添加新屬性MyURL進行存儲。

5.為person類添加一個新方法;

將第五個按鈕關(guān)聯(lián)到ViewController.h,添加行為并命名其方法為:“addMethod”:

-(IBAction)addMethod:(UIButton *)sender;

在ViewController.m中的實現(xiàn)如下:

/*5.添加新的方法試試(這種方法等價于對Father類添加Category對方法進行擴展):*/- (IBAction)addMethod:(UIButton*)sender {/* 動態(tài)添加方法:

第一個參數(shù)表示Class cls 類型;

第二個參數(shù)表示待調(diào)用的方法名稱;

第三個參數(shù)(IMP)myAddingFunction,IMP一個函數(shù)指針,這里表示指定具體實現(xiàn)方法myAddingFunction;

第四個參數(shù)表方法的參數(shù),0代表沒有參數(shù);

*/class_addMethod([per class],@selector(NewMethod), (IMP)myAddingFunction,0);//調(diào)用方法 【如果使用[per NewMethod]調(diào)用方法,在ARC下會報“no visible @interface"錯誤】[per performSelector:@selector(NewMethod)];}//具體的實現(xiàn)(方法的內(nèi)部都默認包含兩個參數(shù)Class類和SEL方法,被稱為隱式參數(shù)。)intmyAddingFunction(idself, SEL _cmd){NSLog(@"已新增方法:NewMethod");return1;}

點擊按鈕后,控制臺輸出:

2016-05-2014:08:55.822runtime運行時[1957:34730] 已新增方法:NewMethod

6.交換person類的2個方法的功能:

將第六個按鈕關(guān)聯(lián)到ViewController.h,添加行為并命名其方法為:“replaceMethod”:

-(IBAction)replaceMethod:(UIButton *)sender;

在ViewController.m中的實現(xiàn)如下:

/* 6.交換兩種方法之后(功能對調(diào)),可以試試讓蘋果亂套... */- (IBAction)replaceMethod:(UIButton*)sender { Method method1 = class_getInstanceMethod([person class],@selector(func1)); Method method2 = class_getInstanceMethod([person class],@selector(func2));//交換方法method_exchangeImplementations(method1, method2); [per func1];//輸出交換后的效果,需要對比的可以嘗試下交換前運行func1;}

點擊按鈕后,控制臺輸出:

2016-05-2014:11:57.381runtime運行時[1957:34730] 執(zhí)行func2方法。

交換方法的使用場景:項目中的某個功能,在項目中需要多次被引用,當項目的需求發(fā)生改變時,要使用另一種功能代替這個功能,且要求不改變舊的項目(也就是不改變原來方法實現(xiàn)的前提下)。那么,我們可以在分類中,再寫一個新的方法(符合新的需求的方法),然后交換兩個方法的實現(xiàn)。這樣,在不改變項目的代碼,而只是增加了新的代碼的情況下,就完成了項目的改進,很好地體現(xiàn)了該項目的封裝性與利用率。

:交換兩個方法的實現(xiàn)一般寫在類的load方法里面,因為load方法會在程序運行前加載一次。

(轉(zhuǎn)載請標明原文出處,謝謝支持 ~ ^-^ ~)

? by:啊左~

推薦拓展閱讀

舉報文章

著作權(quán)歸作者所有

如果覺得我的文章對您有用,請隨意打賞。您的支持將鼓勵我繼續(xù)創(chuàng)作!

¥ 打賞支持

喜歡

88

分享到微博分享到微信

更多分享

喜歡的用戶

鑫胖2016.11.03 10:42

congdufs2016.11.02 17:53

是刺猬2016.11.01 05:34

CarlXu2016.10.29 00:21

竹內(nèi)溪風2016.10.27 09:40

sq_882016.10.13 18:04

康龍歸海2016.10.08 10:37

上發(fā)條的樹2016.09.20 16:54

花花你個花花呦2016.08.21 22:40

Peter_時2016.08.17 23:25

沒駱駝de祥子2016.08.16 17:59

常義2016.08.14 22:23

SunshineAU2016.08.12 19:50

iOS_Job2016.08.09 18:47

于_先笙2016.08.08 19:33

22條評論(按時間正序·按時間倒序·按喜歡排序添加新評論

Prospero

2 樓 ·2016.05.24 09:52

不錯,好好學習一下

喜歡(0)回復(fù)

華之曦

3 樓 ·2016.05.24 18:51

mark

喜歡(0)回復(fù)

簡單也好

4 樓 ·2016.05.26 22:47

說的明白,還有例子,??

喜歡(0)回復(fù)

簡單也好

5 樓 ·2016.05.28 21:47

寫了一下,在添加新方法那里,

class_addMethod([_per class], @selector(sayHi), (IMP)myAddingFunction, 0);

selector()里面的方法名好像沒什么用?,只會執(zhí)行IMP類型的C語言函數(shù)?

喜歡(0)回復(fù)

啊左@簡單也好第二個參數(shù)是讓對象調(diào)用的方法名稱。

[per performSelector:@selector(NewMethod)];

回復(fù)2016.05.30 14:22

簡單也好@啊左[per performSelector:@selector(NewMethod)]; 卻沒有執(zhí)行NewMethod方法,執(zhí)行的是(IMP)myAddingFunction里面的內(nèi)容

回復(fù)2016.05.30 16:20

啊左@簡單也好NewMetho只是一個方法名,可隨意命名,主要是給對象用來調(diào)用的。但是實際上具體方法實現(xiàn)是在(IMP)函數(shù)指針指向的內(nèi)容里面,那是“實現(xiàn)這個方法的函數(shù)”。

雖然一般都說class_addMethod是添加方法,但個人覺得說是添加函數(shù)更貼切些。

回復(fù)2016.05.30 17:02

還有2條評論,展開查看添加新回復(fù)

夜3033

6 樓 ·2016.06.28 15:58

寫的很好,重點是有代碼能自己試一下,學習了。

喜歡(0)回復(fù)

0ef0376dc7d1

7 樓 ·2016.07.04 16:51

樓主,額能請教你些問題嗎?

喜歡(0)回復(fù)

啊左@0ef0376dc7d1你可以直接問呀。

回復(fù)2016.07.04 20:16

添加新回復(fù)

butterflyer

8 樓 ·2016.08.08 16:54

給樓主點個贊。

喜歡(1)回復(fù)

啊左@butterflyer木有看到你的贊哦~

回復(fù)2016.08.10 22:04

添加新回復(fù)

同樣的錯誤不能錯兩次

9 樓 ·2016.08.16 14:41

??

喜歡(0)回復(fù)

酷愛西西的偽球迷

10 樓 ·2016.08.24 10:04

這里獲取所有對象的所有方法得到的是其所有的實例方法

喜歡(0)回復(fù)

鼻毛長長

11 樓 ·2016.10.10 11:12

什么是編譯,什么是運行?

喜歡(0)回復(fù)

啊左@鼻毛長長

編譯:編譯器幫你把源代碼翻譯成機器能識別的代碼,或者說識別語法等代碼錯誤并產(chǎn)生能夠識別的指令以便讓運行時能夠進行相應(yīng)的內(nèi)存分配。

運行時:代碼跑(run)起來了,進行內(nèi)存的分配與操作。

以上可能不太準確,純屬個人見解。具體的可以網(wǎng)上查找這兩個的意義與區(qū)別。

回復(fù)2016.10.10 11:41

添加新回復(fù)

阿呆的樂樂園

12 樓 ·2016.10.28 08:06

獲取的實例變量方法中,name沒有下劃線是因為樓主寫的不規(guī)范,實例變量name沒有加下劃線,所以打印出來就沒有,而系統(tǒng)自動生成的age實例變量就有下劃線

喜歡(0)回復(fù)

啊左@阿呆的樂樂園

其實這里一直重點強調(diào)的是,age會因為屬性變量機制中默認的@synthesize使得它自動補全為_age,所以很明顯把name命名為沒有下劃線是為了區(qū)分出來:屬性變量age在@property后會補全下劃線,在這里沒有強調(diào)name的下劃線,是為了避免混淆;

你可以直接說這種命名不規(guī)范,但不是“因為不規(guī)范”所以才有下劃線??,那是因為:在語法正確情況下不管怎么命名實例變量,它都就只輸出原來的名字,即使我加了2個下劃線,它也照樣輸出2個下劃線。

另外,實例變量這種語法屬于比較老派的用法,以前為了防止內(nèi)存管理泄露,與屬性變量調(diào)用訪問方法區(qū)分開來,”_變量名”往往意味著實例變量,包括我公司舊的項目也是實例變量加_、屬性變量加“@synthesize name = _name;”但是在現(xiàn)在ARC后,不存在上述問題,一年多前我身邊的挺多同行也是常常加下劃線,包括后來看《精通iOS開發(fā)》這些書介紹的時候,也是發(fā)現(xiàn)里面的實例變量也沒有加下劃線,所以我想現(xiàn)在更多的已經(jīng)是習慣問題而不是規(guī)范,當然也會有相關(guān)資料建議下劃線做為編碼規(guī)范。這就看開發(fā)者怎么去處理了。

回復(fù)2016.10.28 22:23

阿呆的樂樂園@啊左寫的好多,好詳細

回復(fù)2016.10.28 22:43

啊左@阿呆的樂樂園??,翻了資料共享一下。

回復(fù)2016.10.28 22:46

添加新回復(fù)

?+Return 發(fā)表

被以下專題收入,發(fā)現(xiàn)更多相似內(nèi)容:

程序員

正在關(guān)注

如果你是程序員,或者有一顆喜歡寫程序的心,喜歡分享技術(shù)干貨、項目經(jīng)驗、程序員日常囧事等等,歡迎投稿《程序員》專題。

專題主編:小...

26612篇文章· 209657人關(guān)注

首頁投稿

添加關(guān)注

玩轉(zhuǎn)簡書的第一步,從這個專題開始。

想上首頁熱門榜么?好內(nèi)容想被更多人看到么?來投稿吧!如果被拒也不要灰心哦~入選文章會進一個隊...

113720篇文章· 138152人關(guān)注

iOS Developer

正在關(guān)注

分享 iOS 開發(fā)的知識,解決大家遇到的問題,討論iOS開發(fā)的前沿,歡迎大家投稿~

14867篇文章· 27901人關(guān)注

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容