iOS優(yōu)化---代碼規(guī)范

原則
  1. 長的席揽,描述性的方法和變量命名是好的汗盘。不要使用簡寫,除非是一些大家都知道的場景比如 VIP猾蒂。不要使用 bgView均唉,推薦使用 backgroundView
  2. 見名知意。含義清楚肚菠,做好不加注釋代碼自我表述能力強舔箭。(前提是代碼足夠規(guī)范)
  3. 不要過分追求技巧,降低代碼可讀性
  4. 刪除沒必要的代碼蚊逢。比如我們新建一個控制器层扶,里面會有一些不會用到的代碼,或者注釋起來的代碼烙荷,如果這些代碼不需要镜会,那就刪除它,留著偷懶嗎终抽?下次需要自己手寫
  5. 在方法內(nèi)部不要重復(fù)計算某個值戳表,適當(dāng)?shù)那闆r下可以將計算結(jié)果緩存起來
  6. 盡量減少單例的使用。
  7. 提供一個統(tǒng)一的數(shù)據(jù)管理入口昼伴,不管是 MVC匾旭、MVVM、MVP 模塊內(nèi)提供一個統(tǒng)一的數(shù)據(jù)管理入口會使得代碼變得更容易管理和維護亩码。
  8. 除了 .m 文件中方法季率,其他的地方"{"不需要另起一行。
- (void)getGooodsList
{
    // ...
}

- (void)doHomework
{
    if (self.hungry) {
        return;
    }
    if (self.thirsty) {
        return;
    }
    if (self.tired) {
        return;
    }
    papapa.then.over;
}
變量
  1. 一個變量最好只有一個作用描沟,切勿為了節(jié)省代碼行數(shù)飒泻,覺得一個變量可以做多個用途鞭光。(單一原則)
  2. 方法內(nèi)部如果有局部變量,那么局部變量應(yīng)該靠近在使用的地方泞遗,而不是全部在頂部聲明全部的局部變量惰许。
  3. 變量名必須使用駝峰格式
類,協(xié)議使用大駝峰:
HomePageViewController.h
<HeaderViewDelegate>
對象等局部變量使用小駝峰:
NSString *personName = @"";
NSUInteger totalCount = 0;
  1. 變量的名稱必須同時包含功能與類型
UIButton *addBtn //添加按鈕
UILabel *nameLbl //名字標(biāo)簽
NSString *addressStr//地址字符串
  1. 系統(tǒng)常用類作實例變量聲明時加入后綴

UIViewController ---- VC
UILabel ---- Lbl
NSMutableArray ----- Marray
NSMutableDictionary ---- Mdict

常量
  1. 常量以相關(guān)類名作為前綴
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;
不推薦這樣寫:
static const NSTimeInterval fadeOutTime = 0.4;
  1. 建議使用類型常量史辙,不建議使用#define預(yù)處理命令
    首先比較一下這兩種聲明常量的區(qū)別:
  • 預(yù)處理命令:簡單的文本替換汹买,不包括類型信息,并且可被任意修改
  • 類型常量:包括類型信息聊倔,并且可以設(shè)置其使用范圍晦毙,而且不可被修改。
    使用預(yù)處理雖然能達到替換文本的目的耙蔑,但是本身還是有局限性的:
  • 不具備類型信息见妒。
  • 可以被任意修改。
  1. 對外公開某個常量:
    如果我們需要發(fā)送通知甸陌,那么就需要在不同的地方拿到通知的“頻道”字符串(通知的名稱)须揣,那么顯然這個字符串是不能被輕易更改,而且可以在不同的地方獲取钱豁。這個時候就需要定義一個外界可見的字符串常量耻卡。
//頭文件
extern NSString *const ZOCCacheControllerDidClearCacheNotification;
//實現(xiàn)文件
static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";
static const CGFloat ZOCImageThumbnailHeight = 50.0f;
不推薦這樣寫:
#define CompanyName @"Apple Inc." 
#define magicNumber 42 
運算符
  1. 1元運算符和變量之間不需要空格。例如:++n
  2. 2元運算符與變量之間需要空格隔開牲尺。例如: containerWidth = 0.3 * Screen_Width
  3. 當(dāng)有多個運算符的時候需要使用括號來明確正確的順序卵酪,可讀性較好。例如: 2 << (1 + 2 * 3 - 4)
條件表達式
  1. 當(dāng)有條件過多秸谢、過長的時候需要換行凛澎,為了代碼看起來整齊些
