編寫高質(zhì)量代碼52個有效方法總結(jié)(上篇)

本篇是我閱讀《Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個有效方法》的摘要與總結(jié)。
一、熟悉Objective-C

1.了解Objective-C語言的起源

Objective-C為C語言添加了面向?qū)ο筇匦?,是其超集。Objective-C使用動態(tài)綁定的消息結(jié)構(gòu),也就是說,在運行時才會檢查對象類型。接收一條消息之后,究竟應(yīng)執(zhí)行何種代碼,由運行環(huán)境而非編譯器來決定。

理解C語言的核心概念有助于寫好Objective-C程序。尤其要掌握內(nèi)存模型與指針。

CoreGraphics 中的CGRect 屬于結(jié)構(gòu)體,如果用OC對象,性能會受影響

2.在類的頭文件中盡量少引入其他頭文件

除非確有必要,否則不要引入頭文件。一般來說,應(yīng)在某個類的頭文件中使用向前聲明(forward declaring)來提及別的類,并在實現(xiàn)文件中引入那些類的頭文件。這樣做可以盡量降低類之間的耦合。

有時無法使用向前聲明,比如要聲明某個類遵循一項協(xié)議。這種情況下,盡量把“該類遵循某協(xié)議”的這條聲明移至“class-continuation”分類中。如果不行的話,就把協(xié)議單獨放在一個頭文件中,然后將其引入。

頭文件中若想導(dǎo)入其他的類,用@class

3.多用字面量語法,少用與之等價的方法

應(yīng)該使用literal語法來創(chuàng)建字符串,數(shù)值,數(shù)組,字典。與創(chuàng)建此類對象的常規(guī)方法相比,這么做更加簡明扼要。

應(yīng)該通過取下標操作來訪問數(shù)組下標或字典中的鍵所對應(yīng)的元素。

用literal語法創(chuàng)建數(shù)組或字典時,若值中有nil,則會拋出異常。因此,務(wù)必確保值里不含nil。

比如多用NSArray *array = @[@1,@2];少用NSArray *array = [NSArray arrayWithObjects:@1,@2,nil];

一句話,就是代碼能寫簡單就寫簡單

4.多用類型常量,少用#define預(yù)處理指令

不要用預(yù)處理指令定義常量。這樣定義出來的常量不含類型信息,編譯器只是會在編譯前據(jù)此執(zhí)行查找與替換操作。即使有人重新定義了常量值,編譯器也不會產(chǎn)生警告信息,這將導(dǎo)致應(yīng)用程序中的常量值不一致。

在實現(xiàn)文件中使用static const來定義只在編譯單元內(nèi)可見的常量。由于此類常量不在全局符號表中,所以無需為其名稱加前綴。

在頭文件中使用extern來聲明全局常量,并在相關(guān)實現(xiàn)文件中定義其值。這種常量要出現(xiàn)在全局符號表中,所以其名稱要加以區(qū)隔,通常用與之相關(guān)的類名做前綴。

用法:將#define EOCAnimatedViewAnimationDuration 0.3 替換為以下

EOCAnimatedView.h

extern const NSTime IntervalEOCAnimatedViewAnimationDuration;

EOCAnimatedView.m

const NSTime IntervalEOCAnimatedViewAnimationDuration = 0.3;

注意命名

5.用枚舉表示狀態(tài)、選項、狀態(tài)碼

應(yīng)該用枚舉來表示狀態(tài)機的狀態(tài)、傳遞給方法的選項遺跡狀態(tài)碼等值,給這些值起個易懂的名字。

如果把傳遞給某個方法的選項表示為枚舉型,而多個選項又可同時使用,那么就將各選項值定義為2的冪,以便通過按位或者操作將其組合起來。

用NS_ENUM與NS_OPTIONS宏來定義枚舉類型,并指明其底層數(shù)據(jù)類型。這樣做可以確保枚舉是用開發(fā)者所選的底層數(shù)據(jù)類型實現(xiàn)出來的,而不會采用編譯器所選的類型。

在處理枚舉類型的switch語句中不要實現(xiàn)default分支。這樣的話,加入新枚舉之后,編譯器就會提示開發(fā)者:switch語句并未處理所有的枚舉。

