OC 語法

下劃線和self.的區別

1.通過self. 訪問,包含了set和get方法。
通過下劃線是獲取自己的實例變量,不包含set和get的方法。

2.self.是對屬性的訪問,而下劃線是對局部變量的訪問。在使用self.時是調用一個getter方法。會使引用計數加一,而下劃線不會使用引用計數器加一的。
所有被聲明為屬性的成員,在iOS5之前需要使用編譯指令@synthesize 來告訴編譯器幫助生成屬性的getter和setter方法,之后這個指令可以不用人為的指定了,默認情況下編譯器會幫助我們生成。編譯器在生成getter,setter方法時首先查找當前的類中用戶是否定義了屬性的getter,setter方法,如果有,則編譯器會跳過,不會再生成,使用用戶定義的方法。

總結:
使用self.是更好的選擇,因為這樣可以兼容懶加載,同時也避免了使用下滑線的時候忽略了self這個指針,后者容易在Block中造成循環引用。也因為,使用 _是獲取不到父類的屬性的,它只是對局部變量的訪問。self方法實際上是用了get和set方法間接調用,下劃線方法是直接對變量操作。

參考文獻:
OC中下劃線和self.的區別

const和#define的區別

define

#define語法稱之為預處理命令。#define聲明主要用于將常量(或字符串)賦予有意義的名字,比如當你在編寫一個日歷程序時,可以定義:

#define MONTHS_PER_YEAR 12

Tips:
1.通常情況下,習慣將預處理的常量名全大寫,單詞之間用下劃線隔開
(與正常變量區分)。
2.如果是定義常量,若這個常量的適用范圍局限于.m,那么習慣性在常量名前
加k,若常量在類之外可見,則通常以類名為前綴。

預處理代碼起的作用實際上相當于在編譯之前?。?!
預處理代碼起的作用實際上相當于在編譯之前!??!
預處理代碼起的作用實際上相當于在編譯之前?。?!

重要事情說三遍。也就是說宏使用過多會增加編譯時間

而一個常量在棧中開辟空間是很高效的。

使用宏有以下好處:

1.增強代碼可讀性
2.方便全局使用和修改一些方法和參數
3.增強復用性

const常量

const 如果是聲明常量,僅僅是想要在.m文件中使用,那么一定要同時使用static 和const來聲明,若不加系統在編譯時會自動為它加一個extern (外部符號),此時,若另一個編譯單元出現了同名變量就會報錯,并且很難查找錯誤。

只能在警告區找到

但無法確認錯誤位置

這是OC程序員最不愿意看到的錯誤。。。也是很難查找的錯誤,跟修改已經創建的類的文件名的錯誤差不多。

總結:

  1. 多用類型常量,少用#define預處理指令(出自《Effective Objective-C》第四條原則)
  2. 聲明const的字符串時,開頭用k標識。推薦k+模板名字首字母大寫+作用名稱 防止和其他的重復
  3. 蘋果的API中,大多數字符串,也是用以下這種方式(如以Key,style,Type結尾的一些參數)
// Constants.h
FOUNDATION_EXPORT NSString *const MyFirstConstant;
FOUNDATION_EXPORT NSString *const MySecondConstant;
// Constants.m
NSString *const MyFirstConstant = @"FirstConstant";
NSString *const MySecondConstant = @"SecondConstant";

或者

extern NSString * const PPRememberMusicList;
extern NSString * const PPLoadMusicAtListLoad;
extern NSString * const PPAfterPlayingMusic;
extern NSString * const PPGotoStartupAfterPlaying;
NSString * const PPRememberMusicList = @"Remember Music List";
NSString * const PPLoadMusicAtListLoad = @"Load music when loading list";
NSString * const PPAfterPlayingMusic = @"After playing music";
NSString * const PPGotoStartupAfterPlaying = @"Go to startup pos. after playing";

參考文獻:
Define與Const的使用

#define詳解

預編譯

程序會在預編譯之前還會有一些操作, 比如:刪除反斜線+換行符的組合, 將各種形式的注釋用空格替代等等。
接著是預編譯階段,預編譯在處理#define的時候,會從#開始一直執行到遇到的第一個換行符(寫代碼的時候換行的作用)為止。
所以可知#define只會允許定義一行的宏,但是因為預編譯之前會刪除反斜線+換行符的組合,所以我們可以利用反斜線+換行符來定義多行宏,在將刪除反斜線和換行符的組合后,在預編譯階段的邏輯上#define定義就成了一行的宏了。
#define在預處理階段只進行文本的替換(相當于把代碼拷貝粘貼),不會進行具體的計算。
#define作用在預編譯時期,其真正的效果就是代碼替換,而且是直接替換(內聯函數!!!),這個和函數有著很大的區別。

