iOS 底層探索 - 類(lèi)

iOS 底層探索 - 類(lèi).png

iOS 底層探索系列

我們?cè)谇懊嫣剿髁?iOS 中的對(duì)象原理,面向?qū)ο缶幊讨杏幸痪涿?

萬(wàn)物皆對(duì)象

那么對(duì)象又是從哪來(lái)的呢?有過(guò)面向?qū)ο缶幊袒A(chǔ)的同學(xué)肯定都知道是類(lèi)派生出對(duì)象的,那么今天我們就一起來(lái)探索一下類(lèi)的底層原理吧。

一、iOS 中的類(lèi)到底是什么?

我們?cè)谌粘i_(kāi)發(fā)中大多數(shù)情況都是從 NSObject 這個(gè)基類(lèi)來(lái)派生出我們需要的類(lèi)。那么在 OC 底層,我們的類(lèi) Class 到底被編譯成什么樣子了呢?

我們新建一個(gè) macOS 控制臺(tái)項(xiàng)目,然后新建一個(gè) Animal 類(lèi)出來(lái)。

// Animal.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Animal : NSObject

@end

NS_ASSUME_NONNULL_END

// Animal.m
@implementation Animal

@end

// main.m
#import <Foundation/Foundation.h>
#import "Animal.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Animal *animal = [[Animal alloc] init];
        NSLog(@"%p", animal);
    }
    return 0;
}

我們?cè)诮K端執(zhí)行 clang 命令:

clang -rewrite-objc main.m -o main.cpp

這個(gè)命令是將我們的 main.m 重寫(xiě)成 main.cpp,我們打開(kāi)這個(gè)文件搜索 Animal:

image

我們發(fā)現(xiàn)有多個(gè)地方都出現(xiàn)了 Animal:

// 1
typedef struct objc_object Animal;

// 2
struct Animal_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};

// 3
objc_getClass("Animal")

我們先全局搜索第一個(gè) typedef struct objc_object,發(fā)現(xiàn)有 843 個(gè)結(jié)果

image

我們通過(guò) Command + G 快捷鍵快速翻閱一下,最終在 7626 行找到了 Class 的定義:

typedef struct objc_class *Class;

由這行代碼我們可以得出一個(gè)結(jié)論,Class 類(lèi)型在底層是一個(gè)結(jié)構(gòu)體類(lèi)型的指針,這個(gè)結(jié)構(gòu)體類(lèi)型為 objc_class。
再搜索 typedef struct objc_class 發(fā)現(xiàn)搜不出來(lái)了,這個(gè)時(shí)候我們需要在 objc4-756 源碼中進(jìn)行探索了。

我們?cè)?objc4-756 源碼中直接搜索 struct objc_class ,然后定位到 objc-runtime-new.h 文件

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
}

看到這里,細(xì)心的讀者可能會(huì)發(fā)現(xiàn),我們?cè)谇懊嫣剿鲗?duì)象原理中遇到的 objc_object 再次出現(xiàn)了,并且這次是作為 objc_class 的父類(lèi)。這里再次引用那句經(jīng)典名言 萬(wàn)物皆對(duì)象,也就是說(shuō)類(lèi)其實(shí)也是一種對(duì)象。

由此,我們可以簡(jiǎn)單總結(jié)一下類(lèi)和對(duì)象在 COC 中分別的定義

C OC
objc_object NSObject
objc_class NSObject(Class)

二、類(lèi)的結(jié)構(gòu)是什么樣的呢?

通過(guò)上面的探索,我們已經(jīng)知道了類(lèi)本質(zhì)上也是對(duì)象,而日常開(kāi)發(fā)中常見(jiàn)的成員變量、屬性、方法、協(xié)議等都是在類(lèi)里面存在的,那么我們是不是可以猜想在 iOS 底層,類(lèi)其實(shí)就存儲(chǔ)了這些內(nèi)容呢?

我們可以通過(guò)分析源碼來(lái)驗(yàn)證我們的猜想。

從上一節(jié)中 objc_class 的定義處,我們可以梳理出 Class 中的 4 個(gè)屬性

  • isa 指針
  • superclass 指針
  • cache
  • bits

需要值得注意的是,這里的 isa 指針在這里是隱藏屬性.

2.1 isa 指針