用法:

typedef NS_Enum(NSUInteger,EOCConnectionState){

EOCConnectionStateDisconnected,

EOCConnectionStateConnecting,

EOCConnectionStateConnected,

}

switch(_currentState){

EOCConnectionStateDisconnected:

//Handle disconnected state

break;

EOCConnectionStateConnecting:

//Handle disconnected state

break;

EOCConnectionStateConnected:

//Handle disconnected state

break;

}

1 << 2 = 0b100

0b1111 >> 3 = 0b0001

二、對象、消息、runtime

6.理解“屬性”這一概念

可以通過@property語法來定義對象中所封裝的數(shù)據(jù)。

通過“特質(zhì)”來指定存儲數(shù)據(jù)所需的正確語義

在設(shè)置屬性所對應(yīng)的實例變量時,一定要遵從該屬性所聲明的語義。

開發(fā)iOS程序時,應(yīng)該使用nonatomic屬性,因為atomic屬性會嚴重影響性能。

7.在對象內(nèi)部盡量直接訪問實例變量

由于不經(jīng)過Objective-C 的“方法派發(fā)”(method dispatch,見11)步驟,所以直接訪問實例變量的速度當然比較快。在這種情況下,編譯器所生成的代碼會直接訪問保存對象實例變量的那塊內(nèi)存。

直接訪問實例變量時,不會調(diào)用其“設(shè)置方法”,這就繞過了為相關(guān)屬性所定義的“內(nèi)存管理語義”。比方說,如果在ARC下直接訪問一個聲明為copy的屬性,那么并不會拷貝該屬性,只會保留新值并釋放舊值。

如果直接訪問實例變量,那么不回觸發(fā)“鍵值觀察(Key-Value Observing, KVO)”通知。這樣做是否會產(chǎn)生問題,還取決于具體的對象行為。

通過屬性來訪問有助于排查與之相關(guān)的錯誤,因為可以給“獲取方法”和/或“設(shè)置方法”中新增“斷點(breakpoint)”監(jiān)測該屬性的調(diào)用者及其訪問時機

在對象內(nèi)部讀取數(shù)據(jù)時,應(yīng)該直接通過實例變量來讀,而寫入數(shù)據(jù)時,應(yīng)該通過屬性來寫。

在初始化方法及dealloc方法中,總是應(yīng)該直接通過實例變量來讀寫數(shù)據(jù)。

有時會使用惰性初始化技術(shù)配置某份數(shù)據(jù),這種情況下,需要通過屬性來讀取數(shù)據(jù)。

8.理解“對象等同性”這一概念

若想檢測對象的等同性,請?zhí)峁癷sEqual:”與hash方法。

相同的對象必須具有相同的hash碼,但是兩個hash碼相同的對象卻未必相同。

不要盲目的逐個監(jiān)測每條屬性,而是應(yīng)該依照具體需求來制定檢測方案。

編寫hash方法時,應(yīng)該使用計算速度快而且哈希碼碰撞幾率低的算法。

對象是否相同 不能用== 要用isEqual

NSString*foo =@"badger 123";

NSString*bar = [NSStringstringWithFormat:@"badger %i",123];

BOOLequalA = (foo == bar );//< equalA = NO

BOOLequalB = [fooisEqual:bar];//< equalB = YES

BOOLequalC = [fooisEqualToString:bar];//< equalC = YES

9.以“類族模式”隱藏實現(xiàn)細節(jié)

子類應(yīng)該繼承自類族中的抽象基類。

子類應(yīng)該定義自己的數(shù)據(jù)存儲方式。

子類應(yīng)當復(fù)寫超類文檔中指明需要復(fù)寫的方法

類族模式可以把實現(xiàn)細節(jié)隱藏在一套簡單的公共接口后面。

系統(tǒng)框架中經(jīng)常使用類族。

從類族的公共抽象基類中繼承子類時要當心,若有開發(fā)文檔,則應(yīng)首先閱讀。

例如NSArray與NSMutableArray

//? YDEmployee.h

#import

typedefNS_ENUM(NSUInteger, YDEmployeeType){

YDEmployeeTypeDeveloper,

YDEmployeeTypeDesigner,

YDEmployeeTypeFinance,

};

