iOS使用频率最高的内存管理有哪几种

发布时间:2021-11-12 16:23:08 作者:iii
来源:亿速云 阅读:111

这篇文章主要介绍“iOS使用频率最高的内存管理有哪几种”,在日常操作中,相信很多人在iOS使用频率最高的内存管理有哪几种问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”iOS使用频率最高的内存管理有哪几种”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

一、block内存管理
1.block内存类型

block内存分为三种类型:

2.三种类型的内存的创建时机

1)对于_NSConcreteStackBlock_NSConcreteGlobalBlock类型
_NSConcreteStackBlock_NSConcreteGlobalBlock这两种类型的block,我们可以手动创建,如下所示:

void (^globalBlock)() = ^{};int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^stackBlock1)() = ^{
        };
    }
    return 0;}

那么我们怎么确定这两个block,就是我们所说的两种类型的block呢,我们可以使用clang -rewrite-objc xxx.m(报错可以使用详细命令: clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk xxxxx.m)编译转换成C++实现,就可以看到转换完的结果,如下所示:

// globalBlockstruct __globalBlock_block_impl_0 {
  struct __block_impl impl;
  struct __globalBlock_block_desc_0* Desc;
  __globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }};...// stackBlockstruct __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;
  }};...int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        void (*stackBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    }
    return 0;}

可以看出可以看出globalBlock是_NSConcreteGlobalBlock类型,即在全局区域创建,block变量存储在全局数据存储区;stackBlock是_NSConcreteStackBlock类型,即在栈区创建。
2)对于_NSConcreteMallocBlock类型
NSConcreteMallocBlock类型的内存是通过_NSConcreteStackBlock类型的block copy得到的,那么哪些类型会对block进行copy呢?

// 如果是weak类型的block,依然不会自动进行copy// <__NSStackBlock__: 0x7fff5fbff728>__weak void (^weakBlock)() = ^{i;};// ARC情况下输出// <__NSMallocBlock__NSLog(@"%@", [self callBack:weakBlock]);- (id)callBack:(void (^)(void))callBack{
     NSLog(@"%@", callBack);
    return callBack;}//输出结果<__NSStackBlock__: 0x7ffee2559838><__NSMallocBlock__: 0x600003a99ce0>
@property (copy, nonatomic) id myCopyBlock;@property (strong, nonatomic) id myStrongBlock;// 如果是weak类型的block,依然不会自动进行copy// <__NSStackBlock__: 0x7fff5fbff728>__weak void (^weakBlock)() = ^{i;};NSLog(@"%@", weakBlock);//会进行copy操作//<__NSMallocBlock__: 0x6000037e8db0>self.myCopyBlock  = weakBlock;NSLog(@"%@", self.myCopyBlock);// 会进行strong操作// <__NSStackBlock__: 0x7fff5fbff728>self.myStrongBlock  = weakBlock;NSLog(@"%@", self.myStrongBlock);//打印结果//<__NSStackBlock__: 0x7ffee8ed5838>//<__NSMallocBlock__: 0x6000037e8db0>//<__NSStackBlock__: 0x7ffee8ed5838>
int i = 10;void (^block)() = ^{i;};// 因为block为strong类型,且捕获了外部变量,所以赋值时,自动进行了copy// <__NSMallocBlock__: 0x100206920>NSLog(@"%@", block);

对于作为参数传递的block,其类型是什么呢?

int i = 10;void (^block)() = ^{i;};__weak void (^weakBlock)() = ^{i;};void (^stackBlock)() = ^{};// ARC情况下// 创建时,都会在栈中// <__NSStackBlock__: 0x7fff5fbff730>NSLog(@"%@", ^{i;});// 因为block为strong类型,且捕获了外部变量,所以赋值时,自动进行了copy// <__NSMallocBlock__: 0x100206920>NSLog(@"%@", block);// 如果是weak类型的block,依然不会自动进行copy// <__NSStackBlock__: 0x7fff5fbff728>NSLog(@"%@", weakBlock);// 如果block是strong类型,并且没有捕获外部变量,那么就会转换成__NSGlobalBlock__// <__NSGlobalBlock__: 0x100001110>NSLog(@"%@", stackBlock);[self callBack:weakBlock];[self callBack:block];[self callBack:stackBlock];- (id)callBack:(void (^)(void))callBack{
     NSLog(@"%@", callBack);
    return callBack;}//结果
 //<__NSStackBlock__: 0x7ffee2572838>//<__NSMallocBlock__: 0x600002e881e0>// <__NSGlobalBlock__: 0x10d68c0f8>//<__NSStackBlock__: 0x7ffee2572838>//<__NSMallocBlock__: 0x600002e881e0>//<__NSGlobalBlock__: 0x10d68c0f8>

