YYModel源代碼閱讀--基礎知識

這段時間由於工做須要,閱讀了YYModel這個開源框架,至於它能作什麼,最直白的講述就是JSON與Model之間的相互轉化。git

源代碼在Github,你們能夠自行git clone或者download。程序員

接下來,筆者主要分析閱讀源代碼而引出的各類問題與知識點,不足之處請你們指正。github

NS_ASSUME_NONNULL_BEGIN & NS_ASSUME_NONNULL_END

這組宏是成對使用的,不得不說咱們本身寫代碼的時候使用的不多,以致於遺漏這個知識點,如今咱們就來看看這兩個宏會引出什麼問題。swift

這組宏會引出幾個關於Objective-C新特性的知識點:數組

  • Nullability Annotations安全

  • Lightweight Genericsapp

  • __kindof框架

Nullability Annotations

咱們都知道在swift中,可使用!和?來表示一個對象是optional的仍是non-optional,如view?和view!。而在 Objective-C中則沒有這一區分,view既可表示這個對象是optional,也可表示是non-optioanl。這樣就會形成一個問題:在 Swift與Objective-C混編時,Swift編譯器並不知道一個Objective-C對象究竟是optional仍是non-optional,所以這種狀況下編譯器會隱式地將Objective-C的對象當成是non-optional。ide

爲了解決這個問題,蘋果在Xcode 6.3引入了一個Objective-C的新特性:nullability annotations。這一新特性的核心是兩個新的類型註釋:** __nullable** 和 **__nonnull** 。從字面上咱們能夠猜到,__nullable表示對象能夠是NULL或nil,而__nonnull表示對象不該該爲空。當咱們不遵循這一規則時,編譯器就會給出警告。函數

咱們來看看如下的實例,

@interface TestNullabilityClass ()
@property (nonatomic, copy) NSArray * items;
- (id)itemWithName:(NSString * __nonnull)name;
@end
@implementation TestNullabilityClass
...
- (void)testNullability {
[self itemWithName:nil]; // 編譯器警告:Null passed to a callee that requires a non-null argument
}
- (id)itemWithName:(NSString * __nonnull)name {
return nil;
}
@end

不過這只是一個警告,程序仍是能編譯經過並運行。

事實上,在任何可使用const關鍵字的地方均可以使用__nullable__nonnull,不過這兩個關鍵字僅限於使用在指針類型上。而在方法的聲明中,咱們還可使用不帶下劃線的nullablenonnull,以下所示:

- (nullable id)itemWithName:(NSString * nonnull)name
在屬性聲明中,也增長了兩個相應的特性,所以上例中的items屬性能夠以下聲明:

@property (nonatomic, copy, nonnull) NSArray * items;
固然也能夠用如下這種方式:

@property (nonatomic, copy) NSArray * __nonnull items;
推薦使用nonnull這種方式,這樣可讓屬性聲明看起來更清晰。

Nonnull區域設置(Audited Regions)

若是須要每一個屬性或每一個方法都去指定nonnullnullable,是一件很是繁瑣的事。蘋果爲了減輕咱們的工做量,專門提供了兩個宏:NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END。在這兩個宏之間的代碼,全部簡單指針對象都被假定爲 nonnull,所以咱們只須要去指定那些nullable的指針。以下代碼所示:

NS_ASSUME_NONNULL_BEGIN
@interface TestNullabilityClass ()
@property (nonatomic, copy) NSArray * items;
- (id)itemWithName:(nullable NSString *)name;
@end
NS_ASSUME_NONNULL_END

在上面的代碼中,items屬性默認是nonnull的,itemWithName:方法的返回值也是nonnull,而參數是指定爲nullable的。

不過,爲了安全起見,蘋果還制定了幾條規則:

  • typedef定義的類型的nullability特性一般依賴於上下文,即便是在Audited Regions中,也不能假定它爲nonnull。

  • 複雜的指針類型(如id *)必須顯示去指定是nonnull仍是nullable。例如,指定一個指向nullable對象的nonnull指針,可使用」__nullable id * __nonnull」。

  • 咱們常用的NSError **一般是被假定爲一個指向nullable NSError對象的nullable指針。