宏(#define)的基本語法:
#define 宏名 主體;
   ↓     ↓    ↓
#define PI  3.1415926
宏(#define)的使用:
類對象宏:

類對象宏一般用來定義數據常量或字符串常量。

#define PI  3.1415926

#define NAME @"SunSatan"
類函數宏:

類函數宏就是宏名類似函數名,宏的主體為函數,并可以幫助主體函數接受參數,使用起來就像是函數一樣。

#define Log(x) NSLog(@"this is test: x = %@", x)

類函數宏和函數的區別是, 類函數宏的參數不指定類型, 具體的參數類型在調用宏的時候由傳入的參數決定,這就可能會遇到類型錯誤的問題。
舉個栗子:

#define power(x) x*x
 
int x = 2;
int pow_1 = power(x);
int pow_2 = power(x+1);
 
猜猜pow_1和pow_2的值分別為多少?

結果是pow_1=4 ,pow_2=5。
這個結果是不是和預想的不一樣,pow_2不應該等于9嗎?因為宏真正的效果就是代碼替換,所以在預編譯后原來的代碼變為了:

#define power(x) x*x
 
int x = 2;
int pow_1 = x*x;    //2*2=4
int pow_2 = x+1*x+1;//2+1*2+1=5
 
這樣看是不是就能看出問題,在代碼替換后優先級不對了。
 
所以想要得到正確的結果的宏定義如下:
 
#define power(x) (x)*(x)

這個栗子就是告訴大家定義類函數宏的時候就真的要小心,不然得到結果可能會出乎我們的意料。

宏(#define)中操作符的使用:

#:字符串化操作符,需要放置在參數前,將類函數宏中傳入的參數用""括起來變成了c語言的字符串。

#define Log(x) #x
 
代碼替換后:
Log(x) -> "x"
 
所以以下兩種使用方法都沒問題,打印信息都是 x
NSLog(@"%s", Log(x)); -> NSLog(@"%s", "x");
NSLog(@"%@",@Log(x)); -> NSLog(@"%@",@"x");

##:符號連接操作符,需要放置在參數前,參數就會和##前面的符號連接起來。

#define single(name) +(instancetype)share##name;
 
代碼替換后:
single(SunSatan) -> +(instancetype)shareSunSatan;
宏(#define)的好處:

使用宏的好處是不言自明的,可以少寫很多重復的代碼,修改一處就可以完成全局修改,在節省工作量的同時,大大增加代碼可讀性。
如果想成為一個能寫出漂亮優雅代碼的開發者,宏絕對是必不可少的技能。

使用宏(#define)的建議:

多多使用類函數宏對開發者而言是非常靈活、方便的,可以抽離很多重復使用的冗長的代碼段,增加代碼可讀性,節省工作量。
使用但不是濫用,應有節制。
使用大量的宏,每次修改宏都需要重新替換,導致編譯時間久。
不建議使用類對象宏,因為類對象宏定義的是常量,既然都要定義為常量,那么我們應該使用const來修飾。
相對于定義常量而言,const要比宏效率高很多,而且宏不做檢查,只是代碼替換,而const會進行編譯檢查,const要比宏更安全。所以應盡可能的使用const來代替類對象宏。

參考文獻:
https://blog.csdn.net/qq_36557133/article/details/86476686

NSAssert(斷言)

NSAssert

斷言(NSAssert)是一個宏,在開發過程中使用NSAssert可以及時發現程序中的問題。
NSAssert聲明如下:

#define NSAssert(condition, desc, ...)
  • condition:條件表達式。條件成立時,運行后面程序;不成立時,拋出帶有desc描述的異常信息。
  • desc:異常描述,通常為NSString類型對象。用于描述條件表達式不成立的錯誤信息和參數的占位符。
  • ...:desc字符串中的參數。
    假設我們需要判斷變量值是否大于5,我們可以用如下代碼進行判斷。
    int i = 6;
    NSAssert(i>5, @"i must bigger than 5");

運行后,控制臺沒有任何輸出。

把變量i的值改為2,如下所示:

    int i = 2;
    NSAssert(i>5, @"i must bigger than 5");

運行demo,demo會崩潰。在控制臺輸出如下信息:

*** Assertion failure in -[ViewController viewDidLoad], /Users/ad/Library/Mobile Documents/com~apple~CloudDocs/file2016/NSAssert0709/NSAssert0709/ViewController.m:23
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'i must bigger than 5'

通過控制臺輸出的信息,可以得到崩潰發生于:ViewController類的viewDidLoad方法中,該文件位于/Users/ad/Library/Mobile Documents/comappleCloudDocs/file2016/NSAssert0709/NSAssert0709/ViewController.m,導致崩潰的代碼位于第23行,崩潰原因為:i must bigger than 5。

使用NSAssert時可以對輸出信息進行傳值。

    int i = 2;
    NSAssert1(i>5, @"The real value is %i", i);

輸出為:

*** Assertion failure in -[ViewController viewDidLoad], /Users/ad/Library/Mobile Documents/com~apple~CloudDocs/file2016/NSAssert0709/NSAssert0709/ViewController.m:23
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The real value is 2'

NSAssert用于Objective-C,NSCAssert用于C語言中。
NSAssert1的desc帶有一個參數,NSAssert2的desc帶有兩個參數,……,NSAssert5帶有五個參數。斷言可以帶有零至五個參數。

也許你會好奇為什么desc中不使用[NSString stringWithFormat:...]格式,而要有五個NSAssert?因為NSAssert()的實現就是一個宏,因此,要處理異常信息中不同數量參數,就要有多個宏,所以就有了NSAssert(condition, dest)、NSAssert1(condition, formatDest, arg1)、NSAssert2(condition, formatDest, arg1, arg2)...NSAssert5(...)。

NSAssert和NSLog都可以在控制臺輸出,但NSAssert輸出后程序立即crash,控制臺也會輸出程序遇到錯誤的位置等信息,而NSLog只用于輸出信息。

NSParameterAssert

如果需要判斷傳入參數是否符合要求,可以使用NSParameterAssert。

- (NSString *)processItem:(NSUInteger)index {
    NSParameterAssert(index<self.myArray.count);
    // do something else
}

如果傳入參數index大于myArray數組內元素數量,則程序會崩潰??刂扑敵鋈缦拢?/p>

*** Assertion failure in -[ViewController processItem:], /Users/ad/Library/Mobile Documents/com~apple~CloudDocs/file2016/NSAssert0709/NSAssert0709/ViewController.m:31
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: index<self.myArray.count'

崩潰信息中會告訴你哪一行代碼出錯了,崩潰原因是什么。
NSParameterAssert用于Objective-C,NSCParameterAssert用于C語言中。

斷言默認只存在于debug版

從Xcode 4.2開始,release版默認關閉了斷言。也就是當編譯發布版時,任何調用NSAssert的地方都被空行替換。所以,不要在NSAssert內執行任何有效操作。
如果想要在發布版中使用NSAssert,可以在Build Setting中的Enable Foundation Assertions中修改。


總結

對來源于系統內部的可靠數據使用斷言,即用斷言來處理絕不應該發生的情況。不要對外部不可靠數據(如用戶輸入、文件、網絡讀取等)使用斷言,即對于外部不可靠數據或預期會發生的情況應當使用錯誤處理。同時要避免把需要執行的代碼放到斷言中,斷言可以看成可執行的注釋。

來源于系統外部的數據都是不可信的,需要嚴格檢查(通常是錯誤處理)才能放行到系統內部,這相當于一個守衛。而對于系統內部的交互(如調用其他方法),如果每次也都要去處理輸入的數據,也就相當于系統沒有可信的邊界,會讓代碼變的臃腫。事實上,在系統內部傳遞給方法正確的參數是調用者的責任,調用者應該確保傳遞給所調用方法的數據是符合要求的。這樣就隔離了不可靠的外部環境和可靠的內部環境,降低復雜度。

但在開發階段,代碼極可能存在缺陷,有可能是處理外部數據的邏輯不周全或調用內部方法的代碼存在錯誤,最終造成調用失敗。這個時候,斷言就可以發揮作用,用來確診到底哪一部分問題導致程序出錯。在清理了所有缺陷后,內外有別的信用體系就建立起來了。等到發行版的時候,這些斷言就沒有存在的必要了。

單元測試可能是一個更好的方法,但有些情況下(如復雜的算法過程中),我們希望在代碼中執行檢查,這時斷言將更有效。

參考文獻:
http://www.lxweimin.com/p/3f16e7aaf7ef

switch case

OC中的switch case語句,在每個case后都要加break。不然會將該case下面的case也全部執行,直到遇到break。
Swift的case,只要有內容,就可以省略break。

在 OC 中的 switch

  • 格式: switch(需要匹配的值) case 匹配的值: 需要執行的語句 break;
  • 可以穿透
  • 可以不寫default
  • default位置可以隨便放
  • 在case中定義變量需要加大括號, 否則作用域混亂(仔細了解了一下在C或者C++中,只要是在任何一對花括號 “{ }”中定義的對象,那么該對象的作用域就局限在這對花括號里面,上面的代碼的錯誤就出現在這兒了。)
  • 不能判斷對象類型, 只能判斷基本類型中的整數(不能判斷NSString)

在 Swift 中的 Switch

  • OC必須是整數, 在 Swift 中可以是對象的類型也可以是 double類型
  • 不可以穿透
  • 可以不寫break
  • 不能不寫default
  • default位置只能在最后
  • 在case中定義變量不用加大括號

@interface

.h中的@interface
供其他的class調用。是public

.m中的@interface
也可以叫做class extension。是.h中@interface的補充。但是對其他class是不開放的。是private

@Interface的寫法
@interface 類名: 父類 <協議1, 協議2>
@interface和類名中間一個空格
類名后緊跟:之后空格加上父類協議之間用,空格分割

寫法模板
方法的參數在一排顯示
方法之間保留一行
第一個方法和@interface保留空行
最后一個方法和@end保留空行

總結:一般把public的方法和變量放到.h中。將private的變量放到.m中。而private的方法在.m中可以不聲明,直接使用。

Copy

當我們使用自定義對象的 copy 方法時會報錯,例如我們自定了一個 Person 類并實例化一個對象,并調用其 copy 方法:

Person *p = [[Person alloc] init];
p.name = @"小明";
Person *p1 = [p copy];

當執行到 [p copy] 時,將會報錯,錯誤如下:

-[Person copyWithZone:]: unrecognized selector sent to instance 0x100612d20

原因是沒有實現 copyWithZone 方法,是因為在 copy 方法中調用了 copyWithZone 方法,所以必須實現 copyWithZone 方法才能夠使用 copy 方法。
要實現 copyWithZone 方法,我們首先需要在 Person.h 文件中讓 Person 類遵循 NSCopying 協議

@interface Person : NSObject  <NSCopying>

然后在 Person.m 文件中實現 copyWithZone 方法:

- (id)copyWithZone:(NSZone *)zone{
    // 如果不實現下面的代碼將會變成淺拷貝
    Person *copy = [[[self class]allocWithZone:zone]init];
    copy.name = self.name;
    return copy;
}

實現后運行以下代碼:

Person *p = [[Person alloc] init];
p.name = @"小明";
Person *p1 = [p copy];

NSLog(@"%p,%p,%@,%@",p,p1,p.name,p1.name);

打印的結果是

0x1006562b0,0x10065a490,小明,小明 

可以看出通過以上代碼調用 copy 方法拷貝是深拷貝。

參考文獻:
https://juejin.im/post/6844903648380583950

dealloc

ARC下,可以不用顯式調用父類的[super dealloc],結果仍然會調用,因為在編譯期間編譯器會自動添加 [super dealloc]。
如果子類不實現dealloc方法,會自動調用父類的dealloc。如果子類實現了dealloc方法,會先調用子類的dealloc,在調用父類的dealloc。

參考文獻:
https://blog.csdn.net/s3590024/article/details/53943074

#import

#import是OC中對#include的改進版本,#import不需要寫條件編譯語句就可以確保引用的文件只會被包含一次,不會陷入遞歸包含的問題。

#import的順序
#import <系統庫>
#import <第三方庫>
#import “其他類”
// 例:
#import <UIKit/UIKit.h>
#import <Google/Analytics.h>
#import "GBOrderEmptyView.h"

盡量按照先系統類 第三方類 自己寫的類順序導入 中間不能有空格

@Class的寫法
// 建議寫法
@class class1, class2;
// 不建議寫法
@class UIPress;
@class UIPressesEvent;
#import<>與#import""

<>是指從系統庫中引用頭文件,也就是從系統庫目錄下查找,如果找不到,則結束查找。
""是先從用戶目錄下查找文件,如果找不到,則繼續在系統庫目錄下查找文件。

一般而言,<>引入的是系統庫的文件,""引入的是本地工程的文件。

#import與@class

#import會包含這個類的所有信息,包含各種變量和方法;而@class則會告訴編譯器,其后面的名稱是一個類的名稱,現在無需知道該類的定義,后面會告訴使用者的。
在類的聲明文件(.h文件)中,一般只需要知道被引用的類的名稱就可以了,不需要知道其具體實現,所以在.h文件中一般使用@class來聲明這個名稱是類的名稱;而在類的實現文件里面,因為會用到這個引用類的內部的實體變量和方法,所以需要使用#import來包含這個所引用類的頭文件。

#import對比#include的一大優勢就是不會重復引入相同的類。所以,不要在當前類的頭文件中使用#import引入其他的類,因為如果引入類的頭文件中也import了其他的雜七雜八的類,那么當前類就會引入許多根本用不到的類,這勢必會增加編譯時間。

所以,在頭文件中是用#import導入引入類,會導致如下兩個問題:
1,可能會引入許多根本用不到的內容,增加編譯時間;
2,容易引起循環導入,進而導致編譯錯誤。
因此,我們在類的頭文件中少使用import引入其他的頭文件,而是使用@class來聲明一個類。

在@interface中的大括號中聲明和@property區別

一共有三種方式
方式一:直接在@interface中的大括號中聲明。

@interface MyTest : NSObject{

    NSString *mystr;

}

方式二:在@interface中聲明,然后再在@property中聲明。

@interface MyTest :NSObject{

   NSString *_mystr;

}

@property (strong, nonatomic)NSString *mystr; 

隨后在.m文件中加入

@synthesize mystr =_myStr;

方式三:直接用@property聲明

@interface MyTest :NSObject{

}

@property (strong, nonatomic)NSString *mystr;

隨后在.m文件中加入(也可以不加,xcode會自動生成)

@synthesize mystr= _myStr;

首先來說一下方式一根方式三的區別,使用方式一聲明的成員變量是只能在自己類內部使用的,而不能在類的外部使用,(就是通過類名. 點的方式是顯示不出來的),方式三則相反,它可以在類的外部訪問,在類的內部可以通過下劃線+變量名或者self.變量名的方式來訪問。

方式二的寫法是一種過時的聲明變量的方式,xcode在早期@systhesize沒有自動合成屬性器之前,需要手寫

getter與setter方法,下劃線從風格上表明這是類的內部變量,要是需要直接使用變量則需要使用get或者set的方式。

在XCode目前有了自動合成屬性器后,編譯器會自動幫我們生成一個以下劃線開頭的的實例變量,所以我們不必去同時聲明屬性與變量。我們可以直接用@property的方式來聲明一個成員屬性,在.m文件中使不使用@systhesize都無所謂,xcode會自動幫你生成getter與setter.

目前推薦方式三,這是是蘋果開發模板所推薦的,也可以在.m文件中不加@systhesize。

參考文獻:
https://blog.csdn.net/baidu_31071595/article/details/50830325

數組

二維數組

首先,OC中是沒有二維數組的。二維數組是通過一位數組的嵌套實現的。
通過字面量創建和使用二維數組(推薦)

// 1.字面量創建二維數組并訪問(推薦)
NSArray *array2d = @[
                     @[@11,@12,@13],
                     @[@21,@22,@23],
                     @[@31,@32,@33]
                     ];
// 字面量訪問方式(推薦)
NSLog(@"array2d[2][2]:%@",array2d[2][2]);
// 數組對象函數訪問
NSLog(@"array2d[2][2]:%@",[[array2d objectAtIndex:2] objectAtIndex:2]);

協議

在.h文件中聲明協議,并且聲明代理
協議需要繼承于 基協議 <NSObject>

@protocol Define <NSObject>
// 協議方法
// @required:表明某方法必須實現
@required
-(void)update:(int)value; //這個方法必須實現
// @optional:表明某個方法可選擇實現
@optional
-(void) download;  //這個方法可以選擇實現,也可以不實現
@end

聲明協議對象

@property (nonatomic, weak) id<Define> viewDelegate;

調用協議時,需要先判斷協議是否實現。

if ([self.viewDelegate respondsToSelector:@selector(headerViewClicked:isOpen:)]) {
    // 判斷有實現之后,在調用協議
}

為什么聲明協議對象時,要用id<協議名>

因為這個協議中定義了一些基本的方法,由于我們使用的所有類都繼承NSObject這個基類,而這個基類遵守了<NSObject>這個協議,那么也就實現了其中的那些方法,這些方法當然可以由NSObject及其子類對象調用,但是在不知道遵守者類型的時候需要用到id <協議名>這樣的指針,這個指針在編譯期并不知道自己指向哪個對象,唯一能調用的便是協議中的方法,然而有時候又需要用一些基本的方法,比如要辨別id <協議名>這個指針所指的對象屬于哪個類,就要用到-isMemberOf:這個方法,而這個方法是<NSObject>這個協議中的方法之一,所以,我們自定義的協議都需要繼承<NSObject><NSObject>中的方法在NSObject基類中實現了,那么無需再關心實現了,直接調用<NSObject>中的方法吧。

參考文獻:
http://www.lxweimin.com/p/614300d8bb1e

for循環

1.1 最常用的循環方式
NSArray *testArray = @[@1,@2,@3,@4];
for(int a = 0; a < testArray.count; a++){
    NSLog(@"%@",testArray[a]);
}

這段代碼最大的問題就是循環每進行一次我們都會調用數組的計數方法. 數組的總數是不會改變的,因此每次都去調用一下這種做法是多余的。常用的優化如下:

NSArray *testArray = @[@1,@2,@3,@4];
NSUInteger count = testArray.count;
for(int a = 0; a < count; a++){
    NSLog(@"%@",testArray[a]);
}
1.2 快速枚舉

快速枚舉是在 Objective-C 2.0 中作為傳統的NSEnumerator的更便利的替代方法而引入的,新的方法順便帶來了一種新的循環語法, for…in 循環。

NSArray *testArray = @[@1,@2,@3,@4];
for(id num in testArray){
    NSLog(@"%@",num);
}

要注意的是使用for in快速枚舉NSMutableArray這類可變對象時要注意不能對容器進行修改,否則會導致遍歷器拋出異常導致程序崩潰。

對上述三個循環進行性能測試

我們這里用命令行運行這段代碼以排除任何干擾最終結果的隱藏在幕后的保留或者排除處理。

clang -framework Foundation main.m -o main

static const NSUInteger arrayItems = 10000000;
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:arrayItems];
    for (int i = 0; i < arrayItems; i++) [array addObject:@(i)];
    array = [array copy];
    
    CFTimeInterval start = CFAbsoluteTimeGetCurrent();
    // 常用的for循環
    for (NSUInteger i = 0; i < [array count]; i++){
        id object = array[i];
        
    }
    CFTimeInterval forLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"For loop: %g", forLoop - start);
    
    //優化過的常用for循環
    NSUInteger count = [array count];
    for (NSUInteger i = 0; i <  count; i++){
        id object = array[i];
        
    }
    CFTimeInterval forLoopWithCountVar = CFAbsoluteTimeGetCurrent();
    NSLog(@"Optimized for loop: %g", forLoopWithCountVar - forLoop);
    

    CFTimeInterval enumeratorLoop = CFAbsoluteTimeGetCurrent();
    // 快速枚舉
    for (id object in array){
        
    }
    CFTimeInterval forInLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"For…in loop: %g", forInLoop - enumeratorLoop);
    }

運行結果如下:



由此可以看出for in的性能最高,優化了的普通循環也要比正常的快很多?;谖覀兯姡绻衅渌囊蛩囟家粯拥脑?,在循環遍歷數組時你應該嘗試去使用for...in循環。至于 NSSet 和 NSDictionary,也可以測試一下,結果是一樣的。

參考文獻:
http://www.lxweimin.com/p/a1d6d52589fe

NSUInteger, NSInteger, int, NSNumber區別

  • 當需要使用int類型的變量的時候,可以像寫C的程序一樣,用int,也可以用NSInteger,但更推薦使用NSInteger,因為這樣就不用考慮設備是32位的還是64位的。(以后使用NSInteger,因為蘋果現在必須讓程序支持64位的,所以以后不要在使用int 來定義變量了)
  • 蘋果的官方文檔中總是推薦用NSInteger。NSInteger是一個封裝,它會識別當前操作系統的位數,自動返回最大的類型。當你不知道你的操作系統是什么類型的時候,你通常會想要使用NSInteger,所以或許你想要你的int類型范圍盡可能的大,用NSInteger,32位系統NSInteger是一個int,即32位,但當時64位系統時,NSInteger便是64位的?!跃褪且话阃扑]用NSInteger的。
  • NSUInteger是無符號的,即沒有負數,NSInteger是有符號的。
  • 有人說既然都有了NSInteger等這些基礎類型了為什么還要有NSNumber?它們的功能當然是不同的。