首先是 isa 指針,我們之前已經(jīng)探索過(guò)了,在對(duì)象初始化的時(shí)候,通過(guò) isa 可以讓對(duì)象和類(lèi)關(guān)聯(lián),這一點(diǎn)很好理解,可是為什么在類(lèi)結(jié)構(gòu)里面還會(huì)有 isa 呢?看過(guò)上一篇文章的同學(xué)肯定知道這個(gè)問(wèn)題的答案了。沒(méi)錯(cuò),就是元類(lèi)。我們的對(duì)象和類(lèi)關(guān)聯(lián)起來(lái)需要 isa,同樣的,類(lèi)和元類(lèi)之間關(guān)聯(lián)也需要 isa

2.2 superclass 指針

顧名思義,superclass 指針表明當(dāng)前類(lèi)指向的是哪個(gè)父類(lèi)。一般來(lái)說(shuō),類(lèi)的根父類(lèi)基本上都是 NSObject 類(lèi)。根元類(lèi)的父類(lèi)也是 NSObject 類(lèi)。

2.3 cache 緩存

cache 的數(shù)據(jù)結(jié)構(gòu)為 cache_t,其定義如下:

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
    
    ...省略代碼...
}

類(lèi)的緩存里面存放的是什么呢?是屬性?是實(shí)例變量?還是方法?我們可以通過(guò)閱讀 objc-cache.mm 源文件來(lái)解答這個(gè)問(wèn)題。

  • objc-cache.m
  • Method cache management
  • Cache flushing
  • Cache garbage collection
  • Cache instrumentation
  • Dedicated allocator for large caches

上面是 objc-cache.mm 源文件的注釋信息,我們可以看到 Method cache management 的出現(xiàn),翻譯過(guò)來(lái)就是方法緩存管理。那么是不是就是說(shuō) cache 屬性就是緩存的方法呢?而 OC 中的方法我們現(xiàn)在還沒(méi)有進(jìn)行探索,先假設(shè)我們已經(jīng)掌握了相關(guān)的底層原理,這里先簡(jiǎn)單提一下。

我們?cè)陬?lèi)里面編寫(xiě)的方法,在底層其實(shí)是以 SEL + IMP 的形式存在。SEL 就是方法的選擇器,而 IMP 則是具體的方法實(shí)現(xiàn)。這里可以以書(shū)籍的目錄以及內(nèi)容來(lái)類(lèi)比,我們查找一篇文章的時(shí)候,需要先知道其標(biāo)題(SEL),然后在目錄中看有沒(méi)有對(duì)應(yīng)的標(biāo)題,如果有那么就翻到對(duì)應(yīng)的頁(yè),最后我們就找到了我們想要的內(nèi)容。當(dāng)然,iOS 中方法要比書(shū)籍的例子復(fù)雜一些,不過(guò)暫時(shí)可以這么簡(jiǎn)單的理解,后面我們會(huì)深入方法的底層進(jìn)行探索。

2.4 bits 屬性

bits 的數(shù)據(jù)結(jié)構(gòu)類(lèi)型是 class_data_bits_t,同時(shí)也是一個(gè)結(jié)構(gòu)體類(lèi)型。而我們閱讀 objc_class 源碼的時(shí)候,會(huì)發(fā)現(xiàn)很多地方都有 bits 的身影,比如:

class_rw_t *data() { 
    return bits.data();
}

bool hasCustomRR() {
    return ! bits.hasDefaultRR();
}    

bool canAllocFast() {
    assert(!isFuture());
    return bits.canAllocFast();
}

這里值得我們注意的是,objc_classdata() 方法其實(shí)是返回的 bitsdata() 方法,而通過(guò)這個(gè) data() 方法,我們發(fā)現(xiàn)諸如類(lèi)的字節(jié)對(duì)齊、ARC、元類(lèi)等特性都有 data() 的出現(xiàn),這間接說(shuō)明 bits 屬性其實(shí)是個(gè)大容器,有關(guān)于內(nèi)存管理、C++ 析構(gòu)等內(nèi)容在其中有定義。

這里我們會(huì)遇到一個(gè)十分重要的知識(shí)點(diǎn): class_rw_t,data() 方法的返回值就是 class_rw_t 類(lèi)型的指針對(duì)象。我們?cè)诒疚暮竺鏁?huì)重點(diǎn)介紹。

三、類(lèi)的屬性存在哪?