兼容性

由於Nullability Annotations是Xcode 6.3新加入的,因此咱們須要考慮以前的老代碼。實際上,蘋果已以幫咱們處理好了這種兼容問題,咱們能夠安全地使用它們:

  • 老代碼仍然能正常工做,即便對nonnull對象使用了nil也沒有問題。

  • 老代碼在須要和swift混編時,在新的swift編譯器下會給出一個警告。

  • nonnull不會影響性能。事實上,咱們仍然能夠在運行時去判斷咱們的對象是否爲nil。

事實上,咱們能夠將nonnull/nullable與咱們的斷言和異常一塊兒看待,其須要處理的問題都是同一個:違反約定是一個程序員的錯誤。特別是,返回值是咱們可控的東西,若是返回值是nonnull的,則咱們不該該返回nil,除非是爲了向後兼容。

Lightweight Generics

Lightweight Generics 輕量級泛型,輕量是由於這是個純編譯器的語法支持(LLVM 7.0),和 Nullability 同樣,沒有藉助任何 objc runtime 的升級,也就是說,這個新語法在 Xcode 7 上可使用且徹底向下兼容(更低的 iOS 版本)

帶泛型的容器

這無疑是本次最重大的改進,有了泛型後終於能夠指定容器類中對象的類型了:

NSArray *strings = @[@"sun", @"yuan"];
NSDictionary *mapping = @{@"a": @1, @"b": @2};

返回值的 id 被替換成具體的類型後,使人感動的代碼提示也出來了。

假如向泛型容器中加入錯誤的對象,編譯器會不開心的。

系統中經常使用的一系列容器類型都增長了泛型支持,甚至連 NSEnumerator 都支持了,這是很是 Nice 的改進。和 Nullability 同樣,我認爲最大的意義仍是豐富了接口描述信息,對比下面兩種寫法:

@property (readonly) NSArray *imageURLs;
@property (readonly) NSArray *imageURLs;

不用多想就清楚下面的數組中存的是什麼,避免了 NSStringNSURL 的混亂。

自定義泛型類

比起使用系統的泛型容器,更好玩的是自定義一個泛型類,目前這裏還沒什麼文檔,但攔不住咱們寫測試代碼,假設咱們要自定義一個 Stack 容器類:

@interface Stack : NSObject
- (void)pushObject:(ObjectType)object;
- (ObjectType)popObject;
@property (nonatomic, readonly) NSArray *allObjects;
@end

這個 ObjectType 是傳入類型的 placeholder,它只能在 @interface 上定義(類聲明、類擴展、Category),若是你喜歡用 T 表示也 OK,這個類型在 @interface@end 區間的做用域有效,能夠把它做爲入參、出參、甚至內部 NSArray 屬性的泛型類型,應該說一切都是符合預期的。咱們還能夠給 ObjectType 增長類型限制,好比:

// 只接受 NSNumber * 的泛型
@interface Stack : NSObject
// 只接受知足 NSCopying 協議的泛型
@interface Stack> : NSObject

若什麼都不加,表示接受任意類型 ( id );當類型不知足時編譯器將產生 error。
實例化一個 Stack,一切工做正常:

對於多參數的泛型,用逗號隔開,其餘都同樣,能夠參考 NSDictionary 的頭文件。

協變性和逆變性

當類支持泛型後,它們的 Type 發生了變化,好比下面三個對象看上去都是 Stack,但實際上屬於三個 Type:

Stack *stack; // Stack *
Stack *stringStack; // Stack
Stack *mutableStringStack; // Stack

當其中兩種類型作類型轉化時,編譯器須要知道哪些轉化是容許的,哪些是禁止的,好比,默認狀況下:

Stack *stack;
Stack *stringStack;
Stack *mutableStringStack; 