NSInteger是基礎類型,但是NSNumber是一個類。如果想要存儲一個數值,直接用NSInteger是不行的,比如在一個Array里面這樣用:

NSArray *array = [[NSArray alloc]init];

[array addObject:3];//會編譯錯誤

這樣是會引發編譯錯誤的,因為NSArray里面放的需要是一個類,但‘3’不是。這個時候需要用到NSNumber:

NSArray *array = [[NSArray alloc]init];

[array addObject:[NSNumber numberWithInt:3]];

Cocoa提供了NSNumber類來包裝(即以對象形式實現)基本數據類型。

例如以下創建方法:

  • (NSNumber *) numberWithChar: (char) value;

  • (NSNumber *) numberWithInt: (int) value;

  • (NSNumber *) numberWithFloat: (float) value;

  • (NSNumber *) numberWithBool: (BOOL) value;

將基本類型數據封裝到NSNumber中后,就可以通過下面的實例方法重新獲取它:

  • (char) charValue;

  • (int) intValue;

  • (float) floatValue;

  • (BOOL) boolValue;

  • (NSString *) stringValue;

參考文獻:
http://www.lxweimin.com/p/5a7f3d0c428b
http://www.lxweimin.com/p/2abaedc8e8a4

字典

初始化:
1、初始化方法