@interfaceYDEmployee :NSObject

@property(nonatomic, copy)NSString*name;

@property(nonatomic, assign)NSUIntegersalary;

//1.定義類方法,根據(jù)不同type,放回同一父類對象

+(YDEmployee*)employeeWithType:(YDEmployeeType)type;

-(void)doWork;

@end

//? YDEmployee.m

#import"YDEmployee.h"

//3.定義各自子類

@interfaceYDEmployeeDeveloper :YDEmployee

@end

@implementationYDEmployeeDeveloper

-(void)doWork

{

[selfwriteCode];

}

-(void)writeCode

{

/////

}

@end

@interfaceYDEmployeeDesigner :YDEmployee

@end

@implementationYDEmployeeDesigner

-(void)doWork

{

[selfdesighApp];

}

-(void)desighApp

{

/////

}

@end

@interfaceYDEmployeeFinance :YDEmployee

@end

@implementationYDEmployeeFinance

-(void)doWork

{

[selfmanageFinance];

}

-(void)manageFinance

{

////

}

@end

@implementationYDEmployee

//2.抽象基類沒有特殊標識,一般不定義init方法也不實現(xiàn)抽象函數(shù),各自在子類里實現(xiàn)

+(YDEmployee*)employeeWithType:(YDEmployeeType)type

{

switch(type) {

caseYDEmployeeTypeDeveloper:

return[YDEmployeeDevelopernew];

break;

caseYDEmployeeTypeDesigner:

return[YDEmployeeDesignernew];

break;

caseYDEmployeeTypeFinance:

return[YDEmployeeFinancenew];

break;

default:

break;

}

returnnil;

}

-(void)doWork

{

//2.1拋出一個異常,避免在基類里面實現(xiàn)

NSException*e = [NSException

exceptionWithName:@"exceptionName"

reason:@"必須在子類實現(xiàn)改方法"

userInfo:nil];

@throwe;

}

@end

10.在既有類中,使用關(guān)聯(lián)對象(Associated Object)存放自定義數(shù)據(jù)

可以通過“關(guān)聯(lián)對象”機制來把兩個對象連起來。

定義關(guān)聯(lián)對象時可指定內(nèi)存管理語義,用以模仿定義屬性時所采用的“擁有關(guān)系”與“非擁有關(guān)系”。

只有在其他做法不可行時才應(yīng)選用關(guān)聯(lián)對象,因為這種做法通常會引入難于查找的bug。

下列方法可以管理關(guān)聯(lián)對象:

voidobjc_setAssociatedObject(idobject,void*key,idvalue, objc_AssociationPolicy policy)此方法以給定的鍵和策略為某對象設(shè)置關(guān)聯(lián)對象值。

idobjc_getAssociatedObject(idobject,void*key)此方法根據(jù)給定的鍵從某對象中獲取相應(yīng)的關(guān)聯(lián)對象值。

voidobjc_removeAssociatedObjects(idobject)此方法移除指定對象的全部關(guān)聯(lián)對象。

關(guān)聯(lián)對象類型 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 等效的@property

OBJC_ASSOCIATION_ASSIGN ? ? ? ? ? assign

_RETAIN_NONATOMIC ? ? ? ? ? ? ? ? ? ? ? ?nonatomic,retain

...

11.理解objc_msgSend的作用

消息由接受者,selector及參數(shù)構(gòu)成。給某對象“發(fā)送消息”也就相當于在該對象上調(diào)用方法。

發(fā)給某對象的全部消息都要由“動態(tài)消息派發(fā)系統(tǒng)”來處理,該系統(tǒng)會查出對應(yīng)的方法,并執(zhí)行其代碼。

id returnValue = [someObject messageName:parameter];

編譯器看到消息后將其轉(zhuǎn)換為標準的C語言函數(shù)調(diào)用,原型是:

void objc_msgSend(id self,SEL cmd,...)

轉(zhuǎn)換后:

id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);

12.理解消息轉(zhuǎn)發(fā)機制

若對象無法響應(yīng)某個selector,則進入消息轉(zhuǎn)發(fā)流程。