stack = stringStack;
stack = mutableStringStack;
stringStack = stack;
stringStack = mutableStringStack;
mutableStringStack = stack;
mutableStringStack = stringStack

在Xcode中咱們能夠看到,不指定泛型類型的 Stack 能夠和任意泛型類型轉化,但指定了泛型類型後,兩個不一樣類型間是不能夠強轉的,假如你但願主動控制轉化關係,就須要使用泛型的協變性和逆變性修飾符了:

__covariant - 協變性,子類型能夠強轉到父類型(里氏替換原則)
__contravariant - 逆變性,父類型能夠強轉到子類型(WTF)

協變

@interface Stack<__covariant ObjectType> : NSObject

逆變

@interface Stack<__contravariant ObjectType> : NSObject

協變是很是好理解的,像 NSArray 的泛型就用了協變的修飾符。

__kindof

__kindof 這修飾符仍是很實用的,解決了一個長期以來的小痛點,拿原來的 UITableView 的這個方法來講:

- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;

使用時前面基本會使用 UITableViewCell 子類型的指針來接收返回值,因此這個 API 爲了讓開發者沒必要每次都蛋疼的寫顯式強轉,把返回值定義成了 id 類型,而這個 API 實際上的意思是返回一個 UITableViewCellUITableViewCell 子類的實例,因而新的 __kindof 關鍵字解決了這個問題:

- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;

既明確代表了返回值,又讓使用者沒必要寫強轉。再舉個帶泛型的例子,UIView 的 subviews 屬性被修改爲了:

@property (nonatomic, readonly, copy) NSArray<__kindof UIView *> *subviews;

這樣,寫下面的代碼時就沒有任何警告了:

UIButton *button = view.subviews.lastObject;

NS_ENUM & NS_OPTIONS

枚舉是指將變量的值一一列舉出來,變量的值只限於列舉出來的值的範圍內。

枚舉本質上是一個整數,枚舉的做用是把值限定在指定的範圍內,而且增長代碼的可讀性。 枚舉的成員若是沒有顯示指定值,那麼第一個成員的值老是0,後面成員的值依次遞增。枚舉能夠直接用於比較。

通常咱們聲明枚舉:

#import 

// 聲明枚舉類型
enum Direction {up, down, left = 10, right};

int main(int argc, const char * argv[]){
...
}
其中up = 0, down = 1, left = 10, right = 11。

咱們會發現枚舉中一些不可自定義的部分,例如,枚舉名。

NS_ENUM 和 NS_OPTIONS 都不算太古老的宏,在iOS 6 / OS X Mountain Lion纔開始有,它們都是代替 enum 的更好的辦法。

NS_ENUM

若是要在早期的iOS或OS X系統中使用這兩個宏,簡單定義一下就好

#ifndef NS_ENUM
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#endif

在OS X 10.4 中的原始定義以下:

#define NS_ENUM(_type, _name) CF_ENUM(_type, _name)
#define NS_OPTIONS(_type, _name) CF_OPTIONS(_type, _name)

在以前枚舉能夠這麼定義:

typedef enum {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};

或者

typedef NSInteger UITableViewCellStyle;

如今,有了統一的風格

typedef NS_ENUM(NSInteger, UITableViewCellSelectionStyle) {
UITableViewCellSelectionStyleNone,
UITableViewCellSelectionStyleBlue,
UITableViewCellSelectionStyleGray,
UITableViewCellSelectionStyleDefault
};

NS_ENUM 的第一個參數是用於存儲的新類型的類型。在64位環境下,UITableViewCellStyleNSInteger 同樣有8 bytes長。你要保證你給出的全部值能被該類型容納,不然就會產生錯誤。第二個參數是新類型的名字。大括號裏面和之前同樣,是你要定義的各類值。

NS_OPTIONS

語法和 NS_ENUM 徹底相同,但這個宏提示編譯器值是如何經過位掩碼 | 組合在一塊兒的。