//Value值xiaochen  key值xc
NSDictionary *dic = [[NSDictionary alloc]initWithObjectsAndKeys:@"xiaochen",@"xc",@"xiaoyun",@"xy",@"xiaohong",@"xh",nil];

2、遍歷構造器

//Value值xiaochen  key值xc
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:@"xiaochen",@"xc",@"xiaoyun",@"xy",@"xiaohong",@"xh",nil];

3、字面量

//字面量key值在前 value在后
NSDictionary *dicts = @{@"name":@"xiaochen",@"gender":@"man",@"age":@"19"};

獲取字典長度

NSUInteger i = dicts.count;

根據Key值獲取value值

    //value 只要是對象就可以,但是key必須要遵從NSCoying協議,字符串默認是遵從的
    NSString *str = [dicts objectForKey:@"name"];
    NSLog(@"name = %@",str);   // name = xiaochen

    NSString *str1 = [dicts objectForKey:@"gender"];
    NSLog(@"gender = %@",str1);  //gender = man

    NSString *str2 = [dicts objectForKey:@"age"];
    NSLog(@"age = %@",str2);   //age = 19

獲取所有的Key值和所有的value值

    NSArray *arr = [dicts allKeys];  //獲取所有的Key值
    NSLog(@"%@",arr);
    /*(
     name,
     age,
     gender
     )*/
    NSArray *ar =[dicts allValues];//獲取所有的value值
    NSLog(@"%@",ar);
    /*(
     xiaochen,
     19,
     man
     )*/