//good
if (condition1() && 
    condition2() && 
    condition3() && 
    condition4()) {
  // Do something
}
//bad
if (condition1() && condition2() && condition3() && condition4()) { // Do something }
  1. 在一個代碼塊里面有個可能的情況時善于使用 return 來結(jié)束異常的情況。
- (void)doHomework
{
    if (self.hungry) {
        return;
    }
    if (self.thirsty) {
        return;
    }
    if (self.tired) {
        return;
    }
    papapa.then.over;
}
  1. 每個分支的實現(xiàn)都必須使用 {} 包含估蹄。
// bad
if (self.hungry) self.eat() 
// good
if (self.hungry) {
    self.eat()
}
  1. 條件判斷的時候應(yīng)該是變量在左,條件在右沫换。 if ( currentCursor == 2 ) { //... }
  2. switch 語句后面的每個分支都需要用大括號括起來臭蚁。
  3. switch 語句后面的 default 分支必須存在,除非是在對枚舉進行 switch讯赏。
類名
  1. 大寫駝峰式命名垮兑。每個單詞首字母大寫。比如「申請記錄控制器」
    ApplyRecordsViewController
  2. 每個類型的命名以該類型結(jié)尾漱挎。
  • ViewController:使用 ViewController 結(jié)尾系枪。例子:ApplyRecordsViewController
  • View:使用 View 結(jié)尾。例子:分界線:boundaryView
  • NSArray:使用 s 結(jié)尾磕谅。比如商品分類數(shù)據(jù)源私爷。categories
  • UITableViewCell:使用 Cell 結(jié)尾雾棺。比如 MyProfileCell
  • Protocol:使用 Delegate 或者 Datasource 結(jié)尾。比如 XQScanViewDelegate
  • Tool:工具類
  • 代理類:Delegate
  • Service 類:Service
類的注釋

有時候我們需要為我們創(chuàng)建的類設(shè)置一些注釋衬浑。我們可以在類的下面添加捌浩。

枚舉

枚舉的命名和類的命名相近。

typedef NS_ENUM(NSInteger, UIControlContentVerticalAlignment) {
    UIControlContentVerticalAlignmentCenter  = 0,
    UIControlContentVerticalAlignmentTop     = 1,
    UIControlContentVerticalAlignmentBottom  = 2,
    UIControlContentVerticalAlignmentFill    = 3,
};
  1. 全部大寫工秩,單詞與單詞之間用 _ 連接尸饺。
  2. 以 K 開頭。后面遵循大寫駝峰命名助币±颂「不帶參數(shù)」
#define HOME_PAGE_DID_SCROLL @"com.xq.home.page.tableview.did.scroll"
#define KHomePageDidScroll @"com.xq.home.page.tableview.did.scroll"
  1. 宏定義中如果包含表達式或變量,表達式和變量必須用小括號括起來眉菱。
#define MY_MIN(A, B)  ((A)>(B)?(B):(A))
屬性

書寫規(guī)則馋辈,基本上就是 @property 之后空一格,括號倍谜,里面的 線程修飾詞迈螟、內(nèi)存修飾詞、讀寫修飾詞尔崔,空一格 類 對象名稱 根據(jù)不同的場景選擇合適的修飾符答毫。

@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, assign, readonly) BOOL loading;   
@property (nonatomic, weak) id<#delegate#> delegate;
@property (nonatomic, copy) <#returnType#> (^<#Block#>)(<#parType#>);
  1. 屬性的關(guān)鍵字推薦按照 原子性,讀寫季春,內(nèi)存管理的順序排列
@property (nonatomic, readwrite, copy) NSString *name;
@property (nonatomic, readonly, copy) NSString *gender;
@property (nonatomic, readwrite, strong) UIView *headerView;
  1. 形容詞性的BOOL屬性的getter應(yīng)該加上is前綴
@property (assign, getter=isEditable) BOOL editable;
  1. 使用getter方法做懶加載
    實例化一個對象是需要耗費資源的洗搂,如果這個對象里的某個屬性的實例化要調(diào)用很多配置和計算贪嫂,就需要懶加載它旬陡,在使用它的前一刻對它進行實例化:
- (NSDateFormatter *)dateFormatter 
{
    if (!_dateFormatter) {
           _dateFormatter = [[NSDateFormatter alloc] init];
           NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
           [_dateFormatter setLocale:enUSPOSIXLocale];
           [_dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS"];
    } 
    return _dateFormatter;
}
但是也有對這種做法的爭議:getter方法可能會產(chǎn)生某些副作用犀变,例如如果它修改了全局變量沉噩,可能會產(chǎn)生難以排查的錯誤倚舀。
  1. 除了init和dealloc方法抱虐,建議都使用點語法訪問屬性
    使用點語法的好處:
    setter:1. setter會遵守內(nèi)存管理語義(strong, copy, weak)当凡。2. 通過在內(nèi)部設(shè)置斷點著觉,有助于調(diào)試bug逞刷。3. 可以過濾一些外部傳入的值嘉涌。4. 捕捉KVO通知。
    getter:1. 允許子類化夸浅。2. 通過在內(nèi)部設(shè)置斷點仑最,有助于調(diào)試bug。3. 實現(xiàn)懶加載(lazy initialization)帆喇。

注意 : 1. 懶加載的屬性警医,必須通過點語法來讀取數(shù)據(jù)。因為懶加載是通過重寫getter方法來初始化實例變量的坯钦,如果不通過屬性來讀取該實例變量预皇,那么這個實例變量就永遠(yuǎn)不會被初始化侈玄。
2 在init和dealloc方法里面使用點語法的后果是:因為沒有繞過setter和getter,在setter和getter里面可能會有很多其他的操作深啤。而且如果它的子類重載了它的setter和getter方法拗馒,那么就可能導(dǎo)致該子類調(diào)用其他的方法。

  1. 不要濫用點語法溯街,要區(qū)分好方法調(diào)用和屬性訪問
view.backgroundColor = [UIColor orangeColor]; 
[UIApplication sharedApplication].delegate;  

不推薦這樣寫:

[view setBackgroundColor:[UIColor orangeColor]]; 
UIApplication.sharedApplication.delegate; 
  1. 盡量使用不可變對象
    建議盡量把對外公布出來的屬性設(shè)置為只讀诱桂,在實現(xiàn)文件內(nèi)部設(shè)為讀寫。具體做法是:
  • 在頭文件中呈昔,設(shè)置對象屬性為readonly挥等。
  • 在實現(xiàn)文件中設(shè)置為readwrite。

這樣一來堤尾,在外部就只能讀取該數(shù)據(jù)肝劲,而不能修改它,使得這個類的實例所持有的數(shù)據(jù)更加安全郭宝。而且辞槐,對于集合類的對象,更應(yīng)該仔細(xì)考慮是否可以將其設(shè)為可變的粘室。
如果在公開部分只能設(shè)置其為只讀屬性榄檬,那么就在非公開部分存儲一個可變型。所以當(dāng)在外部獲取這個屬性時衔统,獲取的只是內(nèi)部可變型的一個不可變版本,例如:

在公共API中:

@interface EOCPerson : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSSet *friends //向外公開的不可變集合

- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName;
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;

@end
在這里鹿榜,我們將friends屬性設(shè)置為不可變的set。然后锦爵,提供了來增加和刪除這個set里的元素的公共接口舱殿。
在實現(xiàn)文件里:

@interface EOCPerson ()

@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;

@end

@implementation EOCPerson {
     NSMutableSet *_internalFriends;  //實現(xiàn)文件里的可變集合
}

- (NSSet*)friends 
{
     return [_internalFriends copy]; //get方法返回的永遠(yuǎn)是可變set的不可變型
}

- (void)addFriend:(EOCPerson*)person 
{
    [_internalFriends addObject:person]; //在外部增加集合元素的操作
    //do something when add element
}

- (void)removeFriend:(EOCPerson*)person 
{
    [_internalFriends removeObject:person]; //在外部移除元素的操作
    //do something when remove element
}

- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName 
{

     if ((self = [super init])) {
        _firstName = firstName;
        _lastName = lastName;
        _internalFriends = [NSMutableSet new];
    }
 return self;
}


我們可以看到,在實現(xiàn)文件里险掀,保存一個可變set來記錄外部的增刪操作沪袭。
這里最重要的代碼是:

- (NSSet*)friends 
{
   return [_internalFriends copy];
}
這個是friends屬性的獲取方法:它將當(dāng)前保存的可變set復(fù)制了一不可變的set并返回。因此迷郑,外部讀取到的set都將是不可變的版本枝恋。
單例

單例適合全局管理狀態(tài)或者事件的場景。一旦創(chuàng)建嗡害,對象的指針保存在靜態(tài)區(qū),單例對象在堆內(nèi)存中分配的內(nèi)存空間只有程序銷毀的時候才會釋放畦攘“悦茫基于這種特點,那么我們類似 UIApplication 對象知押,需要全局訪問唯一一個對象的情況才適合單例叹螟,或者訪問頻次較高的情況鹃骂。我們的功能模塊的生命周期肯定小于 App 的生命周期,如果多個單例對象的話罢绽,勢必 App 的開銷會很大畏线,糟糕的情況系統(tǒng)會殺死 App。如果覺得非要用單例比較好良价,那么注意需要在合適的場合 tearDown 掉寝殴。
單例的使用場景概括如下:

  • 控制資源的使用,通過線程同步來控制資源的并發(fā)訪問明垢。
  • 控制實例的產(chǎn)生蚣常,以達到節(jié)約資源的目的。
  • 控制數(shù)據(jù)的共享痊银,在不建立直接關(guān)聯(lián)的條件下抵蚊,讓多個不相關(guān)的進程或線程之間實現(xiàn)通信。
+ (instancetype)sharedInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //because has rewrited allocWithZone  use NULL avoid endless loop lol.
        _sharedInstance = [[super allocWithZone:NULL] init];
    });
    
    return _sharedInstance;
}