上一節(jié)我們對(duì) OC 中類(lèi)結(jié)構(gòu)有了基本的了解,但是我們平時(shí)最常打交道的內(nèi)容-屬性,我們還不知道它究竟是存在哪個(gè)地方。接下來(lái)我們要做一件事情,就是在 objc4-756 的源碼中新建一個(gè) Target,為什么不直接用上面的 macOS 命令行項(xiàng)目呢?因?yàn)槲覀円_(kāi)始結(jié)合 LLDB 打印一些類(lèi)的內(nèi)部信息,所以只能是新建一個(gè)依靠于 objc4-756 源碼 projecttarget 出來(lái)。同樣的,我們還是選擇 macOS 的命令行作為我們的 target

接著我們新建一個(gè)類(lèi) Person,然后添加一些實(shí)例變量和屬性出來(lái)。

// Person.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
{
    NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
@end

NS_ASSUME_NONNULL_END

// main.m
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *p = [[Person alloc] init];
        Class pClass = object_getClass(p);
        NSLog(@"%s", p);
    }
    return 0;
}

我們打一個(gè)斷點(diǎn)到 main.m 文件中的 NSLog 語(yǔ)句處,然后運(yùn)行剛才新建的 target

target 跑起來(lái)之后,我們?cè)诳刂婆_(tái)先打印輸出一下 pClass 的內(nèi)容:

image

3.1 類(lèi)的內(nèi)存結(jié)構(gòu)

我們這個(gè)時(shí)候需要借助指針平移來(lái)探索,而對(duì)于類(lèi)的內(nèi)存結(jié)構(gòu)我們先看下面這張表格:

類(lèi)的內(nèi)存結(jié)構(gòu) 大小(字節(jié))
isa 8
superclass 8
cache 16

前兩個(gè)大小很好理解,因?yàn)?isasuperclass 都是結(jié)構(gòu)體指針,而在 arm64 環(huán)境下,一個(gè)結(jié)構(gòu)體指針的內(nèi)存占用大小為 8 字節(jié)。而第三個(gè)屬性 cache 則需要我們進(jìn)行抽絲剝繭了。

cache_t cache;

struct cache_t {
    struct bucket_t *_buckets; // 8
    mask_t _mask;  // 4
    mask_t _occupied; // 4
}

typedef uint32_t mask_t; 

從上面的代碼我們可以看出,cache 屬性其實(shí)是 cache_t 類(lèi)型的結(jié)構(gòu)體,其內(nèi)部有一個(gè) 8 字節(jié)的結(jié)構(gòu)體指針,有 2 個(gè)各為 4 字節(jié)的 mask_t。所以加起來(lái)就是 16 個(gè)字節(jié)。也就是說(shuō)前三個(gè)屬性總共的內(nèi)存偏移量為 8 + 8 + 16 = 32 個(gè)字節(jié),32 是 10 進(jìn)制的表示,在 16 進(jìn)制下就是 20。

3.2 探索 bits 屬性

我們剛才在控制臺(tái)打印輸出了 pClass 類(lèi)對(duì)象的內(nèi)容,我們簡(jiǎn)單畫(huà)個(gè)圖如下所示:

image

那么,類(lèi)的 bits 屬性的內(nèi)存地址順理成章的就是在 isa 的初始偏移量地址處進(jìn)行 16 進(jìn)制下的 20 遞增。也就是

0x1000021c8 + 0x20 = 0x1000021e8

我們嘗試打印這個(gè)地址,注意這里需要強(qiáng)轉(zhuǎn)一下:

image

這里報(bào)錯(cuò)了,問(wèn)題其實(shí)是出在我們的 target 沒(méi)有關(guān)聯(lián)上 libobjc.A.dylib 這個(gè)動(dòng)態(tài)庫(kù),我們關(guān)聯(lián)上重新運(yùn)行項(xiàng)目

image

我們重復(fù)一遍上面的流程:

image

這一次成功了。在 objc_class 源碼中有:

class_rw_t *data() { 
    return bits.data();
}

我們不妨打印一下里面的內(nèi)容:

image

返回了一個(gè) class_rw_t 指針對(duì)象。我們?cè)?objc4-756 源碼中搜索 class_rw_t:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;
    
    ...省略代碼...    
}

顯然的,class_rw_t 也是一個(gè)結(jié)構(gòu)體類(lèi)型,其內(nèi)部有 methods、propertiesprotocols 等我們十分熟悉的內(nèi)容。我們先猜想一下,我們的屬性應(yīng)該存放在 class_rw_tproperties 里面。為了驗(yàn)證我們的猜想,我們接著進(jìn)行 LLDB 打印:

