本文結(jié)構(gòu)
參考孟巖老師的文章圣贸,對本文結(jié)構(gòu)如下劃分
- 基本數(shù)據(jù)類型
- 基本語法
- 數(shù)組和其他集合類
- 基本輸入輸出和文件處理艾扮,輸入輸出流類的組織
- 序列化和反序列化
- 面向?qū)ο筇匦?/li>
- 異常咧欣、錯誤處理臭墨、斷言、日志和調(diào)試支持润讥,對單元測試的支持
- RunTime
- callback方法調(diào)用转锈,事件驅(qū)動編程模型
參考鏈接
在完成本文過程中,或轉(zhuǎn)載楚殿,或參考了以下鏈接
- 匿名函數(shù)
http://blog.devtang.com/2013/07/28/a-look-inside-blocks/
http://coolshell.cn/articles/8309.html
http://www.reibang.com/p/29d70274374b
- 集合
http://blog.csdn.net/whoten/article/details/17892673
http://blog.sunnyxx.com/2014/04/30/ios_iterator/
- 屬性
http://www.devtalking.com/articles/you-should-to-know-property/
http://www.reibang.com/p/2a9c98a29685
- 斷言&錯誤&日志
http://blog.csdn.net/lcl130/article/details/41889185
http://www.reibang.com/p/6e444981ab45
- 面向?qū)ο?/li>
- 內(nèi)存
- 單元測試
https://hjgitbook.gitbooks.io/ios/content/01-thinking/01-the-basic-knowledge-of-unit-test.html
http://www.reibang.com/p/8bbec078cabe
http://www.cocoachina.com/ios/20150702/12253.html
- 類別
- 回調(diào)
http://blog.csdn.net/wzzvictory/article/details/9295317
http://www.cnblogs.com/TsengYuen/archive/2011/04/20/2022060.html
http://www.reibang.com/p/376ba5343097
https://segmentfault.com/q/1010000000387240
http://wdxtub.com/2016/02/20/dive-in-objc-1/
- 文件&流
http://www.reibang.com/p/fbb997eb032d
http://blog.csdn.net/swingpyzf/article/details/16325923
- 運行時
http://www.reibang.com/p/f73ea068efd2
http://yulingtianxia.com/page/8/
- 其他
http://blog.csdn.net/myan/article/details/3144661
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html
http://www.reibang.com/p/8b76814b3663
https://wiki.haskell.org/Cn/Introduction#Quicksort_in_Haskell
https://github.com/oa414/objc-zen-book-cn#%E9%9D%A2%E5%90%91%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B
基本數(shù)據(jù)類型
C的基本數(shù)據(jù)類型
objC作為C語言的一個超集撮慨,所有C語言支持的基本數(shù)據(jù)類型竿痰,ObjC同樣支持-
int 與 NSInteger
NSInteger的定義如下:
#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || T ARGET_OS_WIN32 || NS_BUILD_32_LIKE_64 typedef long NSInteger; typedef unsigned long NSUInteger; #else typedef int NSInteger; typedef unsigned int NSUInteger; #endif
很明顯,做了32位64位機器的移植性處理。
-
BOOL
YES 或 NO
-
float 與 CGFloat
CGFloat對于float相當于NSInteger對于int
-
NSString
后面單獨介紹砌溺。
需要注意的是:NSString類型的等號賦值做的是深拷貝
-
NSValue
NSValue是個可以和各種基本數(shù)據(jù)類型相互轉(zhuǎn)換的類影涉。
例
[NSValue valueWithCGSize:CGSizeMake(100, 100)]; [NSValue valueWithRange:NSMakeRange(0, 10)];
-
NSNumber
NSNumber與上面不同的是,NSNumber不是基本的數(shù)據(jù)類型,而是對象规伐。
繼承關(guān)系:NSNumber->NSValue->NSObject
同時NSNumber支持和NSString一樣的@符號簡寫
NSNumber * number = @(123); NSNumber * number1 = @(3.1415); NSNumber * number2 = @(YES); NSInteger intValue = [number integerValue]; CGFloat floatValue = [number1 doubleValue]; BOOL boolValue = [number2 boolValue];
基本語法
-
減號和加號
-
減號表示一個函數(shù)或消息的開始
舉個例子蟹倾,在c#中,一個方法的寫法是
private void add(bool isAdd){ ... }
用oc寫出來就是
-(void)add:(Bool)isAdd{ ... }
加號代表是類的靜態(tài)方法猖闪,不需要實例化即可調(diào)用鲜棠。
-
-
中括號
中括號可以理解為調(diào)用方法,在oc中培慌,嚴格來說豁陆,應(yīng)該表述為發(fā)消息
具體理解如下:
因為在Objective-C中,message與方法是在執(zhí)行階段綁定的吵护,而不是編譯階段盒音。簡單的說 [a someFunc] 這樣一個調(diào)用,在編譯階段馅而,編譯器并不知道someFunc要執(zhí)行哪段代碼祥诽。這個時候[a someFunc]會被轉(zhuǎn)換為 objc_msgSend(a, "someFunc"),字面的意思也很容易理解瓮恭,就是給a這個instance雄坪,發(fā)“someFunc”這個消息,以selector的形式偎血。在運行階段,執(zhí)行到上述的objc_msgSend這個函數(shù)時盯漂。函數(shù)內(nèi)部會到a對應(yīng)的內(nèi)存地址颇玷,尋找someFunc這個方法的地址,并執(zhí)行就缆。如果找不到帖渠,就會拋一個“unknown selector sent to instance”的異常。(比如.h中聲明了方法竭宰,但.m中沒有實現(xiàn)空郊,就可以重現(xiàn)這個錯誤) 所以嚴格意義上來將,任何Objective C的函數(shù)調(diào)用切揭,編譯階段的表現(xiàn)狞甚,都只能算一種“發(fā)消息”的行為。
深入理解可見:Objective-C 消息發(fā)送與轉(zhuǎn)發(fā)機制原理
總之廓旬,從語法層面上來說哼审。
在C#中,我們這樣寫:
this.hello(true);
在oc中,我們這樣寫:
[self hello:YES];
-
#import @interface
-
#import 和 #include
#import可以認為是#include的升級版涩盾,使用#import可以保證頭文件不會重復(fù)引用十气。
在c中,防止頭文件的重復(fù)引用春霍,常吃椅鳎可見類似如下代碼
#ifndef xxx_H #define xxx_H #include "xxx.h" #endif
通常建議是:oc文件使用#import形式,c\c++文件使用#include形式址儒。
-
@interface 和 @implementation
用一個簡單的例子理解芹枷,定義一個老鷹捉小雞類
使用c#
public class Chicken : System{ private string ckName = "chick"; private int ckSize = 15; private bool IsCaught(){ return true; } }
使用oc
- Chicken.h
@interface Chicken : NSObject{ NSString *ckName; int ckSize; } -(BOOL)IsCaught:; @end
- Chicken.mm
#import "Chicken.h" @implementation Chicken -(void)init{ ckName=@"chick"; ckSize=15; } -(BOOL) IsCaught:{ return YES; } @end
-
-
參數(shù)格式以及參數(shù)的傳遞
-
多個參數(shù)的寫法
(方法的數(shù)據(jù)類型)方法名:(參數(shù)1數(shù)據(jù)類型)參數(shù)1數(shù)值名 參數(shù)2名:(參數(shù)2數(shù)據(jù)類型)參數(shù)2數(shù)值名 參數(shù)3名:(參數(shù)3數(shù)據(jù)類型)參數(shù)3數(shù)值名 ...
-
參數(shù)傳遞
舉個例子
[[[MyClass alloc] init:[foo bar]] autorelease]
對應(yīng)于
MyClass.alloc().init(foo.bar()).autorelease()
-
數(shù)組和其他集合類
Foundation framework中用于收集cocoa對象(NSObject對象)的三種集合
NSArray 用于對象有序集合(數(shù)組)
NSSet 用于對象無序集合 (集合)
NSDictionary用于鍵值映射(字典)
以上三種集合類是不可變的(一旦初始化后,就不能改變)
對應(yīng)的可變集合類(這三種可變集合類是對應(yīng)上面三種集合類的子類):
NSMutableArray
NSMutableSet 可修改的集合离福。主要用于集合運算(并集杖狼,交集,差集)
NSMutableDictionary 允許用戶添加和刪除key和value
這些集合類只能容納cocoa對象(NSOjbect對象)妖爷,如果想保存一些原始的C數(shù)據(jù)(例如蝶涩,int, float, double, BOOL等),則需要將這些原始的C數(shù)據(jù)封裝成NSNumber類型進行存儲絮识。NSNumber對象是cocoa對象绿聘,可以被保存在集合類中。
遍歷
-
索引
NSArray *array = [NSArray arraywithobjects:@"1",@"2",@"3",@"4",nil];
NSUInteger count = [array count];
for (int i = 0 ; i ! = count;i++){
id obj = [array objectAtIndex:i];
//自定義code...
}
- 迭代器
NSEnumerator *enumerator = [array objectEnumerator];
id obj = nil;
while(obj = [enumerator nextobject]){
//自定義code
}
- 快速枚舉
for(id obj in array){
//自定義code
}
***字典使用快速枚舉時次舌,得到的obj是key而不是keypair***
- 代碼塊
為什么使用代碼塊熄攘,因為代碼塊可以讓循環(huán)操作并發(fā)執(zhí)行。而上面的三種方式都是線性操作彼念。
```
[array enumerateObjectsUsingBlock:^(id obj,NSUInteger idx,BOOL *stop){
//obj為取出的對象挪圾,idx為對應(yīng)的下標
}];
```
```
if(idx == 1){
*stop = YES;
}
```
- 技巧
1. 倒序遍歷
NSArray和NSOrderedSet都支持使用reverseObjectEnumerator倒序遍歷,如:
```
NSArray *strings = @[@"1", @"2", @"3"];
for (NSString *string in [strings reverseObjectEnumerator]) {
NSLog(@"%@", string);
}
```
這個方法只在循環(huán)第一次被調(diào)用逐沙,所以也不必擔心循環(huán)每次計算的問題哲思。
同時,使用enumerateObjectsWithOptions:NSEnumerationReverse也可以實現(xiàn)倒序遍歷:
```
[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(Sark *sark, NSUInteger idx, BOOL *stop) {
[sark doSomething];
}];
```
2.使用block同時遍歷字典key吩案,value
block版本的字典遍歷可以同時取key和value(forin只能取key再手動取value)棚赔,如:
```
NSDictionary *dict = @{@"a": @"1", @"b": @"2"};
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(@"key: %@, value: %@", key, obj);
}];[]()
```
## 基本輸入輸出和文件處理,輸入輸出流類的組織
- 輸入輸出
兼容C的scanf徘郭,printf靠益,不再敘述。
- 文件處理
- 基本讀寫操作
Objective-C使用NSFileHandle類對文件進行基本操作,iOS文件操作
NSFileHandle類中得方法可以對文件進行基本的讀寫残揉,偏移量的操作胧后。
NSFileHandle基本步驟:
1. 打開文件,獲取一個NSFileHandle對象抱环。
2. 對打開NSFileHandle的文件對象行I/O操作
3. 關(guān)閉文件對象
- 簡單對象的讀寫(I/O)操作
iOS中提供四種類型(包括其子類型)可以直接進行文件存燃薄:
1. NSString
2. NSDictionary
3. NSArray
4. NSData
其基本操作如下:
```
// 在Documents下面創(chuàng)建一個文本路徑途样,假設(shè)文本名稱為objc.txt
NSString *txtPath = [docPath stringByAppendingPathComponent:@"objc.txt"]; // 此時僅存在路徑,文件并沒有真實存在
NSString *string = @"Objective-C";
// 字符串寫入時執(zhí)行的方法
[string writeToFile:txtPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"txtPath is %@", txtPath);
// 字符串讀取的方法
NSString *resultStr = [NSString stringWithContentsOfFile:txtPath encoding:NSUTF8StringEncoding error:nil];
NSLog(@"resultStr is %@", resultStr);
```
- 文件管理器
使用文件管理器(NSFileManager)可以實現(xiàn)對文件進行操作(創(chuàng)建濒憋、刪除何暇、改名等)以及文件信息的獲取
- 流
使用Cocoa框架中的輸入輸出流,可以從文件或應(yīng)用中內(nèi)存讀取數(shù)據(jù)凛驮,也可以向文件/應(yīng)用中內(nèi)存寫入數(shù)據(jù)裆站。還可以用于socket的數(shù)據(jù)交互處理。
其主要類與方法如下:
![NSStream的主要類與方法](http://upload-images.jianshu.io/upload_images/6836572-9f75de0b6c31847d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
## 序列化和反序列化
前面提到過黔夭,NSArray,NSDictionary,NSString,NSNumber,NSDate,NSData以及它們的可變版本(指NSMutableArray,NSMutableDictionary...這一類) 宏胯,都可以方便的將自身的數(shù)據(jù)以某種格式(比如xml格式)序列化后保存成本地文件。
但是如果用于存放數(shù)據(jù)的類是自己定義的本姥,并不是上面這些預(yù)置的對象肩袍,如自定以的Person類,像這種自定義的類是無法在程序內(nèi)部通過writeToFile這個方法寫入到文件內(nèi)
既然復(fù)雜對象無法使用內(nèi)部方法進行數(shù)據(jù)持久化婚惫,那么只能通過將復(fù)雜對象轉(zhuǎn)換成NSData氛赐,然后在通過上面的方法寫入文件,而這種轉(zhuǎn)換的步驟就被稱為歸檔先舷,從文件中讀取NSData數(shù)據(jù)艰管,將NSData轉(zhuǎn)換為復(fù)雜對象,這個步驟就是反歸檔蒋川。
- 要點
- 復(fù)雜對象寫入文件的過程(復(fù)雜對象->歸檔->NSData->writeToFile)
- 從文件中讀取出復(fù)雜對象過程(讀取文件->NSData->反歸檔->復(fù)雜對象
- 實現(xiàn)步驟
1. 首先牲芋,復(fù)雜對象所屬的類要遵守<NSCoding>
2. 其次,實現(xiàn)協(xié)議中的兩個方法:
- -(void)encodeWithCoder:(NSCoder *)aCoder; 序列化
- -(id)initWithCoder:(NSCoder *)aDecoder; 反序列化
- 例子
1. 首先捺球,遵守NSCoding協(xié)議
```
@interface Person:NSObject<NSCoding>
@property(nonatomic,copy) NSString *name
@property(nonatomic,assign) integer age;
@end
```
2. 其次缸浦,實現(xiàn)協(xié)議中的兩個方法:
```
// 對person對象進行歸檔時,此方法執(zhí)行氮兵。
// 對person中想要進行歸檔的所有屬性裂逐,進行序列化操作。
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
// 對person對象進行反歸檔時胆剧,該方法執(zhí)行絮姆。
// 創(chuàng)建一個新的person對象醉冤,所有屬性都是通過反序列化得到的秩霍。
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
// 準備一個NSMutableData, 用于保存歸檔后的對象
NSMutableData *data = [NSMutableData data];
// 創(chuàng)建歸檔工具
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingMutableData:data];
// 歸檔
[archiver encodeObject:p] forKey:@"p1"];
// 結(jié)束
[archiver finishEncoding];
// 拼音寫入沙盒路徑
NSString *caches = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [caches stringByAppendingPathComPonent:@"person"];
// 寫入沙盒
[data writeToFile:filePath atomically:YES];
// 反歸檔
// 從filePath文件路徑讀取
NSData *data = [NSData dataWithContentsOfFile:filePath];
// 反歸檔工具
NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
// 反歸檔成對象
Person *p2 = [unArchiver decodeObjectForKey:@"p1"];
// 反歸檔結(jié)束
[unArchiver finshDeoding];
```
## 面向?qū)ο筇匦?
- 繼承&多態(tài)
objc的這兩個特性除了使用語法上,與其他oo語言沒有什么什么不同蚁阳×迦蓿基本語法之前也有提過,@interface用于聲明一個類螺捐,@implementation用于類的定義颠悬。
不過需要值得注意的一點是矮燎,由于objc的動態(tài)消息傳遞機制,objc中不存在真正意義上的私有方法赔癌。但是如果方法不再.h文件中聲明诞外,而只在.m文件中實現(xiàn),基本上也和私有方法差不多灾票。
所以在使用objc時峡谊,一般會將公有的放到.h文件中,將私有的放到.m文件中刊苍。實現(xiàn)私有一般用類擴展實現(xiàn)既们。
- 屬性
在屬性方面,objc相比于其他oo語言正什,有自己的語法糖啥纸,@Property和@synthesize忧设。簡單來說荆萤,.h文件中使用@Property, .m文件中使用@synthesize劳坑,編譯器會自動創(chuàng)建成員屬性以及對應(yīng)的get和set方法莹妒。
比較值得關(guān)注的名船,是@Property的幾個特性。
- 原子性
1. atomic(默認使用)旨怠。使用該選項渠驼,保證調(diào)用者對變量的訪問是線程安全的,在多線程環(huán)境下會返回一個多多個值(其他線程前后)鉴腻,而不是一個垃圾值迷扇。
2. noaomic。如其名爽哎,使用該關(guān)鍵字后蜓席,不能保證線程安全,但是性能及訪問效率優(yōu)于前值课锌。所以在單線程環(huán)境下一般指定該關(guān)鍵字厨内。
- 寄存器控制
1. readwrite(默認):readwrite是默認值,表示該屬性同時擁有setter和getter渺贤。
2. readonly: readonly表示只有g(shù)etter沒有setter雏胃。
- 內(nèi)存管理
1. assign(默認):assign用于值類型,如int志鞍、float瞭亮、double和NSInteger,CGFloat等表示單純的復(fù)制固棚。
其在set的實現(xiàn)统翩,是采用直接賦值來實現(xiàn)設(shè)值操作的
```
-(void)setVar:(int)newVar{
var= newVar;
}
```
2. retain:在set方法中仙蚜,需要對傳入的對象進行引用計數(shù)加1的操作
簡單來說,就是對傳入的對象擁有所有權(quán)厂汗,只要對該對象擁有所有權(quán)委粉,該對象就不會被釋放。如下代碼所示:
```
-(void)setName:(NSString*)_name{
if ( name != _name){
[name release];
name = [_name retain];
}
}
```
首先判斷是否與舊對象一致娶桦,如果不一致進行賦值艳丛。之所以要增加if判斷,是因為如果是同一個對象的話趟紊,進行if內(nèi)的代碼會造成一個極端的情況:當此name的retain為1時氮双,使此次的set操作讓實例name提前釋放,而達不到賦值目的
3. strong:表示實例變量對傳入的對象要有所有權(quán)關(guān)系霎匈,即強引用戴差。strong跟retain的意思相同并產(chǎn)生相同的代碼,但是語意上更好更能體現(xiàn)對象的關(guān)系铛嘱。
4. weak:在set方法中暖释,需要對傳入的對象不進行引用計數(shù)加1的操作。
簡單來說墨吓,就是對傳入的對象沒有所有權(quán)球匕,當該對象引用計數(shù)為0時,即該對象被釋放后帖烘,用weak聲明的實例變量指向nil亮曹,即實例變量的值為0。
5. copy:與strong類似秘症,但區(qū)別在于實例變量是對傳入對象的副本擁有所有權(quán)照卦,而非對象本身
- Category
這算是oc的一個比較有意思特性。如果我們想給一個已存在的乡摹、很復(fù)雜的類添加一個新的方法(包括系統(tǒng)類)役耕。一般來說,對于自定義類聪廉,我們會找源碼瞬痘,然后添加新方法。但是如果我們新增的邏輯也很復(fù)雜板熊,這樣就會擴大原始設(shè)計的規(guī)模框全,有可能會打亂整個設(shè)計的結(jié)構(gòu)。
Category就是oc提供的為我們解決這一問題的方法邻邮。它可以讓我們動態(tài)的在已經(jīng)存在的類中添加新的方法竣况。對類進行擴展時不需要訪問其源碼克婶,也不需要創(chuàng)建子類筒严。
Category的實現(xiàn)很簡單丹泉,舉個例子。
```
// Deck.h
#import <Foundation/Foundation.h>
#import "Card.h"
@interface Deck : NSObject
- (Card *)randomDrawCard;
@end
```
這是類Deck的聲明文件鸭蛙,其中包含一個實例方法randomDrawCard摹恨,如果我們想在不修改原始類、不增加子類的情況下娶视,為該類增加一個drawCardFromTop方法晒哄,只需要定義兩個文件Deck+DrawCardFromTop.h和Deck+DrawCardFromTop.m,在聲明文件和實現(xiàn)文件中用()把Category的名稱括起來即可肪获,聲明文件如下:
```
// Deck+DrawCardFromTop.h
#import "Deck.h"
#import "Card.h"
@interface Deck(DrawCardFromTop)
- (Card *)drawCardFromTop;
@end
```
實現(xiàn)文件如下:
```
// Deck+DrawCardFromTop.m
#import "Deck+DrawCardFromTop.h"
#import "Card.h"
@implementation Deck(DrawCardFromTop)
- (Card *)drawCardFromTop
{
//TODO.....
}
@end
```
DrawCardFromTop是Category的名稱寝凌。這里一般使用約定俗成的習慣,將聲明文件和實現(xiàn)文件統(tǒng)一采用”原類名+Category名”的方式命名孝赫。
使用也非常簡單较木,引入Category的聲明文件,然后正常調(diào)用即可:
```
// main.m
#import "Deck+DrawCardFromTop.h"
#import "Card.h"
int main(int argc, char * argv[])
{
Deck *deck = [[Deck alloc] init];
Card *card = [deck drawCardFromTop];
return 0;
}
```
使用類別(Category)青柄,不僅在團隊協(xié)作開發(fā)時帶來方便(至少不會因為同時更改一個文件伐债,svn更新后需要自己解決沖突)。當一些基礎(chǔ)類庫滿足不了我們的需求時我們還可以拓展基礎(chǔ)類庫致开。
舉個例子峰锁,如果我們要分割一個字符串,然后用一個NSArray記錄每個分割子串的長度双戳。也就是說虹蒋,NSArray每個元素保存子NSstring的length。但是飒货,如之前提到千诬,NSArray只能保存NS對象,基本值類型(在這里是int)無法保存膏斤,扎心了徐绑。于是我們每次都要先獲取子Nstring的length,然后轉(zhuǎn)換為NSnumber莫辨,再保存于數(shù)組中傲茄。此時,我們完全可以擴展NSstring沮榜,使其在獲取長度時返回NSnumber對象盘榨。
使用Category,還可以實現(xiàn)類擴展蟆融。前面說過草巡,objc中定義私有的屬性和方法,一般用class extension實現(xiàn)型酥。其特點如下:
- 不需要名字
- 可以在自己的類中使用
- 可以添加實例變量
- 可以將只讀權(quán)限修改為可讀寫權(quán)限
- 創(chuàng)建數(shù)量不限
舉個例子:
```
//Things.h
@interface Things : NSObject
@proterty (assign) NSInteger thing1;
@ptoterty (readonly, assign) NSInteger thing2;
-(void)resetAllVal;
@end
```
```
//Things.m
@interface Things(){
NSInteger thing4;
}
@proterty (readwrite, assign) NSInteger thing2;
@proterty (assign) NSInteger thing3;
@end
@implementation
...
@end
```
我們使用了類擴展山憨,添加了私有實例變量和私有屬性查乒,還修改了thing2的讀寫權(quán)限,其對外只提供讀郁竟,對內(nèi)可讀寫玛迄。
但是Category不是萬能的,Category可以訪問原始類的實例變量棚亩,但不能添加變量蓖议,如果想添加變量,可以考慮通過繼承創(chuàng)建子類讥蟆。
- 類擴展與類別的區(qū)別:
1. 類別中只能增加方法
2. 類擴展不僅可以增加方法勒虾,還可以增加實例變量(或者合成屬性),只是該實例變量默認是@private類型的(作用范圍只能在自身類瘸彤,而不是子類或其他地方)从撼;
3. 類擴展中聲明的方法沒被實現(xiàn),編譯器會報警钧栖,但是類別中的方法沒被實現(xiàn)編譯器是不會有任何警告的低零。這是因為類擴展是在編譯階段被添加到類中,而類別是在運行時添加到類中拯杠。
4. 類擴展不能像類別那樣擁有獨立的實現(xiàn)部分(@implementation部分)掏婶,也就是說,類擴展所聲明的方法必須依托對應(yīng)類的實現(xiàn)部分來實現(xiàn)潭陪。
- 匿名函數(shù)(block)
objc中的block相當于c中的函數(shù)指針雄妥。二者仍有一定區(qū)別,如下
- block的代碼是內(nèi)聯(lián)的依溯,效率高于函數(shù)調(diào)用
- block對于外部變量默認是只讀屬性
- block被Objective-C看成是對象處理
block聲明和定義語法如下圖所示老厌。
![objc聲明與語法定義](http://upload-images.jianshu.io/upload_images/6836572-f34f5ff05a3b806b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
block特性如下
- 捕獲外界便變量
```
CGPoint center = cell.center;
CGPoint startCenter = center;
startCenter.y += LXD_SCREEN_HEIGHT;
cell.center = startCenter;
[UIView animateWithDuration: 0.5 delay: 0.35 * indexPath.item usingSpringWithDamping: 0.6 initialSpringVelocity: 0 options: UIViewAnimationOptionCurveLinear animations: ^{
cell.center = center;
} completion: ^(BOOL finished) {
NSLog("animation %@ finished", finished? @"is": @"isn't");
}];
```
這里面就用到了void(^animations)(void)跟void(^completion)(BOOL finished)兩個block,系統(tǒng)會在動畫開始以及動畫結(jié)束的時候分別調(diào)用者兩個 block黎炉。在實現(xiàn)動畫的block內(nèi)部枝秤,代碼訪問了上文中的center屬性——在動畫開 始的時候這個動畫函數(shù)的生命周期早已結(jié)束,而block會捕獲代碼外的局部變量慷嗜, 當然這只局限于只讀操作淀弹。如果我們在block中修改外部變量,編譯器將會報錯庆械。
同時薇溃,block在捕獲變量的時候只會保存變量被捕獲時的狀態(tài)(對象變量除外),之后即便變量再次改變缭乘,block中的值也不會發(fā)生改變沐序。見下面代碼:
```
CGPoint center = CGPointZero;
CGPoint (^pointAddHandler)(CGPoint addPoint) = ^(CGPoint addPoint) {
return CGPointMake(center.x + addPoint.x, center.y + addPoint.y);
}
center = CGPointMake(100, 100);
NSLog(@"%@", pointAddHandler(CGPointMake(10, 10))); //輸出{10,10}
```
要想在block內(nèi)部修改外部變量,可以給變量增加 _block關(guān)鍵字。
- 循環(huán)引用
前面說過策幼,block在iOS開發(fā)中被視作是對象邑时,因此其生命周期會一直等到持有者 的生命周期結(jié)束了才會結(jié)束。另一方面垄惧,由于block捕獲變量的機制,使得持有 block的對象也可能被block持有绰寞,從而形成循環(huán)引用到逊,導(dǎo)致兩者都不能被釋放:
```
@implementation LXDObject
{
void (^_cycleReferenceBlock)(void);
}
- (void)viewDidLoad
{
[super viewDidLoad];
_cycleReferenceBlock = ^{
NSLog(@"%@", self); //引發(fā)循環(huán)引用
};
}
@end
```
這種情況最后會導(dǎo)致內(nèi)存泄露,兩者都無法釋放滤钱。跟普通變量存在__block關(guān)鍵字 一樣的觉壶,系統(tǒng)提供給我們__weak的關(guān)鍵字用來修飾對象變量,聲明這是一個弱引用 的對象件缸,從而解決了循環(huán)引用的問題铜靶。
```
__weak typeof(*&self) weakSelf = self;
_cycleReferenceBlock = ^{
NSLog(@"%@", weakSelf); //弱指針引用,不會造成循環(huán)引用
};
```
## 異常他炊、錯誤處理争剿、斷言、日志和調(diào)試支持痊末,對單元測試的支持
- 異常
老生常談的try catch蚕苇,與其他oo語言一樣,不再多述凿叠。
```
@try {
// do something that might throw an exception
}
@catch (NSException *exception) {
// deal with the exception
}
@finally {
// optional block of clean-up code
// executed whether or not an exception occurred
}
```
- 錯誤處理
NSError是objc的系統(tǒng)錯誤信息類涩笤。其有三個較重要的私有變量:
- code
是一個整數(shù),最好是一個枚舉盒件,和特定的錯誤域是對應(yīng)的蹬碧。
- domain
一個字符串,標記錯誤域炒刁。
- userInfo
一個字典恩沽,包括任意的鍵值對。其中有:
1. NSLocalizedDescriptionKey:本地化的錯誤描述
2. NSLocalizedRecoverySuggestionErrorKey:本地化的恢復(fù)建議
3. NSLocalizedFailureReasonErrorKey:本地化的失敗原因
NSError主要有兩個用法:
- 獲取錯誤信息
```
//獲取錯誤
NSError *error = nil;
BOOL success = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"path" error:&error];
if (!success) {
NSLog(@"%@", [error localizedDescription]);
}
```
- 編輯錯誤信息
```
//預(yù)定義信息
#define JohnnyErrorDomain @"com.JohnnyError.Domain"
typedef NS_ENUM(NSInteger, ErrorFail){
ErrorOne = 1,
ErrorTwo,
ErrorThree
};
```
```
//產(chǎn)生錯誤信息
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: NSLocalizedString(@"Operation fail", nil),
NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"The operation timed out.", nil),
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Have you tried turning it off and on again?", nil)
};
NSError *error = [NSError errorWithDomain:JohnnyErrorDomain
code:2
userInfo:userInfo];
//提示
[[[UIAlertView alloc] initWithTitle:error.localizedDescription
message:error.localizedRecoverySuggestion
delegate:nil
cancelButtonTitle:NSLocalizedString(@"OK", nil)
otherButtonTitles:nil, nil nil] show];
```
- 斷言
- NsAsest
NSAssert()是一個宏翔始,用于開發(fā)階段調(diào)試程序中的Bug飒筑,通過為NSAssert()傳 遞條件表達式來斷定是否屬于Bug,滿足條件返回真值绽昏,程序繼續(xù)運行协屡,如果返回 假值,則拋出異常全谤,并且可以自定義異常描述肤晓。
NSAssert()是這樣定義的
```
#define NSAssert(condition, desc)
```
NSAssert用法
```
int a = 1;
NSCAssert(a == 2, @"a must equal to 2"); //第一個參數(shù)是條件,如果第一個參數(shù)不滿足條件,就會記錄并打印后面的字符串
```
- NSParameterAssert
NSAssert和 NSParameterAssert的區(qū)別是前者是針對條件斷言, 后者只是針對參數(shù)是否存在的斷言, 調(diào)試時候可以結(jié)合使用,先判斷參數(shù),再進一步斷言,確認原因.
NSParameterAssert用法
```
- (void)assertWithPara:(NSString *)str
{
NSParameterAssert(str); //只需要一個參數(shù),如果參數(shù)存在程序繼續(xù)運行,如果參數(shù)為空,則程序停止打印日志
//further code ...
}
```
- 自定義NSAssertionHandler
Objc中的斷言處理使用的是 NSAssertionHandler补憾。
每個線程擁有它自己的斷言處理器漫萄,它是 NSAssertionHandler 類的實例對象。NSAssertionHandler實例是自動創(chuàng)建的盈匾,用于處理錯誤斷言腾务。如果 NSAssert和NSCAssert條件評估為錯誤,會向 NSAssertionHandler實例發(fā)送一個表示錯誤的字符串削饵。每個線程都有它自己的NSAssertionHandler實例岩瘦。
我們可以自定義處理方法,從而使用斷言的時候窿撬,控制臺輸出錯誤启昧,但是程序不會直接崩潰。
```
#import "MyAssertHandler.h"
@implementation MyAssertHandler
//處理Objective-C的斷言
- (void)handleFailureInMethod:(SEL)selector object: (id)object file:(NSString *)fileName lineNumber: (NSInteger)line description:(NSString *)format,...
{
NSLog(@"NSAssert Failure: Method %@ for object %@ in %@#%li", NSStringFromSelector(selector), object, fileName, (long)line);
}
//處理C的斷言
- (void)handleFailureInFunction:(NSString *)functionName file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format,...
{
NSLog(@"NSCAssert Failure: Function (%@) in %@#%li", functionName, fileName, (long)line);
}
@end
```
給線程添加處理類
```
NSAssertionHandler *myHandler = [[MyAssertHandler alloc] init];
//給當前的線程
[[[NSThread currentThread] threadDictionary] setValue:myHandler
forKey:NSAssertionHandlerKey];
```
自定義NSAssertionHandler后,程序能夠獲得斷言失敗后的信息,但是程序可以繼續(xù)運行,不會強制退出程序.
- 日志
objc中日志輸出處理主要使用的是NSLog劈伴,要在日志輸出信息中添加上下文信息密末,編譯器提供了常用的表達式。
| Expression | Format Specifier | Description |
|-----------------|------------------------|--- -------------|
|NSStringFromSelector(_cmd) | %@ | 當前選擇器的名字 |
| NSStringFromClass([self class]) | %@ | 當前對象類的名字 |
| [[NSString stringWithUTF8String:\__FILE__] lastPathComponent] | %@ | 源碼文件的名稱|
| [NSThread callStackSymbols] | %@ | 當前棧信息的刻度字符串數(shù)組跛璧。僅用于調(diào)試严里,不用向終端用戶展示或者在代碼中用作任何邏輯。|
- 單元測試
objc中可以使用OCUnit(即用XCTest進行測試)其實就是蘋果自帶的測試框架追城。
一般測試用例分為三個階段:排列資源田炭、執(zhí)行行為、斷言結(jié)果漓柑。
- 排列資源
排列資源教硫,便是提供一切測試方法所需要的東西,而這些東西便稱之為資源辆布。這些資源包括:
1. 方法的輸入?yún)?shù)
2. 方法所執(zhí)行的特定上下文
這個階段相當于準備階段瞬矩,一切都是為了這個用例中執(zhí)行行為而作準備,如果沒有任何需要準備的數(shù)據(jù)锋玲,這個階段是可以被忽略的景用。
```
- (void)test_setObject$forKey {
// arrange
NSString *key = @"test_key";
NSString *value = @"test_value";
NSMutableDictionary *dic = [NSMutableDictionary new];
}
```
- 執(zhí)行行為
當準備階段完畢后,便進入要測試行為的執(zhí)行階段惭蹂,在這個階段伞插,我們會使用準備好的資源,并記錄下行為的輸出以供下個階段使用盾碗。這里的行為輸出不一定就是方法執(zhí)行的返回值媚污,很多時候我們要測試的方法并沒有任何返回值,但一個方法執(zhí)行后廷雅,總歸會有一個預(yù)期的行為會發(fā)生耗美,即便是空方法也是(什么都不會被改變)京髓,而這個預(yù)期行為便是測試行為的輸出。
加入執(zhí)行行為的代碼:
```
- (void)test_setObject$forKey {
// arrange
NSString *key = @"test_key";
NSString *value = @"test_value";
NSMutableDictionary *dic = [NSMutableDictionary new];
// act
[dic setObject:value forKey:key];
}
```
- 斷言結(jié)果
最后一步商架,也是最核心的一步堰怨,它決定著一個測試用例的成功與否,我們需要在這一步斷言執(zhí)行行為的輸出是否達到預(yù)期蛇摸。確定一個行為的輸出备图,我們可能需要有多次斷言,這里需要遵循一個原則:**先執(zhí)行的斷言赶袄,不應(yīng)該以后執(zhí)行的斷言成功為前提**揽涮。
```
- (void)test_setObject$forKey {
// arrange
NSString *key = @"test_key";
NSString *value = @"test_value";
NSMutableDictionary *dic = [NSMutableDictionary new];
// act
[dic setObject:value forKey:key];
// assert
XCTAssertNotNil([dic objectForKey:key]);
XCTAssertEqual([dic objectForKey:key], value);
}
```
可以看到,最后我們是先斷言是否為空弃鸦,再斷言是否相等绞吁,后者是在前者成功的前提下才可能不失敗幢痘。如果顛倒順序唬格,就很難盡早的發(fā)現(xiàn)錯誤原因。
## RunTime
Objc是一門動態(tài)語言颜说,所以它總是想辦法把一些決定工作從編譯連接推遲到運行時购岗。也就是說只有編譯器是不夠的,還需要一個運行時系統(tǒng) (runtime system) 來執(zhí)行編譯后的代碼门粪。
- 使用RunTime的場景
OC程序使用Runtime 系統(tǒng)有三種情景:Objective-C Source Code喊积、NSObject Methods、Runtime Functions玄妈;
- Objective-C Source Code
- NSObject Methods
- Runtime Functions
- 消息機制
上面提到過乾吻,RunTime的實質(zhì)就是消息的發(fā)送。在objc中拟蜻,調(diào)用方法:
```
[receiver messge]
```
在編譯器中會轉(zhuǎn)換成消息機制里的消息發(fā)送形式
```
objc_msgSend(receiver, selector)
//帶參數(shù)
objc_msgSend(receiver, selector, arg1, arg2...)
```
消息功能為動態(tài)綁定做了很多必要的工作:
1. 通過selector在消息接收者class里選擇方法實現(xiàn)(method implement)
2. 調(diào)用方法實現(xiàn)绎签,傳遞到接收對象with參數(shù)
3. 傳遞方法實現(xiàn)返回值
為了讓編譯器編譯時,消息機制與類的結(jié)構(gòu)關(guān)聯(lián)上酝锅,每個類的結(jié)構(gòu)里添加了兩個基本的元素:
1. 指向父類的指針(isa指針)
2. 類調(diào)度表(A class dispatch table)诡必,通過Selector方法名在dispatch table里面匹配對應(yīng)的方法地址(class-specific address)
當一個對象被創(chuàng)建并分配內(nèi)存時,它的實例里的變量會初始化搔扁,里面有一個指向它的類的結(jié)構(gòu)體爸舒,isa指針。
消息發(fā)送到一個對象時稿蹲,通過class結(jié)構(gòu)體里的isa指針在dispatch table里尋找相應(yīng)的selector扭勉,如果找不到便進入父類里找,一直找到NSObject苛聘,一旦定位到selector剖效,便調(diào)用該方法嫉入,傳遞相關(guān)數(shù)據(jù)。為了提高效率璧尸,RunTime會緩存調(diào)用過的selector和方法地址咒林,在到dispatch table查找之前,先到cache里查找爷光。
## 如何進行callback方法調(diào)用垫竞,如何支持事件驅(qū)動編程模型
- 非正式協(xié)議
引用《Cocoa設(shè)計模式》
> 非正式協(xié)議通常定義為NSObject的類別。類別接口中指定的方法可能會或者可能不會被框架類實際地實現(xiàn)蛀序。非正式協(xié)議位于一種設(shè)計灰區(qū)中欢瞪。正式協(xié)議由編譯器檢查并且代表一種關(guān)于對象能力的保證,但是非正式協(xié)議不會做出保證----而只會給出提示徐裸。
引用官方文檔
> An informal protocol is a category on NSObject, which implicitly makes almost all objects adopters of the protocol. (A category is a language feature that enables you to add methods to a class without subclassing it.) Implementation of the methods in an informal protocol is optional. Before invoking a method, the calling object checks to see whether the target object implements it. Until optional protocol methods were introduced in Objective-C 2.0, informal protocols were essential to the way Foundation and AppKit classes implemented delegation.
可以看出遣鼓,非正式協(xié)議就是類別,凡是NSObject或其子類的類別重贺,都是非正式協(xié)議骑祟。
- 正式協(xié)議
正式協(xié)議從概念上理解起來就簡單的多了,它指的是一個以@protocol方式命名的方法列表气笙,與非正式協(xié)議相比不同的是次企,它要求顯示的采用協(xié)議。
- 正式協(xié)議的聲明
1. @required 該類的方式在遵守相應(yīng)協(xié)議的類中是必須被實現(xiàn)的潜圃,不然編譯器會警告(顯然這是在編譯時做的檢查缸棵,而不是在運行時)
2. @optional 該類的方法在遵守相應(yīng)協(xié)議的類中是否實現(xiàn)是可選的,@optional已取代非正式協(xié)議
- 正式協(xié)議的繼承性
正式協(xié)議和類一樣谭期,是可以繼承的堵第,書寫格式同類繼承相似:
```
@protocol NewProtocal <Protocal>
@end
```
- 委托方法
委托概念什么的這里略過。下面主要敘述如何在objc中定義和使用委托隧出。
- 定義委托
```
#import <BlaClass/BlaClass.h>
@class MyClass; //定義類踏志,這樣協(xié)議可以看到MyClass
@protocol MyClassDelegate //定義委托協(xié)議
- (void) myClassDelegateMethod: (MyClass *) sender; //定義在另一個類里實現(xiàn)的委托方法
@end //結(jié)束協(xié)議
@interface MyClass : NSObject {
}
@property (nonatomic, weak) id <MyClassDelegate> delegate; //定義 MyClassDelegate為委托
@end
```
- 定制委托
```
#import "MyClass.h"
@interface MyVC:UIViewController <MyClassDelegate> { //make it a delegate for MyClassDelegate
}
```
```
myClass.delegate = self; //設(shè)置委托至自身的某個地方
```
- 使用委托, 如下形式
```
if([[self delegate] respondsToSelector:@selector(windowDidMove:)]) {
[[self delegate] windowDidMove:notification];
}
```
- 響應(yīng)選擇器selector
selector,實際上是函數(shù)指針的一種實現(xiàn)形式鸳劳,我們用一個 C string 來表示對象中的某個函數(shù)狰贯,所以就可以把這個函數(shù)作為參數(shù),傳到其他的方法中去進行調(diào)用赏廓。
Objective-C 的 Class 在編譯時會變成 C struct涵紊,Class 中包含的方法也會轉(zhuǎn)換成 C function。之后在運行的時候幔摸,runtime 會建立起從 Objective-C Method 到 C function 的映射(可以認為是一個 virtual table)摸柄。
Runtime 會為每個類準備一個 virtual table,里面是一個個鍵值對既忆,key 稱為 selector驱负,類型是 SEL嗦玖,value 實際上是 C function 的函數(shù)指針,類型是 IMP跃脊。而這里的 SEL 類型實際上就是 C string宇挫。
因此 selector 可以看做是函數(shù)的另一個名字,所以很多需要調(diào)用函數(shù)或者建立連接的地方酪术,都可以用到器瘪。
- objc的回調(diào)實現(xiàn)
- Run loop
objc提供的NSRunLoop實例會持續(xù)等待著,當特定事件發(fā)生時绘雁,觸發(fā)回調(diào)(callback)橡疼。
調(diào)用以下方法,即可得到一個run loop庐舟。
```
[[NSRunLoop currentRunLoop] run];
```
- target-action/目標-動作對
實例:
```
// 為按鈕添加回調(diào)——Target-action/目標-動作對
// 第一個參數(shù):發(fā)送消息給誰
// 第二個參數(shù):事件發(fā)生后,執(zhí)行什么代碼(回調(diào))
// 第三個參數(shù):發(fā)生哪類型的點擊事件會觸發(fā)回調(diào)
[button addTarget:self
action:@selector(click:)
forControlEvents:UIControlEventTouchUpInside];
```
目標-動作對欣除,就是當事件發(fā)生時,像指定的對象發(fā)送指定的消息挪略。對target历帚,action的對應(yīng)理解,可以這樣認為瘟檩,執(zhí)行某個類(target)的某個方法抹缕。
- Helper object/委托
委托使用如上文所示澈蟆。
- Notification/通告
objc提供了一個叫做「通告中心」的對象墨辛,可以通過[NSNotificationCenter defaultCenter]獲得,利用這個通告中心趴俘,我們可以「發(fā)通告」睹簇、「監(jiān)測(接收)通告」,利用這個機制寥闪,實現(xiàn)回調(diào)太惠。
- Block
在objc中使用block實現(xiàn)回調(diào),除了基本聲明語法疲憋,其他與大多oo語言相同凿渊,不再敘述。
```
#import <Foundation/Foundation.h>
@import CoreBluetooth;
// 步驟1:
// 將Block重新定義為一種新的數(shù)據(jù)類型
// 這個Block無返回值;有一個參數(shù)(類型為NSUInteger)
typedef void(^AllDevicesDidConnectedBlock)(NSUInteger divicesCount);
@interface MyCnetralManager : NSObject
// 步驟2:
// 聲明一個(Block)變量
@property (nonatomic, strong) AllDevicesDidConnectedBlock callbackForAllDevicesDidConnected;
@end
```
然后缚柳,就可以使用了埃脏。
- 總結(jié)
1. 當只發(fā)生單個事件(event),只需要完成一件事情進行響應(yīng)秋忙,建議用「Target-action/目標-動作對」彩掐。比如NSTimer、UIButton等灰追。
2. 當會發(fā)生若干事件(event)堵幽,要完成多件事情進行響應(yīng)狗超,建議使用「Helper objects/輔助對象」,當然了朴下,最常見的是「delegate/委托」(另外還有「data sources/數(shù)據(jù)源」)努咐。
3. 當發(fā)生單個事件(event),多個對象要進行響應(yīng)殴胧,建議使用「Notifications/通告」
4. 使用Block麦撵,可以寫出更簡潔的代碼、更好的代碼結(jié)構(gòu)溃肪。