+ (id)allocWithZone:(struct _NSZone *)zone
{
    return [TestNSObject sharedInstance];
}

+ (instancetype)alloc
{
    return [TestNSObject sharedInstance];
}

- (id)copy
{
    return self;
}

- (id)mutableCopy
{
    return self;
}

- (id)copyWithZone:(struct _NSZone *)zone
{
    return self;
}

私有變量

推薦以 _ 開頭溯革,寫在 .m 文件中贞绳。例如 NSString * _somePrivateVariable

代理方法
  1. 類的實例必須作為方法的參數(shù)之一。
  2. 對于一些連續(xù)的狀態(tài)的致稀,可以加一些 will(將要)冈闭、did(已經(jīng))
  3. 以類的名稱開頭
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
方法
  1. 方法與方法之間間隔一行
  2. 大量的方法盡量要以組的形式放在一起,比如生命周期函數(shù)豺裆、公有方法拒秘、私有方法、setter && getter臭猜、代理方法..
  3. 方法最后面的括號需要另起一行躺酒。遵循 Apple 的規(guī)范
  4. 對于其他場景的括號,括號不需要單獨換行蔑歌。比如 if 后面的括號羹应。
  5. 如果方法參數(shù)過多過長,建議多行書寫次屠。用冒號進行對齊园匹。
  6. 一個方法內(nèi)的代碼最好保持在50行以內(nèi),一般經(jīng)驗來看如果一個方法里面的代碼行數(shù)過多劫灶,代碼的閱讀體驗就很差(別問為什么裸违,做過重構(gòu)代碼行數(shù)很長的人都有類似的心情)
  7. 一個函數(shù)只做一個事情,做到單一原則本昏。所有的類供汛、方法設(shè)計好后就可以類似搭積木一樣實現(xiàn)一個系統(tǒng)。
  8. 對于有返回值的函數(shù),且函數(shù)內(nèi)有分支情況怔昨。確保每個分支都有返回值雀久。
  9. 函數(shù)如果有多個參數(shù),外部傳入的參數(shù)需要檢驗參數(shù)的非空趁舀、數(shù)據(jù)類型的合法性赖捌,參數(shù)錯誤做一些措施:立即返回、斷言矮烹。
  10. 多個函數(shù)如果有邏輯重復(fù)的代碼越庇,建議將重復(fù)的部分抽取出來,成為獨立的函數(shù)進行調(diào)用