我们可以发现函数参数的block为什么类型,block在函数中就是什么类型。

二、autorelease内存管理
1、哪些对象是autorelease管理的?

1)enumerateObjectsUsingBlock中的对象

    [NSArray array] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {//自动缓存池
    }

2)__autoreleasing 修饰的对象

id obj = [NSObject new];id __autoreleasing o = obj;

3)array、dictiongnary、stringWithString等非init或者new方法生成的对象

int main(int argc, char * argv[]) {NSMutableArray *array = [NSMutableArray array];NSMutableArray *array1 = [NSMutableArray arrayWithCapacity:5];NSMutableDictionary *dict = [NSMutableDictionary dictionary];NSMutableString *str = [NSMutableString stringWithString:@"dsdsds"];

以上类型实验结果:

int main(int argc, char * argv[]) {
      id obj = [NSObject new];
      id __autoreleasing o = obj;
      id __autoreleasing o1 = obj;
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:5];
    [array addObject:@"0"];
    [array addObject:@"1"];
    [array addObject:@"2"];
    [array addObject:@"3"];
    [array addObject:@"4"];
    [array addObject:@"5"];
    [array addObject:@"6"];
    NSMutableArray *array1 = [NSMutableArray array];
    [array1 addObject:@"11"];
    [array1 addObject:@"12"];
    [array1 addObject:@"13"];
    [array1 addObject:@"14"];
    [array1 addObject:@"15"];
    [array1 addObject:@"16"];
    [array1 enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        id __autoreleasing o = obj;
    }];
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:@"1" forKey:@"1"];
    NSMutableString *str = [NSMutableString stringWithString:@"dsdsds"];//   _objc_autoreleasePoolPrint()}//在armv7上、使用_objc_autoreleasePoolPrint()调试打印结果 (lldb) po _objc_autoreleasePoolPrint()objc[96185]: ##############
objc[96185]: AUTORELEASE POOLS for thread 0x20d080objc[96185]: 6 releases pending.objc[96185]: [0x7e115000]  ................  PAGE  (hot) (cold)objc[96185]: [0x7e115028]        0x7be71ca0  NSObject
objc[96185]: [0x7e11502c]        0x7be71ca0  NSObject
objc[96185]: [0x7e115030]        0x7c470560  __NSArrayM
objc[96185]: [0x7e115034]        0x7be723b0  __NSArrayM
objc[96185]: [0x7e115038]        0x7c170b80  __NSDictionaryM
objc[96185]: [0x7e11503c]        0x7be72540  __NSCFString
objc[96185]: ##############0x0a5c2500//在arm64的手机上、使用_objc_autoreleasePoolPrint()调试打印结果 (lldb) po _objc_autoreleasePoolPrint()objc[96400]: ##############
objc[96400]: AUTORELEASE POOLS for thread 0x1151d75c0objc[96400]: 5 releases pending.objc[96400]: [0x7fae43000000]  ................  PAGE  (hot) (cold)objc[96400]: [0x7fae43000038]    0x600003a6c840  __NSArrayI//系统创建对象objc[96400]: [0x7fae43000040]    0x600000c358b0  __NSSetI//系统创建对象objc[96400]: [0x7fae43000048]    0x600002d380d0  NSObject
objc[96400]: [0x7fae43000050]    0x600002d380d0  NSObject
objc[96400]: [0x7fae43000058]    0x6000021649f0  __NSArrayM
objc[96400]: ##############0xe0675b6edaa1003f(lldb) po 0x6000021649f0<__NSArrayM 0x600001435d70>(0,1,2,3,4,5,6)

注意:这里面的实验结果不一样,在arm64上、array、dictiongnary、stringWithString等方法生成的对象,在自动缓存池中只能看见第一个对象,而armv7的机型上,可以看见所有的,不知这里是什么原因,有知道的欢迎告诉我

