zoukankan      html  css  js  c++  java
  • 转向ARC的说明

    转自http://www.cocoachina.com/applenews/devnews/2013/1209/7497.html

    ARC是一个编译器特征,它提供了对OC对象自动管理内存。ARC让开发者专注于感兴趣的代码和对象的关系,而不用考虑对象的retain和release。
     
     
    概要
    ARC在编译时期添加代码,保证对象可用。概念上说,ARC遵循手动引用计数的规则,替开发者,在编译时期添加合适的代码。
     
    Xcode4.2(Mac OS 10.6、10.7和iOS4和iOS5)支持ARC,弱引用在10.6和iOS4上不支持。
     
    Xcode提供了一个工具:自动机械得转化为ARC(比如移除retain和release的调用),并帮助开发者解决不能自动迁移的问题。迁移工具将所有文件转化成ARC,开发者也可以对单个文件实施ARC,方便于开发者对某些文件手动引用计数。
     
    ARC概要
    ARC评估了对象的生命周期,并自动插入合适的代码在编译时期,从而代替开发者不得不考虑何时需要retain、release和autolease。同编译器替开发者,生成合适的dealloc方法。一般来说,当开发者需要在手动引用计数和ARC配合使用时候,使用传统ARC的Cocoa命名惯例是很重要的。
     
    一个正确的完整的Person类如下:
     
    1. @interface Person : NSObject   
    2. @property NSString *firstName;   
    3. @property NSString *lastName;   
    4. @property NSNumber *yearOfBirth;   
    5. @property Person *spouse;   
    6. @end   
    7.     
    8. @implementation Person   
    9. @end   
     
    对象的属性默认是strong;strong属性在 “ARC Introduces New Lifetime Qualifiers.”有描述
     
    1. - (void)contrived {   
    2.     Person *aPerson = [[Person alloc] init];   
    3.     [aPerson setFirstName:@"William"];   
    4.     [aPerson setLastName:@"Dudney"];   
    5.     [aPerson setYearOfBirth:[[NSNumber alloc] initWithInteger:2011]];   
    6.     NSLog(@"aPerson: %@", aPerson);   
    7. }   
     
    ARC掌管了内存管理,所以Person和NSNumber不会泄露。
     
    开发者还可以安全得实现Person类的方法takeLastNameFrom:
     
    1. - (void)takeLastNameFrom:(Person *)person {   
    2.     NSString *oldLastname = [self lastName];   
    3.     [self setLastName:[person lastName]];   
    4.     NSLog(@"Lastname changed from %@ to %@", oldLastname, [self lastName]);   
    5. }   
     
    ARC确保oldLastName在NSLog之前不会被销毁。
     
    ARC执行了新的规则
    ARC执行了一些新的规则,是其他编译模式没有的。这个规则是:致力于提供一个安全可靠的内存管理模式。在某些情况下,他们只是执行最佳实践;某些情况,他们简化你的代码或者处理那没明显的不需要内存问题。如果你违反这些规则,立即得到一个编译错误,而不会在运行显示一个微妙的bug。
     
    ● 开发者不能显示调用dealloc;不能实现和调用retain、release、retainCount和autorelease。
    禁止使用@selector(retain),@selector(release)等等。
    开发者仍可以实现dealloc方法,如果你想管理资源而不是变量。开发者不能release变量,但是可以调用[systemClassInstance setDelegate:nil]在系统类上,或者其他不是用ARC编译的代码上。
    ARC中自定义的dealloc方法,不需要调用[super dealloc](其实这样做就会导致编译错误),编译器会强制自动链接到父类。
    开发者仍可以对Core Foundation-style对象,使用CFRetain,CFRelease和其他相关方法。
     
    ● 不能使用NSAllocateObject或者NSDeallocateObject。开发者创建对象使用alloc,运行时环境负责销毁对象。
     
    ● 在C数据结构中,不能使用对象指针。可以使用OC类来代替C的struct
     
    ● id和void*没有自动转换.
    开发者必须使用特殊的类型转化.开发者需要通过函数参数传递,在OC对象和Core Foundation之间做类型转换。
     
    ● 开发者不能使用NSAutoreleasePool对象。ARC下使用@autoreleasepool,它比NSAtuoreleasePool更有效率。
     
    ● 开发者不能使用内存zones。不用再使用NSZone了。他们已经被现代的OC运行环境给忽略了。
     
    为了配合手动引用计数,ARC的方法命名有限制:
    ● 访问器方法不能已new开头,反过来就是:开发者不能声明一个已new开头的属性,除非你给你指定一个getter
     
    1. // 不正确:   
    2. @property NSString *newTitle;   
    3.     
    4. // 正确:   
    5. @property (getter=theNewTitle) NSString *newTitle;   
     
    ARC引入了新的Lifetime修饰符。
     
    ARC引入几个新的修饰符和弱(weak)引用。弱引用不会延伸到对象生命周期,并自动设置为nil,当该对象没有任何的强引用的时候。
     
    开发者需要使用这些修饰来管理对象图。通常ARC不对循环引用做警告。谨慎得使用弱引用可以保证不会循环引用。
     
    Property属性
    weak和strong关键字作为新的property属性被引入,例如:
     
    1. // The following declaration is a synonym for: @property(retain) MyClass *myObject;   
    2. @property(strong) MyClass *myObject;   
    3.     
    4. // The following declaration is similar to "@property(assign) MyClass *myObject;"   
    5. // except that if the MyClass instance is deallocated,   
    6. // the property value is set to nil instead of remaining as a dangling pointer.   
    7. @property(weak) MyClass *myObject;   
     
    ARC下,strong是默认property属性
     
    变量的修饰符
    开发者使用下面得lifetime修饰。
    __strong
    __weak
    __unsafe_unretained
    __autoreleasing
     
    ● 默认是__strong。只要对象还有强引用,该对象“活着”。
    ● __weak不保留对象,只是简单引用。weak对象将被设置nil,当对象没有任何强引用的时候。
    ● __unsafe_unretained 不保留对象,只是简单引用。但是不设置为nil,当对象没有任何强引用得时候。如果对象被销毁,__unsafe_unretained的对象将会野指针。
    ● __autoreleasing 用于标识id*的引用参数,或者需要自动释放的返回的对象。
     
    开发者需要正确修饰变量。使用下面的格式来修饰变量声明。
     
    类名*  修饰  变量名
    例如:
     
    1. MyClass * __weak myWeakReference;   
    2. MyClass * __unsafe_unretained myUnsafeReference;   
     
    其他的变种在技术上是不正确的,但可能可以通过编译。了解更多http://cdecl.org/
     
    在栈上小心使用__weak。考虑下面的代码:
    1. NSString * __weak string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];   
    2. NSLog(@"string: %@", string);   
    尽管string在初始化后被使用了。但是,由于在赋值的时候没有强引用;因此它将立即被销毁。
     
    开发者同样需要注意传递引用的地方,例如下面的代码:
    1. NSError *error;   
    2. BOOL OK = [myObject performOperationWithError:&error];   
    3. if (!OK) {   
    4.     // Report the error.   
    5.     // ...   
     
    然而,这种错误是隐含的。
    1. NSError * __strong e;   
    并且声明的方法可能是下面这样:
    1. -(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;   
    编译器将重写代码:
    1. NSError * __strong error;   
    2. NSError * __autoreleasing tmp = error;   
    3. BOOL OK = [myObject performOperationWithError:&tmp];   
    4. error = tmp;   
    5. if (!OK) {   
    6.     // Report the error.   
    7.     // ...   
    本地变量声明(__strong)和参数(__autoreleasing)导致编译器创建临时变量。当开发者对__strong对象取地址,将参数声明为id __strong*,就得到原始的指针。或者开发者可以将变量声明为__autoreleasing
     
    使用Lifetime修饰符,避免循环引用
     
    开发者可以通过lifetime修饰符来避免循环引用。例如,如果你有一个对象图(关于父-子继承)。并且父类需要引用子类,相反子类也要用。这时候,开发者把parent-to-child写做strong并把child-to-parent关系写作weak。其他情况可能有些麻烦,特别是调用block变量的时候。
     
    手动引用计数模式下,__block id x 这样的写法,不会对x进行retain。在ARC模式,__block id x;默认retain x(就像其他变量一样)。如果想在ARC模式下,得到手动引用计数的效果,开发者可以使用__unsafe_unretained __block id x。顾名思义,__unsafe_unretained __block id x是危险的(因为他可能导致野指针)因此也不建议使用。好的解决方案是:使用__weak或者将__block的值设置为nil,来打断retain循环。
    下面的代码段展示了如何使用手动引用计数
    1. MyViewController *myController = [[MyViewController alloc] init…];   
    2. // ...   
    3. myController.completionHandler =  ^(NSInteger result) {   
    4.    [myController dismissViewControllerAnimated:YES completion:nil];   
    5. };   
    6. [self presentViewController:myController animated:YES completion:^{   
    7.    [myController release];   
    8. }];   
    正如说的那样,开发者可以使用__block修饰符并且设置myController变量为nil,在完成处理的时候。
    1. MyViewController * __block myController = [[MyViewController alloc] init…];   
    2. // ...   
    3. myController.completionHandler =  ^(NSInteger result) {   
    4.     [myController dismissViewControllerAnimated:YES completion:nil];   
    5.     myController = nil;   
    6. };   
    或者,你可以使用临时的__weak变量。下面是一个简单的实现
    1. MyViewController *myController = [[MyViewController alloc] init…];   
    2. // ...   
    3. MyViewController * __weak weakMyViewController = myController;   
    4. myController.completionHandler =  ^(NSInteger result) {   
    5.     [weakMyViewController dismissViewControllerAnimated:YES completion:nil];   
    6. };   
    对于non-trivial循环,开发者应该使用下面代码:
     
    1. MyViewController *myController = [[MyViewController alloc] init…];   
    2. // ...   
    3. MyViewController * __weak weakMyController = myController;   
    4. myController.completionHandler =  ^(NSInteger result) {   
    5.     MyViewController *strongMyController = weakMyController;   
    6.     if (strongMyController) {   
    7.         // ...   
    8.         [strongMyController dismissViewControllerAnimated:YES completion:nil];   
    9.         // ...   
    10.     }   
    11.     else {   
    12.         // Probably nothing...   
    13.     }   
    14. };   
     
    在某些情况,开发者使用__unsafe_unretained,如果类本身不是__weak修饰。然而,这样将变得不切合实际了,因为它可能很难或者不可能去验证:__unsafe_unretained的指针仍然可用并且指向某些变量。
     
    ARC使用一个新语句管理Autorelease Pools
    使用ARC,开发者不能直接使用NSAutoreleasePool来管理autorelease pools。而使用@autoreleasepool代替它。
    1. @autoreleasepool {   
    2.      // Code, such as a loop that creates a large number of temporary objects.   
    3. }   
    这个简单的结构允许编译器思考引用计数的状态。进入的时候,自动释放池被push。在正常退出的时候自动释放池配poped出来。为了配合现有代码,如果代码异常退出,自动释放池将不会pop出来。它比NSAutoreleasePool更有效率;因此建议开发者替换NSAtuoreleasePool。
     
    Patterns for Managing Outlets Become Consistent Across Platforms(略)
     
    栈变量被初始化为nil
     
    使用ARC,strong、weak和autoreleasing栈变量将不会显示初始化为nil,例如:
    1. - (void)myMethod {   
    2.     NSString *name;   
    3.     NSLog(@"name: %@", name);   
    4. }   
     
    将打印出null,而不是崩溃。
     
    使用编译器选项来开启和关闭ARC
     
    开发者使用-fobjc-arc 编译选项开启ARC,还可以对某一个文件使用ARC,便于在使用手动引用计数的文件中使用ARC。对于已经使用ARC的工程,仍可以指定一个文件来关闭ARC通过-fno-objc-arc编译选项。
     
    管理Toll-Free Bridging
     
    在多数的Cocoa程序中,开发者需要使用Core Foundaton-style对象,无论是从Core Foundation框架还是从Core foundation的其他框架比如Core Graphics。
     
    编译器不会自动管理Core foundation对象的生命周期。开发者必须根据COreFoundation的内存管理规则,使用CFRetain和CFRelease。
     
    如果开发者在OC和Core foundation两种对象做转换,需要告诉编译器对象的所有权。
     
    ● __bridge 不改变所有权的情况下,将OC和Core foundaton对象之间转换。
     
    ● __bridge_retained 或者 CFBridgingRetain 或者对象的所有权,将OC和Corefoundaton对象之间转换。开发者仍有责任将释放对象通过CFRelease。
     
    ● __bridge_transfer 或者CFBridgingRelease将一个非OC指针,转化为OC指针,ARC负责释放对象。
     
    例如,现有代码:
    1. - (void)logFirstNameOfPerson:(ABRecordRef)person {   
    2.     
    3.     NSString *name = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);   
    4.     NSLog(@"Person's first name: %@", name);   
    5.     [name release];   
    6. }   
     
    开发用下面的代码代替
    1. - (void)logFirstNameOfPerson:(ABRecordRef)person {   
    2.     
    3.     NSString *name = (NSString *)CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty));   
    4.     NSLog(@"Person's first name: %@", name);   
    5. }   
     
    编译器处理从COcoa方法返回的 CF 对象
     
    编译器知道返回Core foundaion类的OC的方法,遵循历史的规定。例如,编译器知道从CGColor方法返回的GCColor是不拥有的。开发者必须使用合适的类型去转化。例如:
    1. NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];   
    2. [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];   
     
    使用所有权关键字转化函数参数
     
    当开发者在OC和Core foundation的对象之间转化时,需要告诉编译器传递object的所有权。Core foundation对象所有权的规则在CoreFoundation的内存管理规则中。OC的所有权规则在Advanced Memory Management Programming Guide中。
     
    下面的代码段,数组传递给CGGradientCreateWithCorlors函数需要一个合适的转化。数组是arrayWitshObjects返回的,所以,不要将所有权传递给函数,因此使用__bridge
    1. NSArray *colors = <#An array of colors#>;   
    2. CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);  
    下面代码段实现了一个context,使用Core Foundation内存管理的方法。
    1. - (void)drawRect:(CGRect)rect {   
    2.     CGContextRef ctx = UIGraphicsGetCurrentContext();   
    3.     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();   
    4.     CGFloat locations[2] = {0.0, 1.0};   
    5.     NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];   
    6.     [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];   
    7.     CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);   
    8.     CGColorSpaceRelease(colorSpace);  // Release owned Core Foundation object.   
    9.     CGPoint startPoint = CGPointMake(0.0, 0.0);   
    10.     CGPoint endPoint = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));   
    11.     CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint,   
    12.                                 kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);   
    13.     CGGradientRelease(gradient);  // Release owned Core Foundation object.   
    14. }   
     
    将工程转化为ARC,遇到的常见问题
     
    当迁移现有的项目,你可能会遇到的各种问题。这里有一些共同的问题,共同解决方案。
     
    ● 不能调用retain,release或者autorelease,这是一种特性,甚至可以这么写:
     
    1. while ([x retainCount]) { [x release]; }   
     
     
    ● 不能调用dealloc
    通常,开发者在单例的实现或者替换一个对象的init方法的时候调用dealloc。对于单例模式,使用共享实例模式。在init方法中,你不用调用dealloc,因为对象会被释放,当重写self的时候。
     
    ● 不能使用NSAutoreleasePool对象
     
    表格@autoreleasepool{}代替NSAutoreleasePool.这将强制一个block处于一个自动释放池中。它比NSAutoreleaePool快6倍。@autoreleasepool也在在非ARC模式下工作。
     
    ● ARC需要开发者对[super init]赋值给self在init方法中。
     
    下面代码是不可行的
    1. [super init];   
    简单的修正是:
    1. self = [super init];   
    根号的修复是这样的
     
    1. self = [super init];   
    2. if (self) {   
    3.    ...   
     
    ● 不能实现retain和release方法。
    实现自定义的retain和release方法打破弱指针。提供了几个常用的 “实现自定义”的原因。
     
    1.性能
    不要这么做,NSObject的retain和release已经很快了。如果你发现仍有问题,请提出这个bug
     
    2.实现一个自定义的弱指针系统
    使用__weak代替
     
    3.实现单例
    使用shared instance pattern代替。或者使用类来代替方法,避免分配对象。
     
    ● assigned 将变成strong
     
    在ARC之前,变量的assigned的不会延伸到对象的生命周期。为了让property变成强引用,开发者通常实例化或者synthesized访问器方法,里面是内存管理方法。相比之下,你可以这样实现访问器方法:
    1. @interface MyClass : Superclass {   
    2.     id thing; // Weak reference.   
    3. }   
    4. // ...   
    5. @end   
    6.     
    7. @implementation MyClass   
    8. - (id)thing {   
    9.     return thing;   
    10. }   
    11. - (void)setThing:(id)newThing {   
    12.     thing = newThing;   
    13. }   
    14. // ...   
    15. @end   
    ARC下,实例中的变量默认是strong引用,assigning一个对象给实例中的变量延伸到对象的生命周期。迁移工具不能决定一个实例变量即将weak。保持相同的行为之前,你必须标记实例变量是weak,或声明它的property
     
    1. @interface MyClass : Superclass {   
    2.     id __weak thing;   
    3. }   
    4. // ...   
    5. @end   
    6.     
    7. @implementation MyClass   
    8. - (id)thing {   
    9.     return thing;   
    10. }   
    11. - (void)setThing:(id)newThing {   
    12.     thing = newThing;   
    13. }   
    14. // ...   
    15. @end   
     
    或者
     
    1. @interface MyClass : Superclass   
    2. @property (weak) id thing;   
    3. // ...   
    4. @end   
    5.     
    6. @implementation MyClass   
    7. @synthesize thing;   
    8. // ...   
    9. @end   
     
    ● 不能使用strong ids在c的数据结构
    例如,下面的c结构将不能编译过
    1. struct X { id x; float y; };   
    这是因为x默认是strong retain,但是,在正常运行情况下,编译器不能安全的合成所有需要的代码。比如:如果你传递一个指针给x或者y后,执行了free,每一个id将被release掉,在struct被free之前。编译器不能可靠的做这些。所以strong ids在结构,在ARC中完全不允许。下面是一些解决方案:
    1.用OC类代替c结构。这应该是最好的解决办法。
     
    2.如果使用OC类是次要的方法(可能你想要一个高密度的结构数组),那么考虑使用void*代替。这需要使用显示的转化。
     
    3.把对象引用作为__unsafe_unretained。这种方法是半常见的模式,这样是有用的。
     
    1. struct x { NSString *S;  int X; } StaticArray[] = {   
    2.   @"foo", 42,   
    3.   @"bar, 97,   
    4. ...   
    5. };   
     
    开发者这样声明
    1. struct x { NSString * __unsafe_unretained S; int X; }   
    这可能是有问题的,如果对象可以被释放时指针是不安全的,但它是非常有用的东西,被称为是字符串常量。
     
    ● 不能直接将id和void*进行转化(包含Core Foundation类型)参考Managing Toll-Free Bridging
     
    经常遇到的问题
     
    我该怎样认识ARC?它在哪儿放置retains/releases。
     
    不要想什么地方调用retain/release,考虑程序本身算法吧。考虑"strong and weak"关系,对象的所有权关系和可能存在的循环引用。
     
    我还需要写dealloc方法么
    可能需要。因为ARC不会自动对 Core Foundation 对象,文件描述符等等进行malloc/free,,这些资源扔需要写dealloc方法。
     
    不不能release对象的变量,但是可以调用[self setDelegate:nil]在系统的类和其他不用ARC的代码。
     
    ARC种 不需要dealloc方法,或者[super dealloc];在运行时系统调用super代码。
     
    在ARC种,仍存在循环引用?
    是的。ARC自动retain/release并可能继承了循环引用的问题。幸运的是:迁移到ARC的代码很少泄露,因为无论properties是不是retain的,都被被声明为retain了。
     
    在ARC种blocks是如何工作的
    Block能正常工作,当你在栈上传递的时候,比如作为返回值。你无需block copy了。当你传递block在栈下面的时候,添加到arrayWithObjects等需要retain的地方,需要使用[^{ }copy]。
     
    有一件事情需要清除:NSString *__block myString是retain在ARC模式种,不可能是野指针。为了得到以前的行为,使用__block NSString *__unsafe_unretained myString 或者使用__block NSString *__weak myString.
     
    我可以在雪豹OS种开发中使用ARC么?
    不行。雪豹版本的Xcode4.2不支持ARC。
     
    ARC下,可以创建C语言的retain的指针数组么
    可以
     
    1. // Note calloc() to get zero-filled memory.   
    2. __strong SomeClass **dynamicArray = (__strong SomeClass **)calloc(entries, sizeof(SomeClass *));   
    3. for (int i = 0; i < entries; i++) {   
    4.      dynamicArray[i] = [[SomeClass alloc] init];   
    5. }   
    6.     
    7. // When you're done, set each entry to nil to tell ARC to release the object.   
    8. for (int i = 0; i < entries; i++) {   
    9.      dynamicArray[i] = nil;   
    10. }   
    11. free(dynamicArray);   
     
    下面是一些注意的地方:
    ● 你要写__strong SomeClass **在某些情况,默认情况是__autoreleasing SomeClass **.
    ● 开辟的内存必须是零填充
    ● 需要设置每一项为nil在释放array的时候(memset和bzero不好使的)
     
    ARC慢么?
    取决于你怎么测量,通常是不慢。编译器消除很多无关紧要的retain/release调用。投入很大的努力在加快OC下的运行环境。尤其是返回一个retian/autoreleased对象,ARC并不是真正将它放到自动释放池。
     
    有一件事需要清除:优化器不在debug模式下。所以想看到更多的retain/release的调用,使用-O0比-Os
     
    ARC在ObjC++模式下工作么?
    是的,甚至可以将strong/weakids在类和容器中。ARC编译器合成retain/release逻辑在拷贝构造函数和析构函数钟。
     
    哪些类不支持weak引用?
    下面的类不能创建弱引用:
    NSATSTypesetter, NSColorSpace, NSFont, NSMenuView, NSParagraphStyle, NSSimpleHorizontalTypesetter, and NSTextView.
     
    对于声明properties时,你应该使用retain代替weak。对于变量你应该使用__unsafe_unretained代替__weak.另外,你不能创建这些类的弱引用:NSHashTable, NSMapTable, or NSPointerArray 
     
    对NSCell和其他类使用NSCopyObject时候。
    没什么特别的。ARC负责的显示retain的场景。ARC模式下,所有的copy方法应该仅仅copy实例变量。
     
    我可以对某个文件不使用ARC么?
     
    是的。当开发者迁移工程到ARC时,对所有OC源文件设置为-fobjc-arc编译选项。开发者可以指定一个文件设置-fno-objc-arc编译选项。
     
    GC(Grabage Collection)在mac上过时了?
    Grabage Collection在10.8上已经过时,将来会重OS X中移除。ARC将是收推荐的替代技术。为了帮助迁移现有程序,ARC迁移工具在Xcode >=4.3 支持将GC迁移到ARC。
  • 相关阅读:
    正经学C#_循环[do while,while,for]:[c#入门经典]
    Vs 控件错位 右侧资源管理器文件夹点击也不管用,显示异常
    asp.net core 获取当前请求的url
    在实体对象中访问导航属性里的属性值出现异常“There is already an open DataReader associated with this Command which must be
    用orchard core和asp.net core 3.0 快速搭建博客,解决iis 部署https无法登录后台问题
    System.Data.Entity.Core.EntityCommandExecution The data reader is incompatible with the specified
    初探Java设计模式3:行为型模式(策略,观察者等)
    MySQL教程77-CROSS JOIN 交叉连接
    MySQL教程76-HAVING 过滤分组
    MySQL教程75-使用GROUP BY分组查询
  • 原文地址:https://www.cnblogs.com/longingforlife/p/3479722.html
Copyright © 2011-2022 走看看