通過運行期的動態(tài)方法解析功能,我們可以在需要用到某個方法時再將其加入類中。

對象可以將其無法解讀的某些selector轉(zhuǎn)交給其他對象處理。

經(jīng)過上述兩步后,如果還是沒辦法處理selector,那就啟動完整的消息轉(zhuǎn)發(fā)機制。

13.用method swizzling調(diào)試黑盒方法

在runtime中,可以向類中新增或替換selector所對應(yīng)的方法實現(xiàn)。

使用另一份實現(xiàn)來替換原有的方法實現(xiàn),這道工序叫做method swizzling,開發(fā)者常用此技術(shù)向原有視線中添加功能。

一般來說,只有調(diào)試程序的時候才需要在runtime中修改方法實現(xiàn),這種做法不宜濫用。

//獲取方法實現(xiàn)

Method originalMethod =class_getInstanceMethod([NSStringclass],@selector(lowercaseSting));

Method swappedMethod =class_getInstanceMethod([NSStringclass],@selector(uppercaseString));

//交換方法

method_exchangeImplementations(originalMethod, swappedMethod);

14.理解“類對象”的用意

每個實例都有一個指向Class對象的指針,用以表明其類型,而這些Class對象則構(gòu)成了類的繼承體系。

如果對象類型無法在編譯期確定,那么就應(yīng)該使用類型信息查詢方法來探知。

盡量使用類型信息查詢方法來確定對象類型,而不要直接比較類對象,因為某些對象可能實現(xiàn)了消息轉(zhuǎn)發(fā)功能。

對象都有isa指針,指向的是對象的元類(metaclass)

id 中也包含isa指針,id對象定義

typedef struct objc_object{

Class isa;

} *id;


三、接口與API設(shè)計

15.用前綴避免命名空間沖突

選擇與你公司、應(yīng)用程序或者二者皆有關(guān)聯(lián)之名稱作為類名的前綴,并在所有代碼中均使用這一前綴。

若自己所開發(fā)的程序庫中用到了第三方庫,則應(yīng)為其中的名稱加上前綴。

Apple宣稱保留使用所有兩字母前綴的權(quán)利,所以自己所選用的前綴最好是三字母的。

比如你的程序中引用了XYZLibrary,想要發(fā)布,那么應(yīng)該都改為YDXYZLibrary,防止直接他人引入XYZLibrary產(chǎn)生沖突

16.提供“全能初始化方法”

在類中提供一個全能初始化方法,并于文檔里指明。其它初始化方法均應(yīng)調(diào)用此方法。

若全能初始化方法與超類不同,則需覆寫超類中對應(yīng)方法。

如果超類的初始化方法并不適用于子類,那么應(yīng)該覆寫這個超類方法,并在其中拋出異常。

拋出異常方法:

@throw[NSExceptionexceptionWithName:NSInternalInconsistencyExceptionreason:@"Must use initWithDimension:instead."userInfo:nil];

17.實現(xiàn)description方法

實現(xiàn)description方法返回一個有意義的字符串,用以描述該實例。

若想在調(diào)試時打印出更詳盡的對象描述信息,則應(yīng)該實現(xiàn)debugDescription方法。

//name,age 為對象屬性

- (NSString*)description{

return [NSString stringWithFormat:@"%@ %d",_name,_age];

}

- (NSString*)debugDescription{

return [NSString stringWithFormat:@"<%@: %p,\"%@ %d\">",[self ?class],self,_name,_age];

}

18.盡量使用不可變對象

盡量創(chuàng)建不可變的對象。

若某屬性僅可于對象內(nèi)部修改,則在“class-continuation分類”中將其由readonly屬性擴展為readwrite屬性。

不要把可變的collection作為屬性公開,而應(yīng)提供相關(guān)方法,一次修改對象中的可變collection。

擴展例子

.h 中

@property(nonatomic,copy,readonly)NSString*someString;

//提供初始化方法

- (instancetype)initWithSomeString:(NSString*)someString;

.m中class-continuation 中

@property(nonatomic,copy,readwrite)NSString*someString;

@property(nonatomic,strong,readwrite)dispatch_queue_tsyncQueue;

.m中