可變字典
初始化

    NSMutableDictionary *dic = [[NSMutableDictionary alloc]initWithCapacity:10];

遍歷構造器

    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:10];

字面量

    NSMutableDictionary *dicts = [@{@"key1":@"value1",@"key2":@"value2"}mutableCopy];

添加元素

    [dicts setObject:@"value3"forKey:@"key3"];

修改value值

    [dicts setObject:@"value10"forKey:@"key1"];

刪除

    [dicts removeObjectForKey:@"key1"];

增與改:

[self.sourceData setValue:@[] forKey:key];
self.sourceData[key] = @[];

賦值其他字典

// 復制后不可變
dic0 = [dic1 copy];
// 復制后可變
dic0 = [dic1 mutableCopy];

集合

特點:無序的 有互異性
初始化方法

    NSSet *set1 =[[NSSet alloc]initWithObjects:@"dic",@"bb",@"cc",@"aa",@"bb",@"cc",nil];

遍歷構造器

    NSSet *set2 =[NSSet setWithObjects:@"aa",@"bb",@"cc",@"dd",@"se",nil];

獲取集合中的元素

 NSString *str= [set2 anyObject];//隨機獲取元素

可變集合
初始化方法

NSMutableSet *set = [[NSMutableSet alloc] initWithCapacity:10];

添加元素

    [set addObject:@"dic"];

刪除集合元素

 [set removeObject:@"ff"];

參考文獻:
http://www.lxweimin.com/p/53e19fa1da17

ViewController相關

1.1 preferredInterfaceOrientationForPresentation
// 默認的屏幕方向(當前ViewController必須是通過模態出來的UIViewController(模態帶導航的無效)方式展現出來的,才會調用這個方法)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationPortrait;
}
1.2 屏幕旋轉

控制頁面旋轉的方式可以總結為兩種,第一種是通過全局設置來控制,第二種是頁面自己單獨控制。
一:修改全局設置來實現
第一種是通過勾選方向讓頁面支持旋轉。


第二種是通過修改info.plist文件Supported interface orientations設置選項,增加方向讓頁面支持旋轉。

第三種是通過在AppDelegate中實現- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window方法讓頁面支持旋轉。

- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    //返回你需要的方向
    return UIInterfaceOrientationMaskPortrait;
}

這三種方式中,前面兩種是一樣的,需要注意的是第三種方式,它的優先級是最高的,也就是你通過AppDelegate這種方式來控制全局旋轉,同時勾選或者修改了plist選項,最終會以AppDelegate中支持的方向為準。

全局控制這種方式,通常用在所有頁面都需要支持全屏的情況下,如果要讓某個頁面支持,大部分頁面不支持,又該怎么處理呢?在這里利用runtime動態替換方法和分類的特性,來實現單獨控制頁面旋轉,經過封裝后,一句話就可以達到讓頁面支持或者不支持旋轉。

AppDelegate分類代碼中實現- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window方法,利用分類的特性來完成AppDelegate需要實現的代碼

- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
    //返回你需要的方向
    return UIInterfaceOrientationMaskPortrait;
}

UIViewController分類中利用runtime動態替換方法實現控制頁面旋轉,這里使用week是因為頁面銷毀的時候需要將其他控制器的方向還原,不被當前頁面修改方向后影響。

- (void)isNeedRotation:(BOOL)needRotation{
    AppDelegate * appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    __weak __typeof(self) weakSelf = self;
    IMP originalIMP = method_getImplementation(class_getInstanceMethod([appDelegate class], @selector(application:supportedInterfaceOrientationsForWindow:)));
    
    IMP newIMP = imp_implementationWithBlock(^(id obj, UIApplication *application, UIWindow *window){
        if (!weakSelf) {
            class_replaceMethod([appDelegate class], @selector(application:supportedInterfaceOrientationsForWindow:), originalIMP, method_getTypeEncoding(class_getInstanceMethod([appDelegate class], @selector(application:supportedInterfaceOrientationsForWindow:))));
        }
        return needRotation ? UIInterfaceOrientationMaskAll : UIInterfaceOrientationMaskPortrait;
    });
    
    class_replaceMethod([appDelegate class], @selector(application:supportedInterfaceOrientationsForWindow:), newIMP, method_getTypeEncoding(class_getInstanceMethod([appDelegate class], @selector(application:supportedInterfaceOrientationsForWindow:))));
}

二:通過每個頁面單獨控制頁面旋轉
通過每個頁面單獨控制頁面旋轉,首先必須打開全局方向設置,設置需要旋轉的方向,然后根據頁面不同的創建方式(push或者present)和不同根控制器(UITabBarController或者UINavigationController),可以分出三種情況。

第一種情況,頁面通過UINavigationController+push創建,在這種情況下,需要在UINavigationController實現以下方法就可以使頁面支持旋轉。

// 是否支持自動轉屏
- (BOOL)shouldAutorotate {
    return [self.topViewController shouldAutorotate];
}
// 支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return [self.topViewController supportedInterfaceOrientations];
}

第二種情況,頁面通過UITabBarController+push創建,在這種情況下,需要在UITabBarController,UINavigationController都實現以下方法才可以讓頁面支持旋轉。其中需要注意的是UITabBarController中,需要利用runtime動態替換系統方法,防止沒有初始值造成越界。

