block底層實(shí)現(xiàn)

最近讀書,關(guān)于block的底層實(shí)現(xiàn),有以下思考和總結(jié)

  • c++部分的相關(guān)步驟分析寫在代碼注釋
一、block是什么

1.首先寫一個(gè)簡(jiǎn)單的block

#import <stdio.h>

int main(void) {
    
    void (^block)(void) = ^{
        printf("hello world!");
    };
    
    block();
    
    return 0;
}

2.將main.m 編譯后 clang -rewite-objc main.m 生成 .cpp 文件

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
/**
3.參數(shù)fp即傳入的參數(shù)__main_block_func_0函數(shù)的地址,賦值給結(jié)構(gòu)體實(shí)例 impl 的屬性 FuncPtr
4.參數(shù)desc即傳入的 &__main_block_desc_0_DATA結(jié)構(gòu)體取地址賦值給了結(jié)構(gòu)體指針 Desc
5.結(jié)構(gòu)體實(shí)例 impl 的 isa 指針存放了 _NSConcreteStackBlock類的地址
6._NSConcreteStackBlock是在將block作為OC對(duì)象處理時(shí),該類的信息放置于_NSConcreteStackBlock 中,
由此可見,block的實(shí)質(zhì)是OC對(duì)象
*/
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {


        printf("hello world!");
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(void) {

    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
/**
對(duì)應(yīng)代碼
void (^block)(void) = ^{
    printf("hello world!");
};
1.等號(hào)左邊 void (*block)(void)是一個(gè)無參無返的函數(shù)指針(是一個(gè)指針,指向函數(shù))
2.等號(hào)右邊 __main_block_impl_0 首先,c++結(jié)構(gòu)體包含自己的屬性,構(gòu)造方法,成員方法,
所以 &__main_block_impl_0()是對(duì)結(jié)構(gòu)體構(gòu)造函數(shù)取地址,
函數(shù)的參數(shù)是 (void *)__main_block_func_0 和 &__main_block_desc_0_DATA
*/

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
/**
對(duì)應(yīng)代碼 block();
7.前半部分((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)
訪問結(jié)構(gòu)體 __block_impl自己的成員變量FuncPtr,
FuncPtr在步驟3被賦值了 __main_block_func_0(block執(zhí)行代碼塊)函數(shù)的地址
8.后半部分((__block_impl *)block),將block自身作為參數(shù)傳遞
*/

    return 0;
}

3.引入變量

int main(void) {
    
    int a = 10;
    void (^block)(void) = ^{
        printf("%d\n",a);
    };
    block();
    
    return 0;
}
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
/**
2.在c++中 a(_a) 表示 _a 這個(gè)形參賦值給 a 這個(gè)實(shí)參
*/
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
/**
3.定義了一個(gè)新的變量,接收結(jié)構(gòu)體成員變量a的值,__cself->a 表示結(jié)構(gòu)體訪問自己的屬性。
*/
        printf("%d\n",a);
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(void) {

    int a = 10;
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
/**
1.將變量a的值傳入,并賦值給了結(jié)構(gòu)體成員變量
*/

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

4.__block變量

int main(void) {
    
    __block int a = 10;
    void (^block)(void) = ^{
        a += 10;
        printf("%d\n",a);
    };
    
    block();
    
    return 0;
}
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;  //由步驟2得出指向的結(jié)構(gòu)體本身
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
/**
4.將傳入的結(jié)構(gòu)體指針的成員變量__forwarding的值(地址),賦值給當(dāng)前結(jié)構(gòu)體成員變量結(jié)構(gòu)體指針a,保存了__block變量地址
*/
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref
/**
5.訪問結(jié)構(gòu)體成員變量a,賦值給新的結(jié)構(gòu)體指針
*/
        (a->__forwarding->a) += 10;
/**
6.a->__forwarding首先找到結(jié)構(gòu)體指針的指向,a->__forwarding->a獲取到有效成員變量a的值并進(jìn)行修改
*/
        printf("%d\n",(a->__forwarding->a));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
/**
7.棧copy到堆時(shí)調(diào)用,因?yàn)榻Y(jié)構(gòu)體成員變量包含所截獲的變量或者_(dá)_block變量結(jié)構(gòu)體指針,所以copy的時(shí)候也一起copy了
*/
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
/**
8.堆上的block廢棄時(shí)調(diào)用
*/
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(void) {

    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
/**
1.變量a不再單純的是一個(gè)int類型,變成了__Block_byref_a_0結(jié)構(gòu)體類型,并分別傳入的參數(shù)進(jìn)行賦值
2.第二個(gè)參數(shù)(__Block_byref_a_0 *)&a,結(jié)構(gòu)體指針,值是a的地址,a又是結(jié)構(gòu)體__Block_byref_a_0,
所以__Block_byref_a_0中的結(jié)構(gòu)體指針__forwarding指向自身
3.C++中,__attribute__ 表示可以設(shè)置函數(shù)屬性。
理解一下字面意思大概知道內(nèi)部發(fā)生了什么
byref 是把內(nèi)存地址告訴程序,所以改變的直接就是內(nèi)存中的數(shù)值。按地址傳遞參數(shù)使過程用變量的內(nèi)存地址去訪問實(shí)際變量的內(nèi)容。結(jié)果,將變量傳遞給過程時(shí),通過過程可永遠(yuǎn)改變變量值。
byval 是把內(nèi)存數(shù)值的拷貝給程序,所以改變的只是拷貝,內(nèi)存原來的值是不會(huì)改變的。按值傳遞參數(shù)時(shí),傳遞的只是變量的副本。如果過程改變了這個(gè)值,則所作變動(dòng)只影響副本而不會(huì)影響變量本身。
*/

    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

??結(jié)論:
1.block是函數(shù)指針,指向的是block代碼塊編譯時(shí)生成的函數(shù)地址。執(zhí)行block相當(dāng)于找到指向block的指針。
2.block對(duì)象是結(jié)構(gòu)體
(1)有isa指針(impl->isa)指向自己的類(_NSConcreteStackBlock)
(2)有desc結(jié)構(gòu)體描述block的信息
(3)__forwarding((__Block_byref_a_0 *)a -> __forwarding)指向自己或堆上自己的地址
(4)當(dāng)block對(duì)象截獲變量,變量也會(huì)出現(xiàn)在block結(jié)構(gòu)體中(int a)
(5)指向block代碼塊的函數(shù)指針(impl.FuncPtr = fp)。
(6)block結(jié)構(gòu)體的構(gòu)造函數(shù)的參數(shù),包括函數(shù)指針,描述block的結(jié)構(gòu)體,自動(dòng)截獲的變量(全局變量不用截獲),引用到的__block變量。(__block對(duì)象也會(huì)轉(zhuǎn)變成結(jié)構(gòu)體)
3.block代碼塊在編譯的時(shí)候會(huì)生成一個(gè)函數(shù),函數(shù)的參數(shù)是block對(duì)象結(jié)構(gòu)體指針。執(zhí)行block,相當(dāng)于通過block的地址找到__block變量結(jié)構(gòu)體指針,再找到變量值,進(jìn)行操作。

二、block的類

1.block在編譯中,會(huì)被當(dāng)成結(jié)構(gòu)體處理,block實(shí)際結(jié)構(gòu)

structBlock_literal_1{
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    intflags;
    intreserved;
    void(*invoke)(void *,...);    //block執(zhí)行時(shí)調(diào)用的函數(shù)指針,block定義時(shí)內(nèi)部的執(zhí)行代碼
    structBlock_descriptor_1{
        unsigned long intreserved;         // NULL
        unsigned long intsize;         // sizeof(struct Block_literal_1)
        // optional helper functions
         void(*copy_helper)(void *dst, void *src);     // IFF (1<<25)
         void(*dispose_helper)(void *src);             // IFF (1<<25)
         // required ABI.2010.3.16
         const char *signature;                         // IFF (1<<30)
     } *descriptor;
     // imported variables
};

2.block的類有三種

block的類 設(shè)置對(duì)象的存儲(chǔ)域
_NSConcreteGlobalBlock 程序的數(shù)據(jù)區(qū)域(.data區(qū))
_NSConcreteStackBlock
_NSConcreteMallocBlock
應(yīng)用程序的內(nèi)存分配

(1)_NSConcreteGlobalBlock

#import <Foundation/Foundation.h>

void (^block)(void) = ^{
    
};

int main(void) {
    
    void (^block1)(void) = ^{
        
    };
    
    NSLog(@"%s %p",object_getClassName(block),block);
    NSLog(@"%s %p",object_getClassName(block1),block1);
    
    return 0;
}

//輸出
//__NSGlobalBlock__ 0x100001058
//__NSGlobalBlock__ 0x100001098

(2)_NSConcreteStackBlock
當(dāng)block引入變量,變量?jī)?nèi)存地址會(huì)發(fā)生如下變化

#import <Foundation/Foundation.h>

int main(void) {
    
    int a = 10;
    void (^block)(void) = ^{
        printf("%d\n",a);
    };
    
    NSLog(@"%s %p",object_getClassName(block),block);
    NSLog(@"%@",^{
        printf("%d\n",a);
    });
    
    return 0;
}
//輸出
//__NSMallocBlock__ 0x100443c80
//<__NSStackBlock__: 0x7fff5fbff6b0>

(3)_NSConcreteMallocBlock
從上一步可以看出,block在被賦值后,從棧來到了堆,這段代碼是從棧copy到堆的過程

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    ...
    aBlock = (struct Block_layout *)arg;
    ...
    // Its a stack block.  Make a copy.
    if (!isGC) {
        // 申請(qǐng)block的堆內(nèi)存
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        // 拷貝棧中block到剛申請(qǐng)的堆內(nèi)存中
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 1;
        // 改變isa指向_NSConcreteMallocBlock,即堆block類型
        result->isa = _NSConcreteMallocBlock;
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
    else {
        ...
    }
}

block是默認(rèn)分配在棧上的唯一OC對(duì)象,因?yàn)榫幾g器更傾向于在棧上分配空間,因?yàn)閳?zhí)行效率較高,但是只能是在已知實(shí)際大小的情況下去分配,只有簡(jiǎn)單的值(比如指針)可以被分配在棧上,block的大小是確定的,你創(chuàng)建了一個(gè)給定的block就不能修改, 在整個(gè)執(zhí)行過程中block不變, 它是需要快速執(zhí)行的代碼段,因此它是棧的最佳候選。