两个常用的调试命令

//打印自动缓存池对象_objc_autoreleasePoolPrint()//打印引用计数_objc_rootRetainCount(obj)
2、autoreleasePool什么时候创建的,里面的对象又是什么时候释放的?

1)系统通过runloop创建的autoreleasePool
runloop 可以说是iOS 系统的灵魂。内存管理/UI 刷新/触摸事件这些功能都需要 runloop 去管理和实现。runloop是通过线程创建的,和线程保持一对一的关系,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。

runloop和autoreleasePool又是什么关系呢?对象又是什么时候释放的?

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。

第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

验证结果:

int main(int argc, char * argv[]) {
    id obj = [NSObject new];
    id __autoreleasing o = obj;
    id __autoreleasing o1 = obj;
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:5];
    [array addObject:@"0"];
    [array addObject:@"1"];
    [array addObject:@"2"];
    [array addObject:@"3"];
    [array addObject:@"4"];
    [array addObject:@"5"];
    [array addObject:@"6"];//   _objc_autoreleasePoolPrint()}//_objc_autoreleasePoolPrint调试打印结果(lldb) po _objc_autoreleasePoolPrint()objc[99121]: ##############
objc[99121]: AUTORELEASE POOLS for thread 0x107b0d5c0objc[99121]: 5 releases pending.objc[99121]: [0x7f93b2002000]  ................  PAGE  (hot) (cold)objc[99121]: [0x7f93b2002038]    0x6000000d66c0  __NSArrayI
objc[99121]: [0x7f93b2002040]    0x6000036b9680  __NSSetI
objc[99121]: [0x7f93b2002048]    0x600001780160  NSObject
objc[99121]: [0x7f93b2002050]    0x600001780160  NSObject
objc[99121]: [0x7f93b2002058]    0x600001bcd230  __NSArrayM
objc[99121]: ##############0x67c4279ea7c20079(lldb) po 0x600001bcd230<__NSArrayM 0x600001bcd230>(0,1,2,3,4,5,6)(lldb) po [NSThread currentThread]<NSThread: 0x6000000953c0>{number = 1, name = main}

2)手动autoreleasePool
我们可以通过@autoreleasepool {}方式手动创建autoreleasepool对象,那么这个对象什么时候释放呢?答案是除了autoreleasepool的大括号就释放了,我们可以看下下面的实验结果