+ (void)load {
    SEL selectors[] = {
        @selector(selectedIndex)
    };
    for (NSUInteger index = 0; index < sizeof(selectors) / sizeof(SEL); ++index) {
        SEL originalSelector = selectors[index];
        SEL swizzledSelector = NSSelectorFromString([@"cl_" stringByAppendingString:NSStringFromSelector(originalSelector)]);
        Method originalMethod = class_getInstanceMethod(self, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
        if (class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
            class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    }
}
- (NSInteger)cl_selectedIndex {
    NSInteger index = [self cl_selectedIndex];
    if (index > self.viewControllers.count){
        return 0;
    }else{
        return index;
    }
}
- (BOOL)shouldAutorotate {
 return [self.selectedViewController shouldAutorotate];
}
- (NSUInteger)supportedInterfaceOrientations {
 return [self.selectedViewController supportedInterfaceOrientations];
}

第三種情況,頁面通過present創建,需要在present出來的頁面實現以下方法才可以讓頁面支持旋轉。

// 是否支持自動轉屏
- (BOOL)shouldAutorotate {
    return NO;
}
// 支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskPortrait;
}
// 默認的屏幕方向(當前ViewController必須是通過模態出來的UIViewController(模態帶導航的無效)方式展現出來的,才會調用這個方法)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationPortrait;
}

頁面自己單獨控制旋轉,需要每個頁面都寫重復的代碼,很多時候都是利用基類來實現,但是需要子頁面繼承,對代碼還是有一定的影響。既不想寫基類,又不想每個頁面單獨寫代碼,又該怎么來實現呢?在這里還是利用分類的特性,分別創建UITabBarController,UINavigationController,UIViewController的分類來實現。

UITabBarController分類中的代碼。

// 是否支持自動轉屏
- (BOOL)shouldAutorotate {
    UIViewController *vc = self.viewControllers[self.selectedIndex];
    if ([vc isKindOfClass:[UINavigationController class]]) {
        UINavigationController *nav = (UINavigationController *)vc;
        return [nav.topViewController shouldAutorotate];
    } else {
        return [vc shouldAutorotate];
    }
}
// 支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    UIViewController *vc = self.viewControllers[self.selectedIndex];
    if ([vc isKindOfClass:[UINavigationController class]]) {
        UINavigationController *nav = (UINavigationController *)vc;
        return [nav.topViewController supportedInterfaceOrientations];
    } else {
        return [vc supportedInterfaceOrientations];
    }
}
// 默認的屏幕方向(當前ViewController必須是通過模態出來的UIViewController(模態帶導航的無效)方式展現出來的,才會調用這個方法)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    UIViewController *vc = self.viewControllers[self.selectedIndex];
    if ([vc isKindOfClass:[UINavigationController class]]) {
        UINavigationController *nav = (UINavigationController *)vc;
        return [nav.topViewController preferredInterfaceOrientationForPresentation];
    } else {
        return [vc preferredInterfaceOrientationForPresentation];
    }
}

UINavigationController分類中的代碼。

// 是否支持自動轉屏
- (BOOL)shouldAutorotate {
    return [self.topViewController shouldAutorotate];
}
// 支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return [self.topViewController supportedInterfaceOrientations];
}
// 默認的屏幕方向(當前ViewController必須是通過模態出來的UIViewController(模態帶導航的無效)方式展現出來的,才會調用這個方法)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return [self.topViewController preferredInterfaceOrientationForPresentation];
}

UIViewController分類中的代碼。

/**
 * 默認所有都不支持轉屏,如需個別頁面支持除豎屏外的其他方向,請在viewController重寫下邊這三個方法
 */
// 是否支持自動轉屏
- (BOOL)shouldAutorotate {
    return NO;
}
// 支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskPortrait;
}
// 默認的屏幕方向(當前ViewController必須是通過模態出來的UIViewController(模態帶導航的無效)方式展現出來的,才會調用這個方法)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationPortrait;
}

參考文獻:
https://juejin.im/post/6844903528087945230

生命周期

viewWillLayoutSubviews和viewDidLayoutSubviews
loadView、viewDidLoad及viewDidUnload的關系
  • loadView
    調用時間:
    每次訪問UIViewController的view(比如controller.view、self.view)而且view為nil,loadView方法就會被調用。
    作用:
    loadView方法是用來負責創建UIViewController的view
    默認實現:
    1、它會先去查找與UIViewController相關聯的xib文件,通過加載xib文件來創建UIViewController的view。
    如果在初始化UIViewController指定了xib文件名,就會根據傳入的xib文件名加載對應的xib文件
    [[MJViewController alloc] initWithNibName:@"MJViewController" bundle:nil];
    如果沒有明顯地傳xib文件名,就會加載跟UIViewController同名的xib文件
    [[MJViewController alloc] init]; // 加載MJViewController.xib
    2、如果沒有找到相關聯的xib文件,就會創建一個空白的UIView,然后賦值給UIViewController的view屬性,大致如下
self.view = [[[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame] autorelease];
// applicationFrame的值是:{{x = 0, y = 20}, {width = 320, height = 460}}
  • viewDidLoad
    調用時間:
    無論你是通過xib文件還是重寫loadView方法創建UIViewController的view,在view創建完畢后,最終都會調用viewDidLoad方法
    作用:
    一般我們會在這里做界面上的初始化操作,比如往view中添加一些子視圖、從數據庫或者網絡加載模型數據裝配到子視圖中。
  • viewDidUnload
    調用時間:
    當內存警告時,UIViewController就會收到didReceiveMemoryWarning消息。didReceiveMemoryWarning方法的默認實現是:如果當前UIViewController的view不在應用程序的視圖層次結構(View Hierarchy)中,即view的superview為nil的時候,就會將view釋放,并且調用viewDidUnload方法(此方法iOS6之后以及不在調用。蘋果在文檔中建議,應該將回收內存的相關操作移到另一個回調函數:didReceiveMemoryWarning。但在實際使用中,你不需要做任何以前 viewDidUnload 的事情,更不需要把以前 viewDidUnload 的代碼移動到 didReceiveMemoryWarning 方法中。)
    作用:
    一般在釋放資源,主要是釋放界面元素相關的資源,將相關的實例都賦值為nil

參考文獻:
https://www.cnblogs.com/mjios/archive/2013/02/26/2933667.html
https://blog.devtang.com/2013/05/18/goodbye-viewdidunload/

block閉包

定義:
閉包是一個函數(或指向函數的指針),再加上該函數執行的外部的上下文變量(有時候也稱作自由變量)。

無參無返回值的定義和使用

//無參無返回值 定義 和使用
void (^MyBlockOne)(void) = ^{
      NSLog(@"無參無返回值");
};
    
// 調用
MyBlockOne();

無參有返回值的定義和使用

// 無參有返回值
int (^MyBlockTwo)(void) = ^{
    NSLog(@"無參有返回值");
    return 2;
};
// 調用
int res = MyBlockTwo();

有參無返回值的定義和使用

//有參無返回值 定義
void (^MyBlockThree)(int a) = ^(int a){
    NSLog(@"有參無返回值 a = %d",a);
};
    
// 調用
MyBlockThree(10);

有參有返回值的定義和使用

//有參有返回值
int (^MyBlockFour)(int a) = ^(int a){
    NSLog(@"有參有返回值 a = %d",a);
    return a * 2;
};
MyBlockFour(4);

block 示例

// 聲明
@property (nonatomic, copy) void(^testBlock)();
// 實現
__weak typeof(self) weakSelf = self;
self.testBlock = ^{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // weakSelf為nil時,并不會崩潰。也不會調用testBlockAction
        [weakSelf testBlockAction];
    });
};
// 調用
self.testBlock();

