iOS编码规范

注释规范

文件头注释

文件头注释采用如下格式,该注释由xcode自动生成。如果你对其他人的原始代码作出重大的修改,请把你自己的名字添加到作者里面,并且填写相应的备注。

//
//  TestComment.m
//  TestVcard
//
//  Created by zhangyang on 2017/6/20.
//  Copyright © 2017年 myCompany. All rights reserved.
//

修改项目名称和公司名称的方法请参考下图。修改用户名请在系统偏好设置->用户账户中进行修改。


image.png

接口注释

每个接口、类别以及协议应辅以注释,以描述它的目的及与整个项目的关系。使用如下格式:

/**
 添加描述
 
 @author zhangyang
 @date 2017/06/20
 */

可以将该段注释放在代码段中,如下图,我设置了快捷键为cmc。注意添加完成后需要修改描述和日期。

变量和常量注释

下面几种情况下的常量和变量,都要添加注释说明,在上方添加注释。//和内容后面有一个空格。

  • 接口中定义的所有常量
  • 公有类的公有常量
  • 枚举类定义的所有枚举常量
  • 实体类的所有属性变量
// 用户名
@property (nonatomic, strong) NSString *userId;

//用户密码
@property (nonatomic, strong) NSString *userPwd;

方法注释

xcode 8已经集成了VVDocument,直接使用该注释格式。所有非系统方法都需要添加注释,包括在头文件和实现文件中。在相应的方法上方使用快捷键alt+command+/,即可生成该方法的注释:

/**
 注册用户

 @param userId 用户id
 @param userPwd 用户密码
 @return 0 成功  其他 失败
 */
- (NSInteger)registerUser:(NSString *)userId userPwd:(NSString *)userPwd;

注意,如果该方法所属的文件是他人创建的,需要添加上用户和日期。如果该方法不是与文件在同一时间创建的,也需要添加时间:

/**
 注册用户

 @author zhangyang
 @date 2017/06/20
 @param userId 用户id
 @param userPwd 用户密码
 @return 0 成功  其他 失败
 */
- (NSInteger)registerUser:(NSString *)userId userPwd:(NSString *)userPwd;

代码组织分类

在函数分组和protocol/delegate实现中使用#pragma mark -来分类方法,要遵循以下一般结构:

#pragma mark - Lifecycle  
- (instancetype)init {}  
- (void)dealloc {}  
- (void)viewDidLoad {}  
- (void)viewWillAppear:(BOOL)animated {}  
- (void)didReceiveMemoryWarning {}  
#pragma mark - Custom Accessors  
- (void)setCustomProperty:(id)value {}  
- (id)customProperty {}  
#pragma mark - IBActions  
- (IBAction)submitData:(id)sender {}  
#pragma mark - Public  
- (void)publicMethod {}  
#pragma mark - Private  
- (void)privateMethod {}  
#pragma mark - Protocol conformance  
#pragma mark - UITextFieldDelegate  
#pragma mark - UITableViewDataSource  
#pragma mark - UITableViewDelegate  
#pragma mark - NSCopying  
- (id)copyWithZone:(NSZone *)zone {}  
#pragma mark - NSObject  
- (NSString *)description {} 

书写规范

缩进

缩进统一为4个空格

花括号书写

  • 左大括号前不换行
  • 左大括号后换行
  • 右大括号前换行
  • 如果右大括号是一个语句、函数体或类的终止,则右大括号后换行; 否则不换行

例如,如果右大括号后面是else或逗号,则不换行。

空格的使用

if、else、for、switch、while等逻辑关键字与后面的语句留一个空格隔开。

if (isSuccess) { 
    // TODO while booleanVariable is true
} else { 
    // TODO else
}

运算符两边各用一个空格隔开。

int result = a + b; //Good, = 和 + 两边各用一个空格隔开
int result=a+b; //Bad,=和+两边没用空格隔开
for (int i = 0; i < 10; i++);   //good
for (int i=0;i<10;i++);   //bad

属性定义括号与前后各有一个空格,指针的*号紧靠变量名。如下所示:

@property (strong, nonatomic) UIWindow *window;

行宽

尽量让你的代码保持在 80 列之内(或者100)。在xcode中的设置方式如下:


image.png

方法声明和定义

Tip
- + 和返回类型之间须使用一个空格,参数列表中只有参数之间可以有空格。

方法应该像这样:

- (void)doSomethingWithString:(NSString *)theString { 
    ...
}

星号前有空格。当写新的代码时,要与先前代码保持一致。

如果一行有非常多的参数,将每个参数单独拆成一行。如果使用多行,将每个参数前的冒号对齐。

- (void)doSomethingWith:(GTMFoo *)theFoo 
                   rect:(NSRect)theRect
               interval:(float)theInterval { 
    ...
}

当第一个关键字比其它的短时,保证下一行至少有 4 个空格的缩进。这样可以使关键字垂直对齐,而不是使用冒号对齐:

- (void)short:(GTMFoo *)theFoo 
    longKeyword:(NSRect)theRect 
    evenLongerKeyword:(float)theInterval { 
    ...
}

方法调用

Tip
方法调用应尽量保持与方法声明的格式一致。当格式的风格有多种选择时,新的代码要与已有代码保持一致。

调用时所有参数应该在同一行:

[myObject doFooWith:arg1 name:arg2 error:arg3];

或者每行一个参数,以冒号对齐:

[myObject doFooWith:arg1 
               name:arg2 
              error:arg3];

不要使用下面的缩进风格:

[myObject doFooWith:arg1 name:arg2 // some lines with >1 arg 
              error:arg3];

[myObject doFooWith:arg1 
               name:arg2 error:arg3];

[myObject doFooWith:arg1 
          name:arg2 // aligning keywords instead of colons 
          error:arg3];

方法定义与方法声明一样,当关键字的长度不足以以冒号对齐时,下一行都要以四个空格进行缩进。

[myObj short:arg1 
    longKeyword:arg2 
    evenLongerKeyword:arg3];

@public和 @private

Tip
@public和 @private访问修饰符应该以一个空格缩进。

与 C++ 中的 public, private以及 protected非常相似。

@interface MyClass : NSObject { 
 @public
  ... 
 @private 
  ...
}
@end

异常

Tip
每个 @标签应该有独立的一行,在 @与 {}之间需要有一个空格,@catch
与被捕捉到的异常对象的声明之间也要有一个空格。

如果决定使用 Objective-C 的异常,那么就按下面的格式。不过最好先看看 避免抛出异常 了解下为什么不要使用异常。

@try { 
    foo();
}
@catch (NSException *ex) { 
    bar(ex);
}
@finally {
    baz();
}

协议名

Tip
类型标识符和尖括号内的协议名之间,不能有任何空格。

这条规则适用于类声明、实例变量以及方法声明。例如:

@interface MyProtocoledClass : NSObject<NSWindowDelegate> { 
    @private id<MyFancyDelegate> delegate_;
}
- (void)setDelegate:(id<MyFancyDelegate>)aDelegate;
@end

块(闭包)

Tip
块(block)适合用在 target/selector 模式下创建回调方法时,
因为它使代码更易读。块中的代码应该缩进 4 个空格。

