轉(zhuǎn):raywenderlich.com Objective-C編碼規(guī)范

文章轉(zhuǎn)自這里, 我只是大自然的搬運工.

介紹

我們制定 Objective-C 編碼規(guī)范的原因是我們能夠在我們的書项戴,教程和初學者工具包的代碼保持優(yōu)雅和一致馆铁。即使我們有很多不同的作者來完成不同的書籍逼争。

這里編碼規(guī)范有可能與你看到的其他Objective-C編碼規(guī)范不同搬素,因為它主要是為了打印和web的易讀性

關于作者

這編碼規(guī)范的創(chuàng)建是由很多來自raywenderlich.com團隊成員在Nicholas Waynik的帶領下共同完成的六敬。團隊成員有:Soheil Moayedi Azarpour, Ricardo Rendon Cepeda, Tony Dahbura, Colin Eberhardt, Matt Galloway, Greg Heo, Matthijs Hollemans, Christopher LaPollo, Saul Mora, Andy Pereira, Mic Pringle, Pietro Rea, Cesare Rocchi, Marin Todorov, Nicholas WaynikRay Wenderlich

我們也非常感謝New York TimesRobots & Pencils'Objective-C編碼規(guī)范的作者锌蓄。這兩個編碼規(guī)范為本指南的創(chuàng)建提供很好的起點稠项。PS:一個都不認識 _!

背景

這里有些關于編碼風格Apple官方文檔涯雅,如果有些東西沒有提及,可以在以下文檔來查找更多細節(jié):

正文從這開始

語言

應該使用 US 英語
應該:

UIColor *myColor = [UIColor whiteColor];

不應該:

UIColor *myColour = [UIColor whiteColor];

代碼組織

在函數(shù)分組和 protocol/delegate 實現(xiàn)中使用 #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/Event Response
- (IBAction)submitData:(id)sender {}
- (void)someButtonDidPressed:(UIButton*)button

#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate

#pragma mark - Public
- (void)publicMethod {}

#pragma mark - Private
- (void)privateMethod {}

#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}

#pragma mark - NSObject
- (NSString *)description {}

空格

  • 縮進使用4個空格活逆,確保在Xcode偏好設置來設置。(raywenderlich.com使用2個空格)
  • 方法大括號和其他大括號(if/else/switch/while 等)總是在同一行語句打開但在新行中關閉乐疆。
    應該:
if (user.isHappy) {
    //Do something
} else {
    //Do something else
}

不應該:

if (user.isHappy)
{
  //Do something
}
else {
  //Do something else
}
  • 在方法之間應該空出有且只有一行划乖,這樣有利于在視覺上更清晰和更易于組織。在方法內(nèi)的使用空白進行功能分離挤土,但通常都抽離出來成為一個新方法琴庵。
  • 優(yōu)先使用 auto-synthesis。但如果有必要仰美,@synthesize@dynamic 應該在實現(xiàn)中每個都聲明新的一行迷殿。
  • 應該避免以冒號對齊的方式來調(diào)用方法。因為有時方法簽名可能有3個以上的冒號和冒號對齊會使代碼更加易讀咖杂。請不要這樣做庆寺,盡管冒號對齊的方法包含代碼塊,因為Xcode的對齊方式令它難以辨認诉字。
    應該:
// blocks are easily readable
[UIView animateWithDuration:1.0 animations:^{
  // something
} completion:^(BOOL finished) {
  // something
}];

不應該:

// colon-aligning makes the block indentation hard to read
[UIView animateWithDuration:1.0
                 animations:^{
                     // something
                 }
                 completion:^(BOOL finished) {
                     // something
                 }];

注釋

當需要注釋時懦尝,注釋應該用來解釋這段特殊代碼為什么要這樣做。任何被使用的注釋都必須保持最新或被刪除壤圃。

一般都避免使用塊注釋陵霉,因為代碼盡可能做到自解釋,只有當斷斷續(xù)續(xù)或幾行代碼時才需要注釋伍绳。例外:這不應用在生成文檔的注釋

命名

Apple命名規(guī)則盡可能堅持踊挠,特別是與這些相關的memory management rules (NARC)。
長的冲杀,描述性的方法和變量命名是好的效床。
應該:

UIButton *settingsButton;

不應該:

UIButton *setBut;

三個字符前綴應該經(jīng)常用在類和常量命名,但在 Core Data 的實體名中應被忽略权谁。對于官方的 raywenderlich.com 書剩檀、初學者工具包或教程,前綴 'RWT' 應該被使用旺芽。

常量應該使用駝峰式命名規(guī)則谨朝,所有的單詞首字母大寫和加上與類名有關的前綴卤妒。
應該:

static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3;

不應該:

static NSTimeInterval const fadetime = 1.7;

屬性也是使用駝峰式,但首單詞的首字母小寫字币。對屬性使用 auto-synthesis则披,而不是手動編寫 @synthesize 語句,除非你有一個好的理由洗出。
應該:

@property (copy, nonatomic) NSString *descriptiveVariableName;

不應該:

id varnm;

下劃線

當使用屬性時士复,實例變量應該使用 self. 來訪問和改變。這就意味著所有屬性將會視覺效果不同翩活,因為它們前面都有 self. 阱洪。

但有一個特例:在初始化方法里,實例變量(例如菠镇,_variableName)應該直接被使用以避免 getters/setters 潛在的副作用冗荸。

局部變量不應該包含下劃線。

方法

在方法簽名中利耍,應該在方法類型(-/+ 符號)之后有一個空格蚌本。在方法各個段之間應該也有一個空格(符合Apple的風格)。在參數(shù)之前應該包含一個具有描述性的關鍵字來描述參數(shù)隘梨。

"and" 這個詞的用法應該保留程癌。它不應該用于多個參數(shù)來說明,就像 initWithWidth:height 以下這個例子:
應該:

- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id)viewWithTag:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;

不應該:

-(void)setT:(NSString *)text i:(UIImage *)image;
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
- (id)taggedView:(NSInteger)tag;
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height;  // Never do this.

變量

變量盡量以描述性的方式來命名轴猎。單個字符的變量命名應該盡量避免嵌莉,除了在for()循環(huán)。

星號表示變量是指針捻脖。例如锐峭, NSString *text 既不是 NSString* text 也不是 NSString * text,除了一些特殊情況下常量可婶。

私有變量 應該盡可能代替實例變量的使用沿癞。盡管使用實例變量是一種有效的方式,但更偏向于使用屬性來保持代碼一致性扰肌。

通過使用 back 屬性(_variable抛寝,變量名前面有下劃線)直接訪問實例變量應該盡量避免熊杨,除了在初始化方法(init, initWithCoder:, 等…)曙旭,dealloc 方法和自定義的 settersgetters。想了解關于如何在初始化方法和dealloc 直接使用 Accessor 方法的更多信息晶府,查看這里
應該:

@interface RWTTutorial : NSObject

@property (copy, nonatomic) NSString *tutorialName;

@end

不應該:

@interface RWTTutorial : NSObject {
  NSString *tutorialName;
}

屬性特性

所有屬性特性應該顯式地列出來桂躏,有助于新手閱讀代碼。屬性特性的順序應該是 storage川陆、atomicity剂习,與在 Interface Builder 連接UI元素時自動生成代碼一致。
應該:

@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (copy, nonatomic) NSString *tutorialName;

不應該:

@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic) NSString *tutorialName;

點符號語法

點語法是一種很方便封裝訪問方法調(diào)用的方式。當你使用點語法時鳞绕,通過使用 gettersetter 方法失仁,屬性仍然被訪問或修改。想了解更多们何,閱讀這里
點語法應該總是被用來訪問和修改屬性萄焦,因為它使代碼更加簡潔。[]符號更偏向于用在其他例子冤竹。
應該:

NSInteger arrayCount = [self.array count];
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;

不應該:

NSInteger arrayCount = self.array.count;
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;

字面值

NSString, NSDictionary, NSArrayNSNumber的字面值應該在創(chuàng)建這些類的不可變實例時被使用拂封。請?zhí)貏e注意nil值不能傳入 NSArrayNSDictionary 字面值,因為這樣會導致 crash鹦蠕。
應該:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingStreetNumber = @10018;

不應該:

NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingStreetNumber = [NSNumber numberWithInteger:10018];

常量

常量是容易重復被使用和無需通過查找和代替就能快速修改值冒签。常量應該使用 static 來聲明而不是使用 #define,除非顯式地使用宏钟病。
應該:

static NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com";

static CGFloat const RWTImageThumbnailHeight = 50.0;

不應該:

#define CompanyName @"RayWenderlich.com"

#define thumbnailHeight 2

枚舉類型

當使用enum時萧恕,推薦使用新的固定基本類型規(guī)格,因為它有更強的類型檢查和代碼補全〉涤疲現(xiàn)在 SDK 有一個宏 NS_ENUM() 來幫助和鼓勵你使用固定的基本類型廊鸥。
例如:

typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {
  RWTLeftMenuTopItemMain,
  RWTLeftMenuTopItemShows,
  RWTLeftMenuTopItemSchedule
};

你也可以顯式地賦值(展示舊的 k-style 常量定義):