- (void)testBlockAction {
    DDLogDebug(@"testBlock Action", nil);
}

還有一種情況,在函數的參數傳遞閉包:

+ (void)testFunc:(UIViewController *)fromViewController completion:(void (^)(BOOL success, id  _Nonnull responseObject, NSError * _Nonnull error))completion;
// 或者
void DispatchOnce(void (^block)());

閉包中,weakSelf為nil時,并不會崩潰。weakSelf的方法也不會調用。

如果需要weakSelf不能為nil,且在block執行完畢后,才置為nil。就需要對weakSelf做一次 __strong。

__weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [NSThread sleepForTimeInterval:1];
            NSLog(@"%@",strongSelf);
        });
    };
}

使用了__strong在dispatch_async 里邊 block 代碼執行完畢之前,對self有一個引用,防止對象(self)提前被釋放。而作用域一過,strongSelf不存在了,對象(self)也會被釋放。

除了weakSelf解決block強引用外,還可以使用__block。使用 __block 關鍵字設置一個指針 vc 指向 self,重新形成一個 self → block → vc → self 的循環持有鏈。在調用結束后,將 vc 置為 nil,就能斷開循環持有鏈,從而令 self 正常釋放。

__block UIViewController *vc = self;
self.block = ^{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@", vc.name);
        vc = nil;
    });
};
self.block();

首先,block 本身不允許修改外部變量的值。但被 __block 修飾的變量會被存在了一個棧的結構體當中,成為結構體指針。當這個對象被 block 持有,就將“外部變量”在棧中的內存地址放到堆中,進而可以在 block 內部修改外部變量的值。

還有一種方式可以避免強引用環形成。就是將 self 以傳參的形式傳入 block 內部,這樣 self 就不會被 block 持用,也就不會形成循環持有鏈。

self.block = ^(UIViewController *vc){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@", vc.name);
    });
};
self.block(self);

關于weakify 和strongify
如果使用了RAC的話,建議使用:

@weakify(self);
[@[] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  @strongify(self);
}];

否則就使用:

__weak typeof(self) weakSelf = self;
[@[] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    __strong typeof(weakSelf) strongSelf = weakSelf;
}];

全局函數和全局變量

全局函數
// .h文件
/// 國際化
NSString * LDNewLocalizedString(NSString *key, ...);

/// 快捷生成UIimage
/// @param name 圖片名
UIImage *__nullable LDNewUIImage(NSString *__nullable name);

/// 單例
void DispatchOnce(void (^block)());

// .m文件
UIImage *__nullable LDNewUIImage(NSString *__nullable name) {
    return [LDNewTool getUIImage:name];
}

void DispatchOnce(void (^block)()) {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        block();
    });
}

已棄用屬性的寫法

@property (nonatomic, strong) UILabel * label_bottomWord __deprecated_msg("");

NSLog打印各種數據的方式

與Swift不同,OC打印需要指明數據的類型,所以便有此統計

// 整型占位符說明
%d : 十進制整數, 正數無符號, 負數有 “-” 符號; 
%o : 八進制無符號整數, 沒有 0 前綴; 
%x : 十六進制無符號整數, 沒有 0x 前綴; 
%u : 十進制無符號整數;
%hd  : 短整型
%ld , %lld  : 長整型

%zd   :  有符號 NSInteger型專用輸出,在iOS開發中應牢記。
%tu   :  無符號NSUInteger的輸出
%lu   :  sizeof(i)內存中所占字節數

// 字符占位符說明
%c : 單個字符輸出; 
%s : 輸出字符串;

// 浮點占位符說明
%f : 以小數形式輸出浮點數, 默認 6 位小數; 如:CGFloat
%e : 以指數形式輸出浮點數, 默認 6 位小數; 
%g : 自動選擇 %e 或者 %f 各式;

// 其它形式占位符
%p : 輸出十六進制形式的指針地址; 
%@ : 輸出 Object-C 對象; 如:NSString *

// 占位符附加字符
– l : 在整型 和 浮點型占位符之前, %d %o %x %u %f %e %g 代表長整型 和 長字符串; 
– n(任意整數) : %8d 代表輸出8位數字, 輸出總位數; 
– .n : 浮點數 限制小數位數, %5.2f 表示 5位數字 2位小數, 字符串 截取字符個數; 
– - : 字符左對齊;

參考文獻:
https://www.cnblogs.com/mukekeheart/p/11280604.html

書寫規范

nil的判斷
// 正確示例
if (objc) {
}

// 錯誤示例
if (nil == objc) {
}
編碼規范
  • 所有的方法之間空一行。
  • 所有的代碼塊之間空一行,刪除多余的注釋。
  • 所有自定義的方法需要給出注釋。
  • 盡量使用懶加載,在控制器分類時有提及和要求,其它自定義類按照控制器格式分類,沒有的分類不寫即可。
  • 代碼后的’{‘不需要獨占一行,包括方法之后,if,switch等。
  • 必須要統一的要求,屬性的定義請按照下圖property之后,空一格,括號之后空一格,寫上類名,空一格之后跟上*和屬性名。
@property (nonatomic, strong) UITableView *tableView;///< 注釋
@property (nonatomic, strong) DeliveryModel *delivery;///< 注釋
@property (nonatomic, strong) DeliveryLookAdapter *lookAdapter;///< 注釋
@property (nonatomic, strong) DeliveryLookAPIManager *lookManager;///< 注釋
  • 遵循一般代碼規范,多模仿蘋果API。
  • 刪除不用的代碼。
  • 如果有方法一直不會用到,請刪除(除工具類)。
  • 沒有執行任何業務邏輯的方法,請刪除或給予注釋,刪除多余的資。源或文件,添加必要的注釋。
  • 比較大的代碼塊需要給出注釋。
  • 方法盡量控制最多五十行。如果超過就精簡代碼 就分開方法寫,方便之后進行熱修復 代碼重構。
  • 不允許外接修改的屬性要設置readonly
  • 頭文件引入的其他類 要使用@class,不使用#import引入
結構
  • life cycle
  • 自定義方法
  • event response
  • Delegate方法實現
  • getter和setter全部放到最后

在函數分組和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/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
}
下劃線

在初始化方法里,應該使用_variableName來避免getter/setter潛在的副作用。
其他位置使用實例變量時,都應該使用self.來訪問和改變。
局部變量 不應該包含下劃線。