image

我們?cè)俳又蛴?properties:

image

properties 居然是空的,難道是 bug?其實(shí)不然,這里我們還漏掉了一個(gè)非常重要的屬性 ro。我們來(lái)到它的定義:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    ...隱藏代碼...    
}

ro 的類(lèi)型是 class_ro_t 結(jié)構(gòu)體,它包含了 baseMethodList、baseProtocols、ivars、baseProperties 等屬性。我們剛才在 class_rw_t 中沒(méi)有找到我們聲明在 Person 類(lèi)中的實(shí)例變量 hobby 和屬性 nickName,那么希望就在 class_ro_t 身上了,我們打印看看它的內(nèi)容:

image

根據(jù)名稱(chēng)我們猜測(cè)屬性應(yīng)該在 baseProperties 里面,我們打印看看:

image

Bingo! 我們的屬性 nickName 被找到了,那么我們的實(shí)例變量 hobby 呢?我們從 $8 的 count 為 1 可以得知肯定不在 baseProperites 里面。根據(jù)名稱(chēng)我們猜測(cè)應(yīng)該是在 ivars 里面。

image

哈哈,hobby 實(shí)例變量也被我們找到了,不過(guò)這里的 count 為什么是 2 呢?我們打印第二個(gè)元素看看:

image

結(jié)果為 _nickName。這一結(jié)果證實(shí)了編譯器會(huì)幫助我們給屬性 nickName 生成一個(gè)帶下劃線前綴的實(shí)例變量 _nickName。

至此,我們可以得出以下結(jié)論:

class_ro_t 是在編譯時(shí)就已經(jīng)確定了的,存儲(chǔ)的是類(lèi)的成員變量、屬性、方法和協(xié)議等內(nèi)容。
class_rw_t 是可以在運(yùn)行時(shí)來(lái)拓展類(lèi)的一些屬性、方法和協(xié)議等內(nèi)容。

四、類(lèi)的方法存在哪?

研究完了類(lèi)的屬性是怎么存儲(chǔ)的,我們?cè)賮?lái)看看類(lèi)的方法。

我們先給我們的 Person 類(lèi)增加一個(gè) sayHello 的實(shí)例方法和一個(gè) sayHappy 的類(lèi)方法。

// Person.h
- (void)sayHello;
+ (void)sayHappy;

// Person.m
- (void)sayHello
{
    NSLog(@"%s", __func__);
}

+ (void)sayHappy
{
    NSLog(@"%s", __func__);
}

按照上面的思路,我們直接讀取 class_ro_t 中的 baseMethodList 的內(nèi)容:

image

sayHello 被打印出來(lái)了,說(shuō)明 baseMethodList 就是存儲(chǔ)實(shí)例方法的地方。我們接著打印剩下的內(nèi)容:

image

可以看到 baseMethodList 中除了我們的實(shí)例方法 sayHello 外,還有屬性 nickNamegettersetter 方法以及一個(gè) C++ 析構(gòu)方法。但是我們的類(lèi)方法 sayHappy 并沒(méi)有被打印出來(lái)。

五、類(lèi)的類(lèi)方法存在哪?

我們上面已經(jīng)得到了屬性,實(shí)例方法的是怎么樣存儲(chǔ),還留下了一個(gè)疑問(wèn)點(diǎn),就是類(lèi)方法是怎么存儲(chǔ)的,接下來(lái)我們用 Runtime 的 API 來(lái)實(shí)際測(cè)試一下。

// main.m
void testInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    NSLog(@"%s",__func__);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *p = [[Person alloc] init];
        Class pClass = object_getClass(p);
        
        testInstanceMethod_classToMetaclass(pClass);
        NSLog(@"%p", p);
    }
    return 0;
}

運(yùn)行后打印結(jié)果如下:

image