//派發(fā)隊列防止發(fā)生“競爭條件”

_syncQueue=dispatch_queue_create("com.manqian.syncQueue",NULL);

- (NSString*)someString{

__blockNSString*localSomeString;

dispatch_sync(_syncQueue, ^{

localSomeString =_someString;

});

returnlocalSomeString;

}

- (void)setSomeString:(NSString*)someString

{

dispatch_barrier_async(_syncQueue, ^{

_someString= someString;

});

}

19.使用清晰而協(xié)調(diào)的命名方式

起名時應(yīng)遵從標準的Objective-C命名規(guī)范,這樣創(chuàng)建出來的接口更容易為開發(fā)者所理解。

方法名要言簡意賅,從左至右讀起來要像個日常用語中的句子才好。

方法名利不要使用縮略后的類型名稱。

給方法嗎起名時的第一要務(wù)就是確保其風格與你自己的代碼或所要集成的框架相符。

繼承至UIView的子類,類名末尾一定是View,同理其他的也需要這樣

20.為私有方法名加前綴

給私有方法的名稱加上前綴,這樣可以很容易的將其通公共方法區(qū)分開。

不要單用一個下劃線做私有方法的前綴,因為這種做法的預(yù)留給蘋果公司用的。

建議私有方法用p_ 開頭,如

- (void)p_privateMethod {

/* … */

}

21.理解Objective-C錯誤模型

只有發(fā)生了可使整個應(yīng)用程序崩潰的嚴重錯誤時,才使用異常。

在錯誤不那么嚴重的情況下,可以指派委托方法來處理錯誤,也可把錯誤信息放在NSError對象里,經(jīng)由輸出參數(shù)返回給調(diào)用者。

NSError ** 會轉(zhuǎn)化為 ?MSError *__autoreleasing*

意為 指向指針的指針

22.理解NSCopying協(xié)議

若想令自己所寫的對象具有拷貝功能,則需實現(xiàn)NSCopying協(xié)議。

如果自定義的對象分為可變版本與不可變版本,那么就要同時實現(xiàn)NSCopying與NSMutableCopying協(xié)議。

復(fù)制對象時需決定采用淺拷貝還是深拷貝,一般情況下應(yīng)該盡量執(zhí)行淺拷貝。

如果你所寫的對象需要深拷貝,那么可考慮新增一個專門執(zhí)行深拷貝的方法。

NSCopying協(xié)議只有一個方法

- (id) copyWithZone:(NSZone *)zone ?//zone 區(qū),每個程序只有一個“默認區(qū)”

深拷貝 會新建一個對象,原來的對象被銷毀了,深拷貝的對象不會被銷毀

淺拷貝 不會新建對象,只是新增了一個指針指向原來的對象,原來的對象被銷毀,淺拷貝的東西也被銷毀

四、協(xié)議與分類

23.通過委托與數(shù)據(jù)源協(xié)議進行對象間通信

委托模式為對象提供了一套接口,使其可由此將相關(guān)事件告知其他對象。

將委托對象應(yīng)該支持的接口定義成協(xié)議,在協(xié)議中把可能需要吃力的事件定義成方法。

當某對象需要從另外一個對象中獲取數(shù)據(jù)時,可使用委托模式。在這種情況下,該模式亦稱數(shù)據(jù)源協(xié)議。

若有必要,可實現(xiàn)含有位段的結(jié)構(gòu)體,將委托對象是否能響應(yīng)相關(guān)協(xié)議方法這一信息緩存至其中。

數(shù)據(jù)流向

Data Source —>class—>Delegate

#import <Foundation/Foundation>

@classYDNetworkFetcher;

@protocolYDNetworkFetcherDelegate

@optional

- (void)networkFetcher:(YDNetworkFetcher*)fetcher didReceiveData:(NSData*)data;

- (void)networkFetcher:(YDNetworkFetcher*)fetcher didFailWithError:(NSError*)error;

- (void)networkFetcher:(YDNetworkFetcher*)fetcher didUpdateProgressTo:(float*)progress;

@end?

@interfaceYDNetworkFetcher :NSObject

/***設(shè)置代理*/

@property(nonatomic,weak)id delegate;

@end