int main(int argc, char * argv[]) {
 //1\.   _objc_autoreleasePoolPrint()   
    @autoreleasepool {
        id obj = [NSObject new];
        id __autoreleasing o = obj;
        id __autoreleasing o1 = obj;//2\.   _objc_autoreleasePoolPrint()
    }//3\.   _objc_autoreleasePoolPrint()}
 //1\.   _objc_autoreleasePoolPrint()  (lldb) po _objc_autoreleasePoolPrint()objc[1555]: ##############
objc[1555]: AUTORELEASE POOLS for thread 0x11331a5c0objc[1555]: 2 releases pending.0x2196ee78f1e100fdobjc[1555]: [0x7fc2a9802000]  ................  PAGE  (hot) (cold)objc[1555]: [0x7fc2a9802038]    0x600002dbb600  __NSArrayI
objc[1555]: [0x7fc2a9802040]    0x600001bd8a50  __NSSetI
objc[1555]: ############## //2\.   _objc_autoreleasePoolPrint()  (lldb) po _objc_autoreleasePoolPrint()objc[1555]: ##############
objc[1555]: AUTORELEASE POOLS for thread 0x11331a5c00x2196ee78f1e100fdobjc[1555]: 5 releases pending.objc[1555]: [0x7fc2a9802000]  ................  PAGE  (hot) (cold)objc[1555]: [0x7fc2a9802038]    0x600002dbb600  __NSArrayI
objc[1555]: [0x7fc2a9802040]    0x600001bd8a50  __NSSetI
objc[1555]: [0x7fc2a9802048]  ################  POOL 0x7fc2a9802048objc[1555]: [0x7fc2a9802050]    0x600003afc030  NSObject
objc[1555]: [0x7fc2a9802058]    0x600003afc030  NSObject
objc[1555]: ############## //3\.   _objc_autoreleasePoolPrint()  (lldb) po _objc_autoreleasePoolPrint()objc[1555]: ##############
objc[1555]: AUTORELEASE POOLS for thread 0x11331a5c00x2196ee78f1e100fdobjc[1555]: 2 releases pending.objc[1555]: [0x7fc2a9802000]  ................  PAGE  (hot) (cold)objc[1555]: [0x7fc2a9802038]    0x600002dbb600  __NSArrayI
objc[1555]: [0x7fc2a9802040]    0x600001bd8a50  __NSSetI
objc[1555]: ##############(lldb)

从上面1、2、3的结果可以看出,当对象出了autoreleasepool的大括号就释放了。

3、子线程的autoreleasepool对象的管理?
线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。所以在我们创建子线程的时候,如果没有获取runloop,那么也就没用通过runloop来创建autoreleasepool,那么我们的autorelease对象是怎么管理的,会不会存在内存泄漏呢?答案是否定的,当子线程有autoreleasepool的时候,autorelease对象通过其来管理,如果没有autoreleasepool,会通过调用 autoreleaseNoPage 方法,将对象添加到 AutoreleasePoolPage 的栈中,也就是说你不进行手动的内存管理,也不会内存泄漏啦!这部分我们可以看下 runtime中NSObject.mm的部分,有相关代码。

static inline id *autoreleaseFast(id obj){
    AutoreleasePoolPage *page = hotPage();
    if (page && !page->full()) {
        return page->add(obj);
    } else if (page) {
        return autoreleaseFullPage(obj, page);
    } else {
        //调用 autoreleaseNoPage 方法管理autorelease对象。
        return autoreleaseNoPage(obj);
    }}

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群: 519832104 不管你是小白还是大牛欢迎入驻,分享经验,讨论技术,大家一起交流学习成长!

另附上一份各好友收集的大厂面试题,需要iOS开发学习资料、面试真题,可以添加iOS开发进阶交流群,进群可自行下载!

iOS使用频率最高的内存管理有哪几种

三、weak对象内存管理

1.释放时机
在dealloc的时候,会将weak属性的值设置为nil

2.如何实现
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针,对于 weak 对象会放入一个 hash 表中,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。 当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
注:由于可能多个weak指针指向同一个对象,所以value为一个数组

weak 的实现原理可以概括以下三步:

1)初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
我们以下面这行代码为例:

代码清单1:示例代码

{
    id __weak obj1 = obj;}

当我们初始化一个weak变量时,runtime会调用objc_initWeak函数。这个函数在Clang中的声明如下:

id objc_initWeak(id *object, id value);

其具体实现如下:

id objc_initWeak(id *object, id value){
    *object = 0;
    return objc_storeWeak(object, value);}

示例代码轮换成编译器的模拟代码如下:

id obj1;objc_initWeak(&obj1, obj);

因此,这里所做的事是先将obj1初始化为0(nil),然后将obj1的地址及obj作为参数传递给objc_storeWeak函数。
objc_initWeak函数有一个前提条件:就是object必须是一个没有被注册为__weak对象的有效指针。而value则可以是null,或者指向一个有效的对象。

2)添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数。
objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

3)释放时,调用clearDeallocating函数。
clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

四、NSString内存管理
1.NSString内存的类型

NSString内存分为两种类型:

2.两种内存类型的创建时机。

生成一个NSString类型的字符串有三种方法:

 NSString *str1 = @"my string";
NSString *str2 = [NSString stringWithString:@"my string"];
NSString *str3 = [[NSString alloc] initWithString:@"my string"];NSString *str4 = [[NSString alloc]initWithFormat:@"my string"];

1)对于__NSCFConstantString
这种类型的字符串是常量字符串。该类型的字符串以字面量的方式创建,保存在字符串常量区,是在编译时创建的。