??結(jié)論:
1.定義在函數(shù)外的block和定義在函數(shù)內(nèi)部且沒有捕獲自動(dòng)變量的block是全局block。
2.ARC下并且有變量捕獲的情況下,對(duì)block自動(dòng)執(zhí)行了copy,將block由棧---->copy---->堆
3.copy的過程中,主要實(shí)現(xiàn),通過memmove函數(shù)將block內(nèi)容進(jìn)行copy,并且將 isa 指針指向了_NSConcreteMallocBlock

三、block的copy

1.自身的copy
(1)棧Block
棧block拷貝復(fù)制了內(nèi)容,重置了isa指針指向,重置了flags參數(shù)

struct Block_layout *result = malloc(aBlock->descriptor->size);
      if (!result) return (void *)0;
      memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
      // reset refcount
      result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
      result->flags |= BLOCK_NEEDS_FREE | 1;
      result->isa = _NSConcreteMallocBlock;
      if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
          //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
          (*aBlock->descriptor->copy)(result, aBlock); // do fixup
      }
      return result;

(2)堆Block
改變引用計(jì)數(shù)

if (aBlock->flags & BLOCK_NEEDS_FREE) {
      // latches on high
      latching_incr_int(&aBlock->flags);
      return aBlock;
  }

(3)全局Block
直接返回了傳入的block