首先 testInstanceMethod_classToMetaclass 方法測(cè)試的是分別從類(lèi)和元類(lèi)去獲取實(shí)例方法、類(lèi)方法的結(jié)果。由打印結(jié)果我們可以知道:

  • 對(duì)于類(lèi)對(duì)象來(lái)說(shuō),sayHello 是實(shí)例方法,存儲(chǔ)于類(lèi)對(duì)象的內(nèi)存中,不存在于元類(lèi)對(duì)象中。而 sayHappy 是類(lèi)方法,存儲(chǔ)于元類(lèi)對(duì)象的內(nèi)存中,不存在于類(lèi)對(duì)象中。
  • 對(duì)于元類(lèi)對(duì)象來(lái)說(shuō),sayHello 是類(lèi)對(duì)象的實(shí)例方法,跟元類(lèi)沒(méi)關(guān)系;sayHappy 是元類(lèi)對(duì)象的實(shí)例方法,所以存在元類(lèi)中。

我們?cè)俳又鴾y(cè)試:

// main.m
void testClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    NSLog(@"%s",__func__);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *p = [[Person alloc] init];
        Class pClass = object_getClass(p);
        
        testClassMethod_classToMetaclass(pClass);
        NSLog(@"%p", p);
    }
    return 0;
}

運(yùn)行后打印結(jié)果如下:

image

從結(jié)果我們可以看出,對(duì)于類(lèi)對(duì)象來(lái)說(shuō),通過(guò) class_getClassMethod 獲取 sayHappy 是有值的,而獲取 sayHello 是沒(méi)有值的;對(duì)于元類(lèi)對(duì)象來(lái)說(shuō),通過(guò) class_getClassMethod 獲取 sayHappy 也是有值的,而獲取 sayHello 是沒(méi)有值的。這里第一點(diǎn)很好理解,但是第二點(diǎn)會(huì)有點(diǎn)讓人糊涂,不是說(shuō)類(lèi)方法在元類(lèi)中是體現(xiàn)為對(duì)象方法的嗎?怎么通過(guò) class_getClassMethod 從元類(lèi)中也能拿到 sayHappy,我們進(jìn)入到 class_getClassMethod 方法內(nèi)部可以解開(kāi)這個(gè)疑惑:

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

Class getMeta() {
    if (isMetaClass()) return (Class)this;
    else return this->ISA();
}    

可以很清楚的看到,class_getClassMethod 方法底層其實(shí)調(diào)用的是 class_getInstanceMethod,而 cls->getMeta() 方法底層的判斷邏輯是如果已經(jīng)是元類(lèi)就返回,如果不是就返回類(lèi)的 isa。這也就解釋了上面的 sayHappy 為什么會(huì)出現(xiàn)在最后的打印中了。

除了上面的 LLDB 打印,我們還可以通過(guò) isa 的方式來(lái)驗(yàn)證類(lèi)方法存放在元類(lèi)中。

  • 通過(guò) isa 在類(lèi)對(duì)象中找到元類(lèi)
  • 打印元類(lèi)的 baseMethodsList

具體的過(guò)程筆者不再贅述。

六、類(lèi)和元類(lèi)的創(chuàng)建時(shí)機(jī)

我們?cè)谔剿黝?lèi)和元類(lèi)的時(shí)候,對(duì)于其創(chuàng)建時(shí)機(jī)還不是很清楚,這里我們先拋出結(jié)論:

  • 類(lèi)和元類(lèi)是在編譯期創(chuàng)建的,即在進(jìn)行 alloc 操作之前,類(lèi)和元類(lèi)就已經(jīng)被編譯器創(chuàng)建出來(lái)了。

那么如何來(lái)證明呢,我們有兩種方式可以來(lái)證明:

  • LLDB 打印類(lèi)和元類(lèi)的指針
image
  • 編譯項(xiàng)目后,使用 MachoView 打開(kāi)程序二進(jìn)制可執(zhí)行文件查看:
image

六、總結(jié)

  • 類(lèi)和元類(lèi)創(chuàng)建于編譯時(shí),可以通過(guò) LLDB 來(lái)打印類(lèi)和元類(lèi)的指針,或者 MachOView 查看二進(jìn)制可執(zhí)行文件
  • 萬(wàn)物皆對(duì)象:類(lèi)的本質(zhì)就是對(duì)象
  • 類(lèi)在 class_ro_t 結(jié)構(gòu)中存儲(chǔ)了編譯時(shí)確定的屬性、成員變量、方法和協(xié)議等內(nèi)容。
  • 實(shí)例方法存放在類(lèi)中
  • 類(lèi)方法存放在元類(lèi)中

我們完成了對(duì) iOS 中類(lèi)的底層探索,下一章我們將對(duì)類(lèi)的緩存進(jìn)行深一步探索,敬請(qǐng)期待~

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

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

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