NSString *a = @"str";NSString *b = [[NSString alloc]init];NSString *c = [[NSString alloc]initWithString:@"str"];NSString *d = [NSString stringWithString:@"str"];NSLog(@"%@ : class = %@",a,NSStringFromClass([a class]));NSLog(@"%@ : class = %@",b,NSStringFromClass([b class]));NSLog(@"%@ : class = %@",c,NSStringFromClass([c class]));NSLog(@"%@ : class = %@",d,NSStringFromClass([d class]));//打印结果2019-06-23 19:23:13.240611+0800 BlockDemo[47229:789011] str : class = __NSCFConstantString2019-06-23 19:23:13.240764+0800 BlockDemo[47229:789011]  : class = __NSCFConstantString2019-06-23 19:23:13.240870+0800 BlockDemo[47229:789011] str : class = __NSCFConstantString2019-06-23 19:23:13.240957+0800 BlockDemo[47229:789011] str : class = __NSCFConstantString

2)对于__NSCFStringNSTaggedPointerString

对于不可以变NSString的测试结果:

NSString *e = [[NSString alloc]initWithFormat:@"str"];NSString *f = [NSString stringWithFormat:@"str"];NSString *g = [NSString stringWithFormat:@"123456789"];NSString *h = [NSString stringWithFormat:@"1234567890"];NSLog(@"%@ : class = %@",e,NSStringFromClass([e class]));NSLog(@"%@ : class = %@",f,NSStringFromClass([f class]));NSLog(@"%@ : class = %@",g,NSStringFromClass([g class]));NSLog(@"%@ : class = %@",h,NSStringFromClass([h class]));//打印结果2019-06-23 19:27:19.115212+0800 BlockDemo[48129:794364] str : class = NSTaggedPointerString2019-06-23 19:27:19.115286+0800 BlockDemo[48129:794364] str : class = NSTaggedPointerString2019-06-23 19:27:19.115388+0800 BlockDemo[48129:794364] 123456789 : class = NSTaggedPointerString2019-06-23 19:27:19.115476+0800 BlockDemo[48129:794364] 1234567890 : class = __NSCFString

对于可变的NSMutableString

NSMutableString *ms1 = [[NSMutableString alloc]init];NSMutableString *ms2 = [[NSMutableString alloc]initWithString:@"str"];NSMutableString *ms3 = [[NSMutableString alloc]initWithFormat:@"str"];NSMutableString *ms4 = [NSMutableString stringWithFormat:@"str"];NSMutableString *ms5 = [NSMutableString stringWithFormat:@"123456789"];NSMutableString *ms6 = [NSMutableString stringWithFormat:@"1234567890"];NSLog(@"%@ : class = %@",ms1,NSStringFromClass([ms1 class]));NSLog(@"%@ : class = %@",ms2,NSStringFromClass([ms2 class]));NSLog(@"%@ : class = %@",ms3,NSStringFromClass([ms3 class]));NSLog(@"%@ : class = %@",ms4,NSStringFromClass([ms4 class]));NSLog(@"%@ : class = %@",ms5,NSStringFromClass([ms5 class]));NSLog(@"%@ : class = %@",ms6,NSStringFromClass([ms6 class]));//打印结果2019-06-23 19:34:08.521931+0800 BlockDemo[49465:802590]  : class = __NSCFString2019-06-23 19:34:08.522058+0800 BlockDemo[49465:802590] str : class = __NSCFString2019-06-23 19:34:08.522131+0800 BlockDemo[49465:802590] str : class = __NSCFString2019-06-23 19:34:08.522196+0800 BlockDemo[49465:802590] str : class = __NSCFString2019-06-23 19:34:08.522281+0800 BlockDemo[49465:802590] 123456789 : class = __NSCFString2019-06-23 19:34:08.522372+0800 BlockDemo[49465:802590] 1234567890 : class = __NSCFString

从结果我们可以看出来NSMutableString都是分配在堆区,且是__NSCFString类型,NSString中Format相关方法也是都分配在堆区,但是会根据字符串的长度,区分为__NSCFString和NSTaggedPointerString两种。在分配堆区的这些变量,其实一部分是正常的对象,一部分变成autorelease对象,具体是哪些,我们可以使用_objc_autoreleasePoolPrint()打印出来,比如实例中的g、ms4、ms5、ms6。

到此,关于“iOS使用频率最高的内存管理有哪几种”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. iOS开发OC基础:OC的内存管理
  2. [iOS开发]内存管理中的命名规则

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

ios

上一篇:Spring怎么解决循环依赖的

下一篇:Django中的unittest应用是什么

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》