- (instancetype)init
{
    self = [super init];
    if (self) {
        <#statements#>
    }
    return self;
}

- (void)doHomework:(NSString *)name
            period:(NSInteger)second
            score:(NSInteger)score;
  1. 方法如果有多個參數(shù)的情況下需要注意是否需要介詞和連詞擂送。很多時候在不知道如何抉擇測時候思考下蘋果的一些 API 的方法命名悦荒。
//good
- (instancetype)initWithAge:(NSInteger)age name:(NSString *)name;

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


//bad
- (instancetype)initWithAge:(NSInteger)age andName:(NSString *)name;

- (void)tableView:(UITableView *)tableView :(NSIndexPath *)indexPath;
  1. .m 文件中的私有方法需要在頂部進行聲明
  2. 方法組之間也有個順序問題。
  • 在文件最頂部實現(xiàn)屬性的聲明嘹吨、私有方法的聲明(很多人省去這一步搬味,問題不大,但是蠻多第三方的庫都寫了蟀拷,看起來還是會很方便碰纬,建議書寫)。
  • 在生命周期的方法里面问芬,比如 viewDidLoad 里面只做界面的添加悦析,而不是做界面的初始化,所有的 view 初始化建議放在 getter 里面去做此衅。往往 view 的初始化的代碼長度會比較長强戴、且一般會有多個 view 所以 getter 和 setter 一般建議放在最下面,這樣子頂部就可以很清楚的看到代碼的主要邏輯挡鞍。
  • 所有button骑歹、gestureRecognizer 的響應(yīng)事件都放在這個區(qū)域里面,不要到處亂放墨微。
    文件基本上就是
#import "ViewController.h"
/*ViewController*/

/*View&&Util*/

/*model*/

/*NetWork InterFace*/

/*Vender*/

@interface ViewController ()

@end

@implementation ViewController

#pragma mark - life cycle
- (void)viewWillAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.title = @"標(biāo)準(zhǔn)模版";
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
}

- (void)dealloc
{
    NSLog(@"%s",__func__);
}

#pragma mark - public Method

#pragma mark - private method

#pragma mark - event response



#pragma mark - UITableViewDelegate

#pragma mark - UITableViewDataSource
//...(多個代理方法依次往下寫)

#pragma mark - getters and setters

@end

14 .私有方法應(yīng)該在實現(xiàn)文件中申明道媚。

@interface ViewController ()
- (void)basicConfiguration;
@end

@implementation ViewController
- (void)basicConfiguration
{
   //Do some basic configuration
}
@end
圖片資源
  1. 單個文件的命名
    文件資源的命名也需要一定的規(guī)范,形式為:功能模塊名類別功能_狀態(tài)@nx.png
    Setting_Button_search_selected@2x.png翘县、Setting_Button_search_selected@3x.png
    Setting_Button_search_unselected@2x.png最域、Setting_Button_search_unselected@3x.png
    2、 資源的文件夾命名 最好也參考 App 按照功能模塊建立對應(yīng)的實體文件夾目錄锈麸,最后到對應(yīng)的目錄下添加相應(yīng)的資源文件镀脂。
注釋

優(yōu)秀的代碼大部分是可以自描述的,我們完全可以用程代碼本身來表達它到底在干什么忘伞,而不需要注釋的輔助狗热。
但并不是說一定不能寫注釋钞馁,有以下三種情況比較適合寫注釋:

  1. 公共接口(注釋要告訴閱讀代碼的人虑省,當(dāng)前類能實現(xiàn)什么功能)匿刮。
  2. 涉及到比較深層專業(yè)知識的代碼(注釋要體現(xiàn)出實現(xiàn)原理和思想)。
  3. 容易產(chǎn)生歧義的代碼(但是嚴(yán)格來說探颈,容易讓人產(chǎn)生歧義的代碼是不允許存在的)熟丸。
    除了上述這三種情況,如果別人只能依靠注釋才能讀懂你的代碼的時候伪节,就要反思代碼出現(xiàn)了什么問題光羞。

最后,對于注釋的內(nèi)容怀大,相對于“做了什么”纱兑,更應(yīng)該說明“為什么這么做”。

  1. 對于類的注釋寫在當(dāng)前類文件的頂部
  2. 對于屬性的注釋需要寫在屬性后面的地方化借。 //*<userId/
  3. 對于 .h 文件中方法的注釋潜慎,一律按快捷鍵 command+option+/。三個快捷鍵解決蓖康。按需在旁邊對方法進行說明解釋铐炫、返回值、參數(shù)的說明和解釋
  4. 對于 .m 文件中的方法的注釋蒜焊,在方法的旁邊添加 //倒信。
  5. 注釋符和注釋內(nèi)容需要間隔一個空格。 例如: // fetch goods list
版本規(guī)范

采用 A.B.C 三位數(shù)字命名泳梆,比如:1.0.2鳖悠,當(dāng)有更新的情況下按照下面的依據(jù)

  • 屬于重大內(nèi)容的更新 1.0.2 -> 2.0.0
  • 屬于小部分內(nèi)容的更新 1.0.2 -> 1.1.1
  • 屬于補丁更新 1.0.2 -> 1.0.3
改進

我們知道了平時在使用 Xcode 開發(fā)的過程中使用的系統(tǒng)提供的代碼塊所在的地址和新建控制器、模型优妙、view等的文件模版的存放文件夾地址后乘综,我們就可以設(shè)想下我們是否可以定制自己團隊風(fēng)格的控制器模版、是否可以打造和維護自己團隊的高頻使用的代碼塊鳞溉?
答案是可以的瘾带。
Xcode 代碼塊的存放地址:~/Library/Developer/Xcode/UserData/CodeSnippets
Xcode 文件模版的存放地址:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/File Templates/