typedef NS_OPTIONS(NSUInteger, AMGResizing) {
AMGResizingNone = 0,
AMGResizingFlexibleWidth = 1 << 0,
AMGResizingFlexibleHeight = 1 << 1,
AMGResizingFlexibleUnicorn = 1 << 2
};

attribute((always_inline))

咱們知道通常的函數調用都會經過call的方式來調用,這樣讓攻擊很容易對一個函數作手腳,若是是以inline的方式編譯的會,會把該函數的code拷貝到每次調用該函數的地方。而static會讓生成的二進制文件中沒有清晰的符號表,讓逆向的人很難弄清楚邏輯。

__attribute__((always_inline)) 的意思是強制內聯,全部加了__attribute__((always_inline)) 的函數再被調用時不會被編譯成函數調用而是直接擴展到調用函數體內,好比定義了函數
__attribute__((always_inline)) void a()

void b(){ 
a();
}

b 調用 a 函數的彙編代碼不會是跳轉到a執行,而是 a 函數的代碼直接在 b 內成爲 b 的一部分。
#define __inline __attribute__((always_inline)) 的意思就是用
__inline 代替__attribute__((always_inline))
內聲明a的時候能夠直接寫成__inline void a() 這樣比較方便由於__attribute__((always_inline)) 字多。

undef

這是預編譯指令,和#define搭配使用,意思是取消以前的宏定義。

#define PROC_ADD 
void main(void) 
{
#ifdef PROC_ADD 
// Do this code here then undefined it to run the code in the else 
// processing work 
#undef PROC_ADD 
#else 
// now that PROC_ADD has been undefined run this code 
// processing work 
#endif 
}

__unsafe_unretained

__unsafe_unretained是對對象的非zeroing的weak reference,意思是當對象所指向的內存被銷燬了,對象還存在,稱爲「野指針」。

在iOS引入了Automatic Reference Count(ARC)以後,編譯器能夠在編譯時對obj-c對象進行內存管理。大體規則以下:

alloc的要release;
retain/copy的要release;
NSAutoreleasePool在ARC中被禁止使用,替換成@autoreleasepool 函數體;
使用@ autoreleasepool,在函數入口的時候,autorelease pool入棧,正常退出時,autorelease pool出棧,從而釋放變量.
注意:@ autoreleasepool在非ARC模式下,也能使用,並聽說使用@autoreleasepool比使用NSAutoreleasePool速度能快6倍, 明顯提高程序性能.

@package

爲了強制一個對象隱藏其數據,編譯器限制實例變量範圍以限制其在程序中的可見性,可是爲了提供靈活性,蘋果也讓開發者顯式設置範圍。

如下是這些關鍵字的使用範圍:

  • @private

The instance variable is accessible only within the class that declares it.

實例變量只能被聲明它的類訪問.

  • @protected

The instance variable is accessible within the class that declares it and within classes that inherit it. All instance variables without an explicit scope directive have @protected scope.

實例變量能被聲明它的類和子類訪問,全部沒有顯式制定範圍的實例變量都是.

  • @public

The instance variable is accessible everywhere.

實例變量能夠被在任何地方訪問.

  • @package

Using the modern runtime, an @package instance variable has @public scope inside the executable image that implements the class, but acts like @private outside.使用modern運行時,一個@package實例變量在實現這個類的可執行文件鏡像中其實是@public的,可是在外面就是@private【runtime須要再看一下蘋果文檔Runtime Programming Guide】

The @package scope for Objective-C instance variables is analogous to private_extern for C variables and functions. Any code outside the class implementation’s image that tries to use the instance variable gets a link error.

Objective-C中的@package與C語言中變量和函數的private_extern相似。任何在實現類的鏡像以外的代碼想使用這個實例變量都會引起link error

This scope is most useful for instance variables in framework classes, where @private may be too restrictive but @protected or @public too permissive.

這個類型最經常使用於框架類的實例變量,使用@private太限制,使用@protected或者@public又太開放.

相關文章
相關標籤/搜索