屬性特性

所有屬性特性應該顯式地列出來,有助于閱讀代碼。
NSString應該使用copy 而不是 strong的屬性特性。
為什么?即使你聲明一個NSString的屬性,有人可能傳入一個NSMutableString的實例,然后在你沒有注意的情況下修改它。

// 正確寫法
@property (nonatomic,copy) NSString *tutorialName;

// 錯誤寫法
@property (nonatomic,strong) NSString *tutorialName;

@property關鍵詞的使用

對象 strong

基本變量assign

XIB控件 代理 weak

字符串和block使用 copy

對于一些弱引用對象使用weak

對于需要賦值內存對象 copy

字面值

NSString, NSDictionary, NSArray, 和 NSNumber的字面值應該在創建這些類的不可變實例時被使用。請特別注意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];
點符號語法

點語法是一種很方便封裝訪問方法調用的方式。當你使用點語法時,通過使用getter或setter方法,屬性仍然被訪問或修改。
點語法的使用必須滿足兩個條件:1是屬性;2是有對應的getter和setter方法

一般情況下,滿足條件的屬性用點語法。而方法則使用中括號。

變量

變量盡量以描述性的方式來命名。單個字符的變量命名應該盡量避免,除了在for()循環。
星號表示變量是指針。例如, NSString *text 既不是 NSString* text 也不是 NSString * text,除了一些特殊情況下常量。
私有變量應該盡可能代替實例變量的使用。盡管使用實例變量是一種有效的方式,但更偏向于使用屬性來保持代碼一致性。

通過使用'back'屬性(_variable,變量名前面有下劃線)直接訪問實例變量應該盡量避免,除了在初始化方法(init, initWithCoder:, 等…),dealloc 方法和自定義的setters和getters。

// 正確寫法
@interface RWTTutorial : NSObject
@property (nonatomic, copy) NSString *tutorialName;
@end

// 錯誤寫法
@interface RWTTutorial : NSObject {
  NSString *tutorialName;
}
常量

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

// 正確寫法
static NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com";
static CGFloat const RWTImageThumbnailHeight = 50.0;

// 錯誤寫法
#define CompanyName @"RayWenderlich.com"
#define thumbnailHeight 2
枚舉類型

當使用``enum時,推薦使用新的固定基本類型規格,因為它有更強的類型檢查和代碼補全?,F在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語句包含多行代碼時,大括號應該加上。

有很多次,當相同代碼被多個cases使用時,一個fall-through應該被使用。一個fall-through就是在case最后移除'break'語句,這樣就能夠允許執行流程跳轉到下一個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;
}
私有屬性

私有屬性應該在類的實現文件中的類擴展(匿名分類)中聲明,命名分類(比如RWTPrivateprivate)應該從不使用除非是擴展其他類。匿名分類應該通過使用<headerfile>+Private.h文件的命名規則暴露給測試。

@interface RWTDetailViewController ()

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

@end
布爾值

Objective-C使用YES和NO。因為true和false應該只在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;

返回布爾值時,要返回YESNO。不能返回邏輯運算的數

// 正確寫法
- (BOOL)isBold {
  return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
}
// 正確,邏輯操作符可以直接轉化為BOOL
- (BOOL)isValid {
  return [self stringValue] != nil;
}

// 錯誤寫法
// 不要將其它類型轉化為BOOL返回。因為在這種情況下,BOOL變量只會取值的最后一個字節來賦值,這樣很可能會取到0(NO)。但是,一些邏輯操作符比如`&&`,`||`,`!`的返回是可以直接賦給BOOL的
- (BOOL)isBold {
  return [self fontTraits] & NSFontBoldTrait;
}
- (BOOL)isValid {
  return [self stringValue];
}
條件語句

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

應該:

if (!error) {
  return success;
}

不應該:

if (!error)
  return success;

或:

if (!error) return success;
三元操作符

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

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生成代碼模板的命名規則。返回類型應該使用instancetype而不是id

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

不要使用new語法。
盡管很多時候能用new代替alloc init方法,但這可能會導致調試內存時出現不可預料的問題。Cocoa的規范就是使用alloc init方法,使用new會讓一些讀者困惑。

類構造方法

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

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

在方法簽名中,應該在方法類型(-/+ 符號)之后有一個空格。在方法各個段之間應該也有一個空格(符合Apple的風格)。在參數之前應該包含一個具有描述性的關鍵字來描述參數。
多個參數的話 務必說明參數的作用
不要用and來連接兩個參數,通常and用來表示方法執行了兩個相對獨立的操作(從設計上來說,這時候應該拆分成兩個獨立的方法): 用with。

如果一個函數有特別多的參數或者名稱很長,應該將其按照:來對齊分行顯示:

-(id)initWithModel:(IPCModle)model
       ConnectType:(IPCConnectType)connectType
        Resolution:(IPCResolution)resolution
          AuthName:(NSString *)authName
          Password:(NSString *)password
               MAC:(NSString *)mac
              AzIp:(NSString *)az_ip 
             AzDns:(NSString *)az_dns  
             Token:(NSString *)token  
             Email:(NSString *)email 
          Delegate:(id<IPCConnectHandlerDelegate>)delegate;

函數調用時,和書寫格式一樣??梢园凑蘸瘮档拈L短來選擇寫在一行或者分成多行。分多行寫時,要按照:對齊各個參數。

語法糖

應該使用可讀性更好的語法糖來構造NSArray,NSDictionary等數據結構,避免使用冗長的alloc,init方法。
如果構造代碼寫在一行,需要在括號兩端留有一個空格,使得被構造的元素于與構造語法區分開來:

//正確,在語法糖的"[]"或者"{}"兩端留有空格
NSArray *array = @[ [foo description], @"Another String", [bar description] ];
NSDictionary *dict = @{ NSForegroundColorAttributeName : [NSColor redColor] };

//不正確,不留有空格降低了可讀性
NSArray* array = @[[foo description], [bar description]];
NSDictionary* dict = @{NSForegroundColorAttributeName: [NSColor redColor]};
命名規范

清晰 命名應該盡可能的清晰和簡潔,但在Objective-C中,清晰比簡潔更重要。由于Xcode強大的自動補全功能,我們不必擔心名稱過長的問題。

黃金路徑

當使用條件語句編碼時,不要嵌套太多if語句,多個返回語句也是OK。
應該:

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

  //Do something important
}

不應該:

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

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

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

不應該:

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

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

單例模式

單例對象應該使用線程安全模式來創建共享實例。

+ (instancetype)sharedInstance {
  static id sharedInstance = nil;

  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedInstance = [[self alloc] init];
  });

  return sharedInstance;
}
換行符

換行符是一個很重要的主題,因為它的風格指南主要為了打印和網上的可讀性。

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

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

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

參考文獻:
http://www.lxweimin.com/p/cb0269b88d8a
https://juejin.im/post/6844903524887691277

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。