代碼塊的改造

我們可以將屬性、控制器生命周期方法熟菲、單例構(gòu)造一個對象的方法看政、代理方法、block抄罕、GCD允蚣、UITableView 懶加載、UITableViewCell 注冊呆贿、UITableView 代理方法的實現(xiàn)嚷兔、UICollectionVIew 懶加載森渐、UICollectionVIewCell 注冊、UICollectionView 的代理方法實現(xiàn)等等組織為 codesnippets

思考
  • 封裝好 codesnippets 之后團隊除了你 編寫這個項目的人如何使用冒晰?如何知道是否有這個代碼塊同衣?
    方案:先在團隊內(nèi)召開代碼規(guī)范會議,大家都統(tǒng)一知道這個事情在壶运。之后大家共同維護 codesnippets耐齐。用法見下
    屬性:通過 Property_類型 開頭,回車鍵自動補全蒋情。比如 Strong 類型埠况,編寫代碼通過 Property_Strong 回車鍵自動補全成如下格式
@property (nonatomic, strong) <#Class#> *<#object#>;

方法:以 Method_關(guān)鍵詞 回車鍵確認(rèn),自動補全棵癣。比如 Method_UIScrollViewDelegate 回車鍵自動補全成 如下格式

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    
}

各種常見的 Mark:以 Mark_關(guān)鍵詞 回車確認(rèn)辕翰,自動補全。比如 Method_MethodsGroup 回車鍵自動補全成 如下格式

#pragma mark - life cycle
#pragma mark - public Method
#pragma mark - private method
#pragma mark - event response
#pragma mark - UITableViewDelegate
#pragma mark - UITableViewDataSource
#pragma mark - getters and setters
  • 封裝好 codesnippets 之后團隊內(nèi)如何統(tǒng)一狈谊?想到一個方案喜命,可以將團隊內(nèi)的 codesnippets 共享到 git,團隊內(nèi)的其他成員再從云端拉取同步的畴。這樣的話團隊內(nèi)的每個成員都可以使用最新的 codesnippets 來編碼渊抄。
    編寫 shell 腳本。幾個關(guān)鍵步驟:
  1. 給系統(tǒng)文件夾授權(quán)
  2. 在腳本所在文件夾新建存放代碼塊的文件夾
  3. 將系統(tǒng)文件夾下面的代碼塊復(fù)制到步驟2創(chuàng)建的文件夾下面
  4. 將當(dāng)前的所有文件提交到 Git 倉庫
文件模版的改造

我們觀察系統(tǒng)文件模版的特點丧裁,和在 Xcode 新建文件模版對應(yīng)护桦。

image.png

所以我們新建 Custom 文件夾,將系統(tǒng) Source 文件夾下面的 Cocoa Touch Class.xctemplate 復(fù)制到 Custom 文件夾下煎娇。重命名為我們需要的名字二庵,我這里以“Power”為例
image.png

進入 PowerViewController.xctemplate/PowerViewControllerObjective-C
修改 FILEBASENAME.h 和 FILEBASENAME.m 文件內(nèi)容
image.png

在替換 .h 文件內(nèi)容的時候后面改為 UIViewController,不然其他開發(fā)者新建文件模版的時候出現(xiàn)的不是 UIViewController 而是我們的 PowerViewController
image.png

修改 TemplateInfo.plist
image.png

  • 如何使用
    商量好一個標(biāo)識(“Power”)缓呛。比如我新建了單例催享、控制器、Model哟绊、UIView4個模版因妙,都以為 Power 開頭。
  • 如何共享
    以 shell 腳本為工具票髓。使用腳本將 git 云端的代碼模版同步到本地 Xcode 文件夾對應(yīng)的位置就可以使用了攀涵。關(guān)鍵步驟:
  1. git clone 代碼到腳本所在文件夾
  2. 進入存放 codesnippets 的文件夾將內(nèi)容復(fù)制到系統(tǒng)存放 codesnippets 的地方
  3. 進入存放 file template 的文件夾將內(nèi)容復(fù)制到系統(tǒng)存放 file template 的地方

./syncSnippets.sh // 同步git云端代碼塊和文件模版到本地
./uploadMySnippets.sh //將本地的代碼塊和文件模版同步到云端

補充

if語句

1。 必須列出所有分支(窮舉所有的情況)洽沟,而且每個分支都必須給出明確的結(jié)果以故。

var hintStr;
if (count < 3) {
  hintStr = "Good";
} else {
  hintStr = "";
}
不推薦這樣寫:
var hintStr;
if (count < 3) {
 hintStr = "Good";
}
  1. 不要使用過多的分支,要善于使用return來提前返回錯誤的情況
- (void)someMethod { 
  if (!goodCondition) {
    return;
  }
  //Do something
}
不推薦這樣寫:
- (void)someMethod { 
  if (goodCondition) {
    //Do something
  }
}
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError)err{
   //方法1. 參數(shù)為nil
   if (!dict) {
     if (err) *err = [JSONModelError errorInputIsNil];
     return nil;
    }

    //方法2. 參數(shù)不是nil裆操,但也不是字典
    if (![dict isKindOfClass:[NSDictionary class]]) {
        if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];
        return nil;
    }

    //方法3. 初始化
    self = [self init];
    if (!self) {
        //初始化失敗
        if (err) *err = [JSONModelError errorModelIsInvalid];
        return nil;
    }

    //方法4. 檢查用戶定義的模型里的屬性集合是否大于傳入的字典里的key集合(如果大于怒详,則返回NO)
    if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
        return nil;
    }

    //方法5. 核心方法:字典的key與模型的屬性的映射
    if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
        return nil;
    }

    //方法6. 可以重寫[self validate:err]方法并返回NO炉媒,讓用戶自定義錯誤并阻攔model的返回
    if (![self validate:err]) {
        return nil;
    }

    //方法7. 終于通過了!成功返回model
    return self;
}