取决于块的长度,下列都是合理的风格准则:

  • 如果一行可以写完块,则没必要换行。
  • 如果不得不换行,关括号应与块声明的第一个字符对齐。
  • 块内的代码须按 4 空格缩进。
  • 如果块太长,比如超过 20 行,建议把它定义成一个局部变量,然后再使用该变量。
  • 如果块不带参数,^{ 之间无须空格。如果带有参数,^( 之间无须空格,但 ) { 之间须有一个空格。
// The entire block fits on one line.
[operation setCompletionBlock:^{ [self onOperationDone]; }];

// The block can be put on a new line, indented four spaces, with the
// closing brace aligned with the first character of the line on which
// block was declared.
[operation setCompletionBlock:^{ 
    [self.delegate newDataAvailable];
}];

// Using a block with a C API follows the same alignment and spacing
// rules as with Objective-C.
dispatch_async(fileIOQueue_, ^{ 
    NSString* path = [self sessionFilePath]; 
    if (path) { 
        // ... 
}});

// An example where the parameter wraps and the block declaration fits
// on the same line. Note the spacing of |^(SessionWindow *window) {|// compared to |^{| above.
[[SessionService sharedService] 
    loadWindowWithCompletionBlock:^(SessionWindow *window) { 
        if (window) { 
            [self windowDidLoad:window]; 
        } else { 
            [self errorLoadingWindow]; 
        } }];

// An example where the parameter wraps and the block declaration does
// not fit on the same line as the name.
[[SessionService sharedService] 
    loadWindowWithCompletionBlock:
        ^(SessionWindow *window) { 
            if (window) { 
                [self windowDidLoad:window]; 
            } else { 
                [self errorLoadingWindow]; 
            } 
        }];

// Large blocks can be declared out-of-line.
void (^largeBlock)(void) = ^{ 
    // ...
};
[operationQueue_ addOperationWithBlock:largeBlock];

命名规范

基本原则

清晰

既清晰又简洁的命名最好,但以清晰为主,不要用单词简写(非常常用除外,苹果列出的可以接受的简写:链接),尽量使用全称。命名应符合OC标准,让人一看就知道是什么意思,不要让人有疑问,使用英文而不是拼音。

一致性

做某件事的代码通常都叫这个名字,比如tag、setStringValue,那你也这么叫。

驼峰原则

大驼峰(UserNameLabel):每个单词首字母大写
小驼峰(userNameLabel):除第一个单词外,其它单词首字母大写

文件命名

Tip
文件名须反映出其实现了什么类 – 包括大小写。

文件的扩展名应该如下:

后缀 说明
.h C/C++/Objective-C 的头文件
.m Ojbective-C 实现文件
.mm Ojbective-C++ 的实现文件
.mm Ojbective-C++ 的实现文件
.cc 纯 C++ 的实现文件
.c 纯 C 的实现文件
  • 所有自定义的文件名称以项目工程开头命名,eg:“XP”、“ZJG”、“SZ”
  • 针对不同视图控制器,在末尾添加后缀,eg:
    • UIViewController 后缀添加“ViewController”
    • UIView 后缀添加“View”

类命名

类名(以及类别、协议名)应首字母大写,并以驼峰格式分割单词

  • 所有类名称以项目工程开头命名,eg:“XP”、“ZJG”、“SZ”
  • 针对不同视图控制器,在末尾添加后缀,eg:
    • UIViewController 后缀添加“ViewController”
    • UIView 后缀添加“View”
    • UIButton 后缀添加“Button”、“Btn”
    • UILabel 后缀添加“Label"

类别名

Tip
类别名应该有两三个字母的前缀以表示类别是项目的一部分
或者该类别是通用的。类别名应该包含它所扩展的类的名字。

比如我们要基于 NSString 创建一个用于解析的类别,我们将把类别放在一个名为 GTMNSString+Parsing.h 的文件中。类别本身命名为 GTMStringParsingAdditions (是的,我们知道类别名和文件名不一样,但是这个文件中可能存在多个不同的与解析有关类别)。类别中的方法应该以 gtm_myCategoryMethodOnAString: 为前缀以避免命名冲突,因为 Objective-C 只有一个名字空间。

类名与包含类别名的括号之间,应该以一个空格分隔。

变量命名

Tip
变量名应该以小写字母开头,并使用驼峰格式。

普通变量名

对于静态的属性(int 或指针),尽量为变量起一个描述性的名字。不要担心浪费列宽,因为让新的代码阅读者立即理解你的代码更重要。例如:

  • 错误的命名:
int w;
int nerr;
int nCompConns;
tix = [[NSMutableArray alloc] init];
obj = [someObject object];
p = [network port];
  • 正确的命名:
int numErrors;
int numCompletedConnections;
tickets = [[NSMutableArray alloc] init];
userInfo = [someObject object];
port = [network port];

类成员变量

  • 如果只是单纯的private变量,最好声明在implementation里.
  • 如果是类的public属性,就用property写在.h文件里
  • 如果自己内部需要setter和getter来实现一些东西,就在.m文件的类目里用property来声明

类成员变量(写在implementation里面),以下划线开头,使用单词全名按顺序拼接方式,属性不需要,系统会自动生成下划线开头的成员变量。

类成员是界面元素的命名

命名方式为变量含义+视图后缀,采用小驼峰方式书写。

@property (nonatomic, strong) UIViewController *loginViewController;
@property (nonatomic, strong) UIView *loginHeaderView;
@property (nonatomic, strong) UILabel *loginLabel;
@property (nonatomic, strong) UIButton *loginBtn;

常量

常量名(如宏定义、枚举、静态局部变量等)应该以小写字母 k 开头,使用驼峰格式分隔单词,如:kInvalidHandle,kWritePerm。

方法命名

方法名应遵守小驼峰原则,首字母小写,其他单词首字母大写,每个空格分割的名称以动词开头。执行性的方法应该以动词开头,小写字母开头,返回性的方法应该以返回的内容开头,但之前不要加get。

- (void)insertModel:(id)model atIndex:(NSUInteger)atIndex;
- (instancetype)arrayWithArray:(NSArray *)array;

Delegate命名

类的实例必须为回调方法的参数之一, 如

-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section

回调方法的参数只有类自己的情况,方法名要符合实际含义, 如:

-(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView

以类的名字开头(回调方法存在两个以上参数的情况)以表明此方法是属于哪个类的, 如:

-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

使用did和will通知Delegate已经发生的变化或将要发生的变化, 如:

-(NSIndexPath*)tableView:(UITableView*)tableView willSelectRowAtIndexPath:(NSIndexPath*)indexPath;
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath;

图片资源文件名

  • 模块+功能命名法(公共使用:common+功能)。公共模块主要包括统一的背景,导航条,标签,公共的按钮背景,公共的默认图等等;私有模块主要根据app的业务功能模块划分,比如用户中心,消息中心等。
  • 单词全拼,或者大家公认无歧义的缩写(如:nav,bg,btn等)

个人中心模块中我的消息按钮示例:personal_btn_my_message
公共模块搜索按钮示例:common_icon_search(或者common_search_icon)

用枚举表示状态、选项、状态码

项目中,用枚举来表示一系列的状态、选项和状态码。例如 iOS SDK 中表示 UICollectionView 滑动方向的枚举定义如下:

typedef NS_ENUM(NSInteger, UICollectionViewScrollDirection) {
    UICollectionViewScrollDirectionVertical,
    UICollectionViewScrollDirectionHorizontal
};

定义的枚举类型名称应以 2~3 个大写字母开头,而这通常与项目设置的类文件前缀相同,跟随其后的命名应采用驼峰命名法则,命名应准确表述枚举表示的意义,枚举中各个值都应以定义的枚举类型开头,其后跟随各个枚举值对应的状态、选项或者状态码。

对于需要以按位或操作来组合的枚举都应使用 NS_OPTIONS 宏来定义,例如 SDK 中:

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

这样定义的选项能够以 按位或操作符 来进行组合,枚举中每个值均可启用或者禁用某一选项,在使用时,可以使用 按位与操作符 来检测是否启用了某一选项,如下:

UIViewAutoresizing resizing = UIViewAutoresizingFlexibleWidth | 
UIViewAutoresizingFlexibleHeight;

if (resizing & UIViewAutoresizingFlexibleWidth) {
    // UIViewAutoresizingFlexibleWidth is set
}

另外,我们可能使用 switch 语句时,会在最后加上 default 分支,但是若用枚举定义状态机,则最好不要使用 default 分支,因为如果稍后再加了一种状态,那么编译器就会发出警告,提示新加入的状态并未在 switch 分支中处理。假如写上了 default 分支,那么它就会处理这个新状态,从而导致编译器不发出警告,用 NS_ENUM 定义其他枚举类型时也要注意此问题。例如在定义代表 UI 元素样式的枚举时,通常要确保 switch 语句能正确处理所有样式。

编程实践

Init方法

Init方法应该遵循Apple生成代码模板的命名规则,返回类型应该使用instancetype而不是id。

- (instancetype)init {  
  self = [super init];  
  if (self) {  
      // ...  
  }  
  return self;  
}  

类构造方法

当类构造方法被使用时,它应该返回类型是instancetype而不是id。这样确保编译器正确地推断结果类型。

@interface Airplane  
+ (instancetype)airplaneWithType:(RWTAirplaneType)type;  
@end  

CGRect函数

当访问CGRect里的x, y, width, 或 height时,应该使用CGGeometry函数而不是直接通过结构体来访问。
引用Apple的CGGeometry:

在这个参考文档中所有的函数,接受CGRect结构体作为输入,在计算它们结果时隐式地标准化这些
rectangles。因此,你的应用程序应该避免直接访问和修改保存在CGRect数据结构中的数据。相反,
使用这些函数来操纵rectangles和获取它们的特性。

应该:

CGRect frame = self.view.frame;  
CGFloat x = CGRectGetMinX(frame);  
CGFloat y = CGRectGetMinY(frame);  
CGFloat width = CGRectGetWidth(frame);  
CGFloat height = CGRectGetHeight(frame);  
CGRect frame = CGRectMake(0.0, 0.0, width, height);  

不应该:

CGRect frame = self.view.frame;  
CGFloat x = frame.origin.x;  
CGFloat y = frame.origin.y;  
CGFloat width = frame.size.width;  
CGFloat height = frame.size.height;  
CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size }; 

单例模式

单例对象应该使用线程安全模式来创建共享实例。

+ (instancetype)sharedInstance {  
  static id sharedInstance = nil;  
  static dispatch_once_t onceToken;  
  dispatch_once(&onceToken, ^{  
      sharedInstance = [[self alloc] init];  
  });  
  return sharedInstance;  
}  

这会防止possible and sometimes prolific crashes

Xcode工程

物理文件应该与Xcode工程文件保持同步来避免文件扩张。任何Xcode分组的创建应该在本地文件系统体现。代码不仅是根据类型来分组,而且还可以根据功能来分组,这样代码更加清晰。

日志规范

只在debug的时候输出日志, release的时候不输出。减少性能损耗和安全隐患。
在-Prefix.pch(pch全称是“precompiled header”,也就是预编译头文件,该文件里存放的工程中一些不常被修改的代码,比如常用的框架头文件)文件中添加

#ifdef DEBUG
#define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define DLog(...)
#endif

其中DLog在开发中根据项目进行命名,在实际使用日志的时候,都使用DLog,不使用NSLog。并且该自定义日志可以输出具体的类和行数。实际开发中,只需要用 DLog(...) 就可以在输出需要信息的同时, 还输出所在类、 函数(方法)名以及行数。可以用这种方法很快找到输出所在的位置。

参考:http://zh-google-styleguide.readthedocs.io/en/latest/google-objc-styleguide/contents/
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html#//apple_ref/doc/uid/10000146-SW1
http://www.csdn.net/article/2015-06-01/2824818-objective-c-style-guide/1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Object-C 开发代码规范概要Object-C是一门面向对象的动态编程语言,主要用于编写IOS和MAC应用程序...
    克鲁德李阅读 530评论 0 1
  • 命名 Bundle id命名: 规则:采用反域名命名规则,全部使用小写字母。一级包名为com,二级包名根据应用进行...
    Tippi阅读 1,614评论 0 2
  • 面试被问到公司编码规范问题,感觉有很多东西,但是不知道该怎么说出来,今天突然找到 李明杰 老师的一份编码规范。重新...
    Dombo_Y阅读 958评论 1 2
  • 一.代码命名 一般性原则 最好是既清晰又简短,但不要为简短而丧失清晰性。 例如:removeObject:AtIn...
    睡不完懒觉阅读 1,764评论 0 16
  • 在最东边的海的岸边 我望见 孤独的岛 我想那如果是你 那么我就是与你相拥而眠的大海 在最苍绿的山的山顶 我望见 冷...
    GAHZ阅读 201评论 0 1