typedef NS_ENUM(NSInteger, RWTGlobalConstants) {
  RWTPinSizeMin = 1,
  RWTPinSizeMax = 5,
  RWTPinCountMin = 100,
  RWTPinCountMax = 500,
};

舊的 k-style 常量定義應該避免除非編寫 Core Foundation C 的代碼。
不應該:

enum GlobalConstants {
  kMaxPinSize = 5,
  kMaxPinCount = 500,
};

Case語句

大括號在case語句中并不是必須的辖所,除非編譯器強制要求惰说。當一個 case 語句包含多行代碼時,大括號應該加上缘回。

switch (condition) {
  case 1:
    // ...
    break;
  case 2: {
    // ...
    // Multi-line example using braces
    break;
  }
  case 3:
    // ...
    break;
  default: 
    // ...
    break;
}

有很多次吆视,當相同代碼被多個 cases 使用時,一個 fall-through 應該被使用酥宴。一個 fall-through 就是在 case 最后移除'break'語句啦吧,這樣就能夠允許執(zhí)行流程跳轉(zhuǎn)到下一個 case 值。為了代碼更加清晰拙寡,一個 fall-through 需要注釋一下授滓。

switch (condition) {
  case 1:
    // ** fall-through! **
  case 2:
    // code executed for values 1 and 2
    break;
  default: 
    // ...
    break;
}

當在switch使用枚舉類型時,default 是不需要的肆糕。例如:

RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;

switch (menuType) {
  case RWTLeftMenuTopItemMain:
    // ...
    break;
  case RWTLeftMenuTopItemShows:
    // ...
    break;
  case RWTLeftMenuTopItemSchedule:
    // ...
    break;
}

私有屬性

私有屬性應該在類的實現(xiàn)文件中的類擴展(匿名分類)中聲明般堆,命名分類(比如RWTPrivate或private)應該從不使用除非是擴展其他類。匿名分類應該通過使用+Private.h文件的命名規(guī)則暴露給測試诚啃。
例如:

@interface RWTDetailViewController ()

@property (strong, nonatomic) GADBannerView *googleAdView;
@property (strong, nonatomic) ADBannerView *iAdView;
@property (strong, nonatomic) UIWebView *adXWebView;

@end

布爾值

Objective-C 使用 YESNO淮摔。因為 truefalse 應該只在CoreFoundation,C或C++代碼使用始赎。既然 nil 解析成 NO和橙,所以沒有必要在條件語句比較仔燕。不要拿某樣東西直接與 YES 比較,因為 YES 被定義為1和一個 BOOL 能被設置為8位魔招。

這是為了在不同文件保持一致性和在視覺上更加簡潔而考慮晰搀。
應該:

if (someObject) {}
if (![anotherObject boolValue]) {}

不應該:

if (someObject == nil) {}
if ([anotherObject boolValue] == NO) {}
if (isAwesome == YES) {} // Never do this.
if (isAwesome == true) {} // Never do this.

如果 BOOL 屬性的名字是一個形容詞,屬性就能忽略"is"前綴办斑,但要指定get訪問器的慣用名稱厕隧。例如:

@property (assign, getter=isEditable) BOOL editable;

文字和例子從這里引用Cocoa Naming Guidelines

條件語句

條件語句主體為了防止出錯應該使用大括號包圍,即使條件語句主體能夠不用大括號編寫(如俄周,只用一行代碼)吁讨。這些錯誤包括添加第二行代碼和期望它成為if語句;還有峦朗,even more dangerous defect可能發(fā)生在if語句里面一行代碼被注釋了建丧,然后下一行代碼不知不覺地成為 if 語句的一部分。除此之外波势,這種風格與其他條件語句的風格保持一致翎朱,所以更加容易閱讀。
應該:

if (!error) {
  return success;
}

不應該:

if (!error) 
  return success;

if (!error) return success;

三元操作符

當需要提高代碼的清晰性和簡潔性時尺铣,三元操作符?:才會使用拴曲。單個條件求值常常需要它。多個條件求值時凛忿,如果使用if語句或重構成實例變量時澈灼,代碼會更加易讀。一般來說店溢,最好使用三元操作符是在根據(jù)條件來賦值的情況下叁熔。

Non-boolean 的變量與某東西比較,加上括號()會提高可讀性床牧。如果被比較的變量是 boolean 類型荣回,那么就不需要括號。
應該:

NSInteger value = 5;
result = (value != 0) ? x : y;

BOOL isHorizontal = YES;
result = isHorizontal ? x : y;

不應該:

result = a > b ? x = c > d ? c : d : y;

Init方法

Init方法應該遵循Apple生成代碼模板的命名規(guī)則戈咳。返回類型應該使用 instancetype 而不是 id

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