可以看到昆烁,在這里吊骤,首先判斷出各種錯誤的情況然后提前返回,把最正確的情況放到最后返回善玫。

  1. 條件表達式如果很長水援,則需要將他們提取出來賦給一個BOOL值
let nameContainsSwift = sessionName.hasPrefix("Swift")
let isCurrentYear = sessionDateCompontents.year == 2014
let isSwiftSession = nameContainsSwift && isCurrentYear
if (isSwiftSession) { 
   // Do something
}
不推薦這樣寫:
if ( sessionName.hasPrefix("Swift") && (sessionDateCompontents.year == 2014) ) { 
    // Do something
}
  1. 條件語句的判斷應(yīng)該是變量在左,常量在右
if ( count == 6) {
}
if ( object == nil) {
}
不推薦這樣寫:
if ( 6 == count) {
}
if ( nil == object ) {
}
for語句
  1. 不可在for循環(huán)內(nèi)修改循環(huán)變量茅郎,防止for循環(huán)失去控制。
for (int index = 0; index < 10; index++){
   ...
   logicToChange(index)
}
  1. 避免使用continue和break或渤。
    ontinue和break所描述的是“什么時候不做什么”系冗,所以為了讀懂二者所在的代碼,我們需要在頭腦里將他們?nèi)》础?/li>

其實最好不要讓這兩個東西出現(xiàn)薪鹦,因為我們的代碼只要體現(xiàn)出“什么時候做什么”就好了掌敬,而且通過適當(dāng)?shù)姆椒ǎ强梢詫⑦@兩個東西消滅掉的:
2.1 如果出現(xiàn)了continue池磁,只需要把continue的條件取反即可

var filteredProducts = Array<String>()
for level in products {
    if level.hasPrefix("bad") {
        continue
    }
    filteredProducts.append(level)
}

我們可以看到奔害,通過判斷字符串里是否含有“bad”這個prefix來過濾掉一些值。其實我們是可以通過取反地熄,來避免使用continue的:

for level in products {
    if !level.hasPrefix("bad") {
      filteredProducts.append(level)
    }
}

Switch語句

  1. 每個分支都必須用大括號括起來
switch (integer) {  
  case 1:  {
    // ...  
   }
    break;  
  case 2: {  
    // ...  
    break;  
  }  
  case 3: {
    // ...  
    break; 
  }
  default:{
    // ...  
    break; 
  }
}
  1. 使用枚舉類型時华临,不能有default分支, 除了使用枚舉類型以外端考,都必須有default分支
RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;  
switch (menuType) {  
  case RWTLeftMenuTopItemMain: {
    // ...  
    break; 
   }
  case RWTLeftMenuTopItemShows: {
    // ...  
    break; 
  }
  case RWTLeftMenuTopItemSchedule: {
    // ...  
    break; 
  }
}

在Switch語句使用枚舉類型的時候雅潭,如果使用了default分支,在將來就無法通過編譯器來檢查新增的枚舉類型了却特。

函數(shù)

1. 一個函數(shù)的長度必須限制在50行以內(nèi)

通常來說扶供,在閱讀一個函數(shù)的時候,如果視需要跨過很長的垂直距離會非常影響代碼的閱讀體驗裂明。如果需要來回滾動眼球或代碼才能看全一個方法椿浓,就會很影響思維的連貫性,對閱讀代碼的速度造成比較大的影響闽晦。最好的情況是在不滾動眼球或代碼的情況下一眼就能將該方法的全部代碼映入眼簾扳碍。

2. 一個函數(shù)只做一件事(單一原則)

每個函數(shù)的職責(zé)都應(yīng)該劃分的很明確(就像類一樣)。

dataConfiguration()
viewConfiguration()
不推薦這樣寫:
void dataConfiguration()
{   
   ...
   viewConfiguration()
}
3. 對于有返回值的函數(shù)(方法)尼荆,每一個分支都必須有返回值
int function()
{
    if(condition1){
        return count1
    }else if(condition2){
        return count2
    }else{
       return defaultCount
    } 
}
不推薦這樣寫:
int function()
{
    if(condition1){
        return count1
    }else if(condition2){
        return count2
    }
}
4. 對輸入?yún)?shù)的正確性和有效性進行檢查左腔,參數(shù)錯誤立即返回
void function(param1,param2)
{
      if(param1 is unavailable){
           return;
      }
    
      if(param2 is unavailable){
           return;
      }

     //Do some right thing
}

5. 如果在不同的函數(shù)內(nèi)部有相同的功能,應(yīng)該把相同的功能抽取出來單獨作為另一個函數(shù)
6. 將函數(shù)內(nèi)部比較復(fù)雜的邏輯提取出來作為單獨的函數(shù)

一個函數(shù)內(nèi)的不清晰(邏輯判斷比較多捅儒,行數(shù)較多)的那片代碼液样,往往可以被提取出去振亮,構(gòu)成一個新的函數(shù),然后在原來的地方調(diào)用它這樣你就可以使用有意義的函數(shù)名來代替注釋鞭莽,增加程序的可讀性坊秸。

openEmailSite();
login();

writeTitle(title);
writeContent(content);
writeReceiver(receiver);
addAttachment(attachment);