#import"YDNetworkFetcher.h"

@interfaceYDNetworkFetcher()

{

struct{

unsignedintdidReceiveData :1;

unsignedintdidFailWithError :1;

unsignedintdidUpdateProgressTo :1;

} _delegateFlags;

}

@end

@implementationYDNetworkFetcher

- (void)setDelegate:(id)delegate{

iddelegateObject = delegate;

_delegate= delegate;

//將代理是否執(zhí)行方法緩存起來

_delegateFlags.didReceiveData= [delegateObjectrespondsToSelector:@selector(networkFetcher:didReceiveData:)];

_delegateFlags.didFailWithError= [delegateObjectrespondsToSelector:@selector(networkFetcher:didFailWithError:)];

_delegateFlags.didUpdateProgressTo= [delegateObjectrespondsToSelector:@selector(networkFetcher:didUpdateProgressTo:)];

}

- (void)networkFetcher:(YDNetworkFetcher*)fetcher didReceiveData:(NSData*)data

{

//不需要每次都去查找是否實現(xiàn)該方法

if(_delegateFlags.didReceiveData) {

[_delegatenetworkFetcher:selfdidReceiveData:data];

}

}

- (void)networkFetcher:(YDNetworkFetcher*)fetcher didFailWithError:(NSError*)error{

if(_delegateFlags.didFailWithError) {

[_delegatenetworkFetcher:selfdidFailWithError:error];

}

}

- (void)networkFetcher:(YDNetworkFetcher*)fetcher didUpdateProgressTo:(float*)progress{

if(_delegateFlags.didUpdateProgressTo) {

[_delegatenetworkFetcher:selfdidUpdateProgressTo:progress];

}

}

@end

24.將類的實現(xiàn)代碼分散到便于管理的數(shù)個分類之中

使用分類機制把類的實現(xiàn)代碼劃分成易于管理的小塊。

將應(yīng)該視為私有的方法歸入名叫Private的分類中,以隱藏實現(xiàn)細節(jié)。

@interfaceYDPerson :NSObject

@property(nonatomic,copy)NSString*firstName;

@property(nonatomic,copy)NSString*lastName;

@property(nonatomic,strong)NSArray*friends;

- (instancetype)initWithFirstName:(NSString*)firstName

LastName:(NSString*)lastName

Friends:(NSArray*)friends;

@end

@interfaceYDPerson (Friendship)

- (void)addFriend:(YDPerson*)person;

- (void)removeFriends:(YDPerson*)person;

- (BOOL)isFriendsWith:(YDPerson*)person;

@end

@interfaceYDPerson (Work)

- (void)performDaysWork;

- (void)takeVacationFromWork;

@end

@interfaceYDPerson (Play)

- (void)goToTheCinema;

- (void)goToSportsGame;

@end

25.總是為第三方類的分類名稱加前綴

向第三方類中添加分類時,總應(yīng)給其名稱加上你專用的前綴。

向第三方類中添加分類時,總應(yīng)給其中的方法名加上你專用的前綴。

26.勿在分類中聲明屬性

把封裝數(shù)據(jù)所用的全部屬性都定義在主接口里。

在class-continuation分類之外的其他分類中,可以定義存取方法,但盡量不要定義屬性。

27.使用class-continuation分類隱藏實現(xiàn)細節(jié)

通過class-continuation分類向類中新增實例變量。

如果某屬性在主接口中聲明為只讀,而類的內(nèi)部又要用設(shè)置方法修改此屬性,那么就在class-continuation分類中將其擴展為可讀寫。

把私有方法的原型聲明在class-continuation分類里面。

若想使類遵循的協(xié)議不為人所知,則可于class-continuation分類中聲明。

28.通過協(xié)議提供匿名對象

協(xié)議可在某種程度上提供匿名類型。具體的對象類型可以淡化成遵從某些一的id類型,協(xié)議里規(guī)定了對象所應(yīng)實現(xiàn)的方法。

使用匿名對象來隱藏類型名稱或類名。

如果具體類型不重要,重要的是對象能夠響應(yīng)(定義在協(xié)議里的)特定方法,那么可使用匿名對象來表示。

最后編輯于
?著作權(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)容