查看更多 instancetype 的信息, 請查看NSHipster.com

類構造方法

當類構造方法被使用時心软,它應該返回類型是 instancetype 而不是 id。這樣確保編譯器正確地推斷結果類型著蛙。

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

查看更多 instancetype 的信息, 請查看NSHipster.com

CGRect函數(shù)

當訪問 CGRect 里的 x, y, widthheight 時删铃,應該使用 CGGeometry 函數(shù)而不是直接通過結構體來訪問。引用Apple的CGGeometry:

在這個參考文檔中所有的函數(shù)册踩,接受CGRect結構體作為輸入泳姐,在計算它們結果時隱式地標準化這些rectangles效拭。因此暂吉,你的應用程序應該避免直接訪問和修改保存在CGRect數(shù)據(jù)結構中的數(shù)據(jù)胖秒。相反,使用這些函數(shù)來操縱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 };

黃金路徑

當使用條件語句編碼時阎肝,左手邊的代碼應該是"golden" 或 "happy"路徑。也就是不要嵌套 if 語句肮街,多個返回語句也是OK风题。
應該:

- (void)someMethod {
  if (![someOther boolValue]) {
    return;
  }

  //Do something important
}

不應該:

- (void)someMethod {
  if ([someOther boolValue]) {
    //Do something important
  }
}

錯誤處理

當方法通過引用來返回一個錯誤參數(shù),判斷返回值而不是錯誤變量嫉父。
應該:

NSError *error;
if (![self trySomethingWithError:&error]) {
  // Handle Error
}

不應該:

NSError *error;
[self trySomethingWithError:&error];
if (error) {
  // Handle Error
}

在成功的情況下沛硅,有些Apple的APIs記錄垃圾值(garbage values)到錯誤參數(shù)(如果non-NULL),那么判斷錯誤值會導致 false 負值和 crash绕辖。

單例模式

單例對象應該使用線程安全模式來創(chuàng)建共享實例摇肌。

+ (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.

換行符

換行符是一個很重要的主題,因為它的風格指南主要為了打印和網(wǎng)上的可讀性仪际。
例如:

self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];

一行很長的代碼應該分成兩行代碼围小,下一行用兩個空格隔開。

self.productsRequest = [[SKProductsRequest alloc] 
  initWithProductIdentifiers:productIdentifiers];

Xcode工程

物理文件應該與Xcode工程文件保持同步來避免文件擴張树碱。任何Xcode分組的創(chuàng)建應該在文件系統(tǒng)的文件體現(xiàn)肯适。代碼不僅是根據(jù)類型來分組,而且還可以根據(jù)功能來分組成榜,這樣代碼更加清晰框舔。

盡可能在 targetBuild Settings 打開 Treat Warnings as Errors,和啟用以下 additional warnings赎婚。如果你需要忽略特殊的警告雨饺,使用 Clang's pragma feature

其他Objective-C編碼規(guī)范

如果這篇編碼規(guī)范不符合你的口味惑淳,可以查看其他的編碼規(guī)范:

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末额港,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子歧焦,更是在濱河造成了極大的恐慌移斩,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绢馍,死亡現(xiàn)場離奇詭異向瓷,居然都是意外死亡,警方通過查閱死者的電腦和手機舰涌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門猖任,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瓷耙,你說我怎么就攤上這事朱躺〉罄担” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵长搀,是天一觀的道長宇弛。 經(jīng)常有香客問我,道長源请,這世上最難降的妖魔是什么枪芒? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮谁尸,結果婚禮上舅踪,老公的妹妹穿的比我還像新娘。我一直安慰自己良蛮,他們只是感情好硫朦,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著背镇,像睡著了一般咬展。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瞒斩,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天破婆,我揣著相機與錄音,去河邊找鬼胸囱。 笑死祷舀,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的烹笔。 我是一名探鬼主播裳扯,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谤职!你這毒婦竟也來了饰豺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤允蜈,失蹤者是張志新(化名)和其女友劉穎冤吨,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饶套,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡漩蟆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了妓蛮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怠李。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捺癞,到底是詐尸還是另有隱情夷蚊,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布翘簇,位于F島的核電站,受9級特大地震影響儿倒,放射性物質(zhì)發(fā)生泄漏版保。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一夫否、第九天 我趴在偏房一處隱蔽的房頂上張望彻犁。 院中可真熱鬧,春花似錦凰慈、人聲如沸汞幢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽森篷。三九已至,卻和暖如春豺型,著一層夾襖步出監(jiān)牢的瞬間仲智,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工姻氨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钓辆,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓肴焊,卻偏偏與公主長得像前联,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子娶眷,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容