send();
中間的部分稍微長一些,我們可以將它們提取出來:
void writeEmail(title, content,receiver,attachment)
{
  writeTitle(title);
  writeContent(content);
  writeReceiver(receiver);
  addAttachment(attachment); 
}
然后再看一下原來的代碼:
openEmailSite();
login();
writeEmail(title, content,receiver,attachment)
send();
8. 避免使用全局變量澎怒,類成員(class member)來傳遞信息褒搔,盡量使用局部變量和參數(shù)。

在一個類里面喷面,經(jīng)常會有傳遞某些變量的情況星瘾。而如果需要傳遞的變量是某個全局變量或者屬性的時候,有些朋友不喜歡將它們作為參數(shù)惧辈,而是在方法內(nèi)部就直接訪問了:

 class A {
   var x;

   func updateX() 
   {
      ...
      x = ...;
   }

   func printX() 
   {
     updateX();
     print(x);
   }
 }

我們可以看到琳状,在printX方法里面,updateX和print方法之間并沒有值的傳遞盒齿,乍一看我們可能不知道x從哪里來的念逞,導(dǎo)致程序的可讀性降低了。
而如果你使用局部變量而不是類成員來傳遞信息边翁,那么這兩個函數(shù)就不需要依賴于某一個類翎承,而且更加容易理解,不易出錯:

func updateX() -> String
 {
    x = ...;
    return x;
 }

 func printX() 
 {
   String x = updateX();
   print(x);
 }
CGRect函數(shù)

其實iOS內(nèi)部已經(jīng)提供了相應(yīng)的獲取CGRect各個部分的函數(shù)了符匾,它們的可讀性比較高叨咖,而且簡短,推薦使用:

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 };

范型

建議在定義NSArray和NSDictionary時使用泛型待讳,可以保證程序的安全性:

NSArray<NSString *> *testArr = [NSArray arrayWithObjects:@"Hello", @"world", nil];
NSDictionary<NSString *, NSNumber *> *dic = @{@"key":@(1), @"age":@(10)};

Block

  1. Block屬性應(yīng)該使用copy關(guān)鍵字
typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);
@property (nonatomic, readwrite, copy) ErrorCodeBlock errorBlock;//將block拷貝到堆中

為常用的Block類型創(chuàng)建typedef
如果我們需要重復(fù)創(chuàng)建某種block(相同參數(shù)芒澜,返回值)的變量,我們就可以通過typedef來給某一種塊定義屬于它自己的新類型

int (^variableName)(BOOL flag, int value) =^(BOOL flag, int value)
{
     // Implementation
     return someInt;
}
這個Block有一個bool參數(shù)和一個int參數(shù)创淡,并返回int類型痴晦。我們可以給它定義類型:
typedef int(^EOCSomeBlock)(BOOL flag, int value);

再次定義的時候,就可以通過簡單的賦值來實現(xiàn):

EOCSomeBlock block = ^(BOOL flag, int value){
     // Implementation
};

定義作為參數(shù)的Block

- (void)startWithCompletionHandler: (void(^)(NSData *data, NSError *error))completion;

這里的Block有一個NSData參數(shù)琳彩,一個NSError參數(shù)并沒有返回值

typedef void(^EOCCompletionHandler)(NSData *data, NSError *error);
- (void)startWithCompletionHandler:(EOCCompletionHandler)completion;”

字面量語法

盡量使用字面量值來創(chuàng)建 NSString , NSDictionary , NSArray , NSNumber 這些不可變對象:

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

不推薦這樣寫:

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

面向協(xié)議編程

如果某些功能(方法)具備可復(fù)用性誊酌,我們就需要將它們抽取出來放入一個抽象接口文件中(在iOS中,抽象接口即協(xié)議)露乏,讓不同類型的對象遵循這個協(xié)議碧浊,從而擁有相同的功能。

因為協(xié)議是不依賴于某個對象的瘟仿,所以通過協(xié)議箱锐,我們可以解開兩個對象之間的耦合。如何理解呢劳较?我們來看一下下面這個例子:
現(xiàn)在有一個需求:在一個UITableViewController里面拉取feed并展示出來驹止。

方案一:

定義一個拉取feed的類ZOCFeedParser浩聋,這個類有一些代理方法實現(xiàn)feed相關(guān)功能:

@protocol ZOCFeedParserDelegate <NSObject>
@optional
- (void)feedParserDidStart:(ZOCFeedParser *)parser;
- (void)feedParser:(ZOCFeedParser *)parser didParseFeedInfo:(ZOCFeedInfoDTO *)info; 
- (void)feedParser:(ZOCFeedParser *)parser didParseFeedItem:(ZOCFeedItemDTO *)item; 
- (void)feedParserDidFinish:(ZOCFeedParser *)parser;
- (void)feedParser:(ZOCFeedParser *)parser didFailWithError:(NSError *)error;@end 

@interface ZOCFeedParser : NSObject
@property (nonatomic, weak) id <ZOCFeedParserDelegate> delegate; 
@property (nonatomic, strong) NSURL *url; 

- (id)initWithURL:(NSURL *)url; 
- (BOOL)start; 
- (void)stop; 
@end 

然后在ZOCTableViewController里面?zhèn)魅隯OCFeedParser,并遵循其代理方法臊恋,實現(xiàn)feed的拉取功能衣洁。

@interface ZOCTableViewController : UITableViewController<ZOCFeedParserDelegate>
- (instancetype)initWithFeedParser:(ZOCFeedParser *)feedParser; 
@end 

具體應(yīng)用:

NSURL *feedURL = [NSURL URLWithString:@"http://bbc.co.uk/feed.rss"]; 
ZOCFeedParser *feedParser = [[ZOCFeedParser alloc] initWithURL:feedURL]; 
ZOCTableViewController *tableViewController = [[ZOCTableViewController alloc] initWithFeedParser:feedParser]; 
feedParser.delegate = tableViewController; 