else if (aBlock->flags & BLOCK_IS_GLOBAL) {
      return aBlock;
  }

2.__block變量的copy
(1)__block修飾的基本數(shù)據(jù)類型變量
會(huì)生成__Block_byref_a_0結(jié)構(gòu)體,__block將基本數(shù)據(jù)類型包裝成對(duì)象,并且只在最初block拷貝時(shí)復(fù)制一次,后面的拷貝只會(huì)增加這個(gè)捕獲變量的引用計(jì)數(shù)。
(2)沒有用__block修飾的對(duì)象變量

    NSObject *obj = [[NSObject alloc] init];
    void(^block)(void) = ^{
        NSLog(@"%@",obj);
    };
    block();
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *obj;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSObject *obj = __cself->obj; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_jctrsbb11gb_vzv3gzmszy1h0000gn_T_main_8ef076_mi_0,obj);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(void) {
    NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

并沒有生成__Block_byref_a_0結(jié)構(gòu)體,在_Block_object_assign中對(duì)應(yīng)的判斷代碼:

else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
    _Block_retain_object(object);
    _Block_assign((void *)object, destAddr);
}

通過以上代碼可以看出,對(duì)對(duì)象進(jìn)行了retain,操作了對(duì)象的引用計(jì)數(shù)
(3)用__block修飾的對(duì)象變量

    __block NSObject *obj = [[NSObject alloc] init];
    void(^block)(void) = ^{
        NSLog(@"%@",obj);
    };
    block();
struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_obj_0 *obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_obj_0 *obj = __cself->obj; // bound by ref

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_jctrsbb11gb_vzv3gzmszy1h0000gn_T_main_ae30ca_mi_0,(obj->__forwarding->obj));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(void) {
    __attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

生成了__Block_byref_obj_0結(jié)構(gòu)體,并且結(jié)構(gòu)體中多了兩個(gè)成員函數(shù),void (*__Block_byref_id_object_copy)(void*, void*);void (*__Block_byref_id_object_dispose)(void*); 用來實(shí)現(xiàn)對(duì)對(duì)象的內(nèi)存管理。copy/dispose可以理解為retain(copy)/release

四、block的循環(huán)引用
#import "Person.h"

typedef void(^block)(void);

@implementation Person
{
    block _blk;
}

-(void)test
{
    _blk = ^{
        NSLog(@"%@",self);  //??warning - retainCycle
    };
}

@end

這段代碼會(huì)報(bào)warning??,因?yàn)樵斐裳h(huán)引用retainCycle


相互持有

解決
1.__weak一端變?yōu)槿跻茫∕RC下無效)

    __weak typeof(self)wself = self;
    _blk = ^{
        NSLog(@"%@",wself);
    };
__weak弱引用

2.引入__block變量(ARC下無效)
當(dāng)block由棧copy到堆,若block使用的變量為附有__block說明符的id類型或?qū)ο箢愋偷淖詣?dòng)變量,不會(huì)被retain
這段代碼在ARC下不會(huì)走dealloc方法

    __block id obj = self;
    _blk = ^{
        NSLog(@"%@",obj);
    };
引入__block變量

以上是我個(gè)人分析,有不合理的地方,歡迎指正

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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