OK,現(xiàn)在我們實現(xiàn)了需求:在ZOCTableViewController里面存放了一個ZOCFeedParser對象來處理feed的拉取功能抖仅。
但這里有一個嚴(yán)重的耦合問題:ZOCTableViewController只能通過ZOCFeedParser對象來處理feed的拉取功能坊夫。
于是我們重新審視一下這個需求:其實我們實際上只需要ZOCTableViewController拉取feed就可以了,而具體是由哪個對象來拉取撤卢,ZOCTableViewController并不需要關(guān)心环凿。

也就是說,我們需要提供給ZOCTableViewController的是一個更范型的對象凸丸,這個對象具備了拉取feed的功能就好了拷邢,而不應(yīng)該僅僅局限于某個具體的對象(ZOCFeedParser)。所以屎慢,剛才的設(shè)計需要重新做一次修改:

方案二

首先需要在一個接口文件ZOCFeedParserProtocol.h里面定義抽象的,具有拉取feed功能的協(xié)議:

@protocol ZOCFeedParserDelegate <NSObject>
@optional
- (void)feedParserDidStart:(id<ZOCFeedParserProtocol>)parser;
- (void)feedParser:(id<ZOCFeedParserProtocol>)parser didParseFeedInfo:(ZOCFeedInfoDTO *)info; 
- (void)feedParser:(id<ZOCFeedParserProtocol>)parser didParseFeedItem:(ZOCFeedItemDTO *)item; 
- (void)feedParserDidFinish:(id<ZOCFeedParserProtocol>)parser;
- (void)feedParser:(id<ZOCFeedParserProtocol>)parser didFailWithError:(NSError *)error;@end 

@protocol ZOCFeedParserProtocol <NSObject>
@property (nonatomic, weak) id <ZOCFeedParserDelegate> delegate; 
@property (nonatomic, strong) NSURL *url;

- (BOOL)start;
- (void)stop;

@end

而原來的ZOCFeedParser僅僅是需要遵循上面這個協(xié)議就具備了拉取feed的功能:

@interface ZOCFeedParser : NSObject <ZOCFeedParserProtocol> 
- (id)initWithURL:(NSURL *)url;//僅僅需要通過傳入url即可忽洛,其他事情都交給ZOCFeedParserProtocol@end 

而且腻惠,ZOCTableViewController也不直接依賴于ZOCFeedParser對象,我們只需要傳給它一個遵循<ZOCFeedParserProtocol>的對象即可欲虚。

@interface ZOCTableViewController : UITableViewController <ZOCFeedParserDelegate>
- (instancetype)initWithFeedParser:(id<ZOCFeedParserProtocol>)feedParser;
@end

這樣一來集灌,ZOCTableViewController和ZOCFeedParser之間就沒有直接的關(guān)系了。以后复哆,如果我們想:

  • 給這個feed拉取器增加新的功能:僅需要修改ZOCFeedParserProtocol.h文件欣喧。
  • 更換一個feed拉取器實例:創(chuàng)建一個新類型來遵循ZOCFeedParserProtocol.h即可。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末梯找,一起剝皮案震驚了整個濱河市唆阿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锈锤,老刑警劉巖驯鳖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異久免,居然都是意外死亡浅辙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門阎姥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來记舆,“玉大人,你說我怎么就攤上這事呼巴≡笕” “怎么了御蒲?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盛正。 經(jīng)常有香客問我删咱,道長,這世上最難降的妖魔是什么豪筝? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任痰滋,我火速辦了婚禮,結(jié)果婚禮上续崖,老公的妹妹穿的比我還像新娘敲街。我一直安慰自己,他們只是感情好严望,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布多艇。 她就那樣靜靜地躺著,像睡著了一般像吻。 火紅的嫁衣襯著肌膚如雪峻黍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天拨匆,我揣著相機與錄音姆涩,去河邊找鬼。 笑死惭每,一個胖子當(dāng)著我的面吹牛骨饿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播台腥,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼宏赘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了黎侈?” 一聲冷哼從身側(cè)響起察署,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜓竹,沒想到半個月后箕母,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡俱济,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年嘶是,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛛碌。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡聂喇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情希太,我是刑警寧澤克饶,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站誊辉,受9級特大地震影響矾湃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜堕澄,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一邀跃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛙紫,春花似錦拍屑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至唁毒,卻和暖如春蒜茴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浆西。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工矮男, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人室谚。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像崔泵,于是被迫代替她去往敵國和親秒赤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,089評論 1 32
  • 一憎瘸、Python簡介和環(huán)境搭建以及pip的安裝 4課時實驗課主要內(nèi)容 【Python簡介】: Python 是一個...
    _小老虎_閱讀 5,723評論 0 10
  • 濯堂 庫斯特說入篮,上帝讓右手成為右手,就是對右手最高的獎賞幌甘。而我也想說:上帝讓左手成為左手又何嘗不是最高的獎...
    濯堂閱讀 438評論 1 1
  • 感覺今天就是來歷練我的潮售。 不僅僅說是特訓(xùn)營的歷練,各種事似乎就是知道我在經(jīng)歷這歷練的過程锅风,也一股腦的找過來了酥诽。弄得...
    卓芳閱讀 163評論 0 0
  • 今天輪到我守店。 下午劉風(fēng)萍邀約了一位顧客來了解產(chǎn)品皱埠。我?guī)退俏活櫩驮谥怅P(guān)節(jié)處涂了醒膚膏肮帐。臨近中午我趕...
    一本經(jīng)典的書閱讀 186評論 0 0