本文結(jié)構(gòu)
參考孟巖老師的文章,對(duì)本文結(jié)構(gòu)如下劃分
- 基本數(shù)據(jù)類型
- 基本語(yǔ)法
- 數(shù)組和其他集合類
- 基本輸入輸出和文件處理,輸入輸出流類的組織
- 序列化和反序列化
- 面向?qū)ο筇匦?/li>
- 異常、錯(cuò)誤處理、斷言、日志和調(diào)試支持,對(duì)單元測(cè)試的支持
- RunTime
- callback方法調(diào)用,事件驅(qū)動(dòng)編程模型
參考鏈接
在完成本文過(guò)程中,或轉(zhuǎn)載,或參考了以下鏈接
- 匿名函數(shù)
http://blog.devtang.com/2013/07/28/a-look-inside-blocks/
http://coolshell.cn/articles/8309.html
http://www.itdecent.cn/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.itdecent.cn/p/2a9c98a29685
- 斷言&錯(cuò)誤&日志
http://blog.csdn.net/lcl130/article/details/41889185
http://www.itdecent.cn/p/6e444981ab45
- 面向?qū)ο?/li>
- 內(nèi)存
- 單元測(cè)試
https://hjgitbook.gitbooks.io/ios/content/01-thinking/01-the-basic-knowledge-of-unit-test.html
http://www.itdecent.cn/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.itdecent.cn/p/376ba5343097
https://segmentfault.com/q/1010000000387240
http://wdxtub.com/2016/02/20/dive-in-objc-1/
- 文件&流
http://www.itdecent.cn/p/fbb997eb032d
http://blog.csdn.net/swingpyzf/article/details/16325923
- 運(yùn)行時(shí)
http://www.itdecent.cn/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.itdecent.cn/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語(yǔ)言的一個(gè)超集,所有C語(yǔ)言支持的基本數(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位機(jī)器的移植性處理。
-
BOOL
YES 或 NO
-
float 與 CGFloat
CGFloat對(duì)于float相當(dāng)于NSInteger對(duì)于int
-
NSString
后面單獨(dú)介紹。
需要注意的是:NSString類型的等號(hào)賦值做的是深拷貝
-
NSValue
NSValue是個(gè)可以和各種基本數(shù)據(jù)類型相互轉(zhuǎn)換的類。
例
[NSValue valueWithCGSize:CGSizeMake(100, 100)]; [NSValue valueWithRange:NSMakeRange(0, 10)]; -
NSNumber
NSNumber與上面不同的是,NSNumber不是基本的數(shù)據(jù)類型,而是對(duì)象。
繼承關(guān)系:NSNumber->NSValue->NSObject
同時(shí)NSNumber支持和NSString一樣的@符號(hào)簡(jiǎn)寫(xiě)
NSNumber * number = @(123); NSNumber * number1 = @(3.1415); NSNumber * number2 = @(YES); NSInteger intValue = [number integerValue]; CGFloat floatValue = [number1 doubleValue]; BOOL boolValue = [number2 boolValue];
基本語(yǔ)法
-
減號(hào)和加號(hào)
-
減號(hào)表示一個(gè)函數(shù)或消息的開(kāi)始
舉個(gè)例子,在c#中,一個(gè)方法的寫(xiě)法是
private void add(bool isAdd){ ... }用oc寫(xiě)出來(lái)就是
-(void)add:(Bool)isAdd{ ... } 加號(hào)代表是類的靜態(tài)方法,不需要實(shí)例化即可調(diào)用。
-
-
中括號(hào)
中括號(hào)可以理解為調(diào)用方法,在oc中,嚴(yán)格來(lái)說(shuō),應(yīng)該表述為發(fā)消息
具體理解如下:
因?yàn)樵贠bjective-C中,message與方法是在執(zhí)行階段綁定的,而不是編譯階段。簡(jiǎn)單的說(shuō) [a someFunc] 這樣一個(gè)調(diào)用,在編譯階段,編譯器并不知道someFunc要執(zhí)行哪段代碼。這個(gè)時(shí)候[a someFunc]會(huì)被轉(zhuǎn)換為 objc_msgSend(a, "someFunc"),字面的意思也很容易理解,就是給a這個(gè)instance,發(fā)“someFunc”這個(gè)消息,以selector的形式。在運(yùn)行階段,執(zhí)行到上述的objc_msgSend這個(gè)函數(shù)時(shí)。函數(shù)內(nèi)部會(huì)到a對(duì)應(yīng)的內(nèi)存地址,尋找someFunc這個(gè)方法的地址,并執(zhí)行。如果找不到,就會(huì)拋一個(gè)“unknown selector sent to instance”的異常。(比如.h中聲明了方法,但.m中沒(méi)有實(shí)現(xiàn),就可以重現(xiàn)這個(gè)錯(cuò)誤) 所以嚴(yán)格意義上來(lái)將,任何Objective C的函數(shù)調(diào)用,編譯階段的表現(xiàn),都只能算一種“發(fā)消息”的行為。
深入理解可見(jiàn):Objective-C 消息發(fā)送與轉(zhuǎn)發(fā)機(jī)制原理
總之,從語(yǔ)法層面上來(lái)說(shuō)。
在C#中,我們這樣寫(xiě):
this.hello(true);在oc中,我們這樣寫(xiě):
[self hello:YES]; -
#import @interface
-
#import 和 #include
#import可以認(rèn)為是#include的升級(jí)版,使用#import可以保證頭文件不會(huì)重復(fù)引用。
在c中,防止頭文件的重復(fù)引用,常??梢?jiàn)類似如下代碼
#ifndef xxx_H #define xxx_H #include "xxx.h" #endif通常建議是:oc文件使用#import形式,c\c++文件使用#include形式。
-
@interface 和 @implementation
用一個(gè)簡(jiǎn)單的例子理解,定義一個(gè)老鷹捉小雞類
使用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ù)的傳遞
-
多個(gè)參數(shù)的寫(xiě)法
(方法的數(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ù)傳遞
舉個(gè)例子
[[[MyClass alloc] init:[foo bar]] autorelease]對(duì)應(yīng)于
MyClass.alloc().init(foo.bar()).autorelease()
-
數(shù)組和其他集合類
Foundation framework中用于收集cocoa對(duì)象(NSObject對(duì)象)的三種集合
NSArray 用于對(duì)象有序集合(數(shù)組)
NSSet 用于對(duì)象無(wú)序集合 (集合)
NSDictionary用于鍵值映射(字典)
以上三種集合類是不可變的(一旦初始化后,就不能改變)
對(duì)應(yīng)的可變集合類(這三種可變集合類是對(duì)應(yīng)上面三種集合類的子類):
NSMutableArray
NSMutableSet 可修改的集合。主要用于集合運(yùn)算(并集,交集,差集)
NSMutableDictionary 允許用戶添加和刪除key和value
這些集合類只能容納cocoa對(duì)象(NSOjbect對(duì)象),如果想保存一些原始的C數(shù)據(jù)(例如,int, float, double, BOOL等),則需要將這些原始的C數(shù)據(jù)封裝成NSNumber類型進(jìn)行存儲(chǔ)。NSNumber對(duì)象是cocoa對(duì)象,可以被保存在集合類中。
遍歷
-
索引
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
}
***字典使用快速枚舉時(shí),得到的obj是key而不是keypair***
- 代碼塊
為什么使用代碼塊,因?yàn)榇a塊可以讓循環(huán)操作并發(fā)執(zhí)行。而上面的三種方式都是線性操作。
```
[array enumerateObjectsUsingBlock:^(id obj,NSUInteger idx,BOOL *stop){
//obj為取出的對(duì)象,idx為對(duì)應(yīng)的下標(biāo)
}];
```
```
if(idx == 1){
*stop = YES;
}
```
- 技巧
1. 倒序遍歷
NSArray和NSOrderedSet都支持使用reverseObjectEnumerator倒序遍歷,如:
```
NSArray *strings = @[@"1", @"2", @"3"];
for (NSString *string in [strings reverseObjectEnumerator]) {
NSLog(@"%@", string);
}
```
這個(gè)方法只在循環(huán)第一次被調(diào)用,所以也不必?fù)?dān)心循環(huán)每次計(jì)算的問(wèn)題。
同時(shí),使用enumerateObjectsWithOptions:NSEnumerationReverse也可以實(shí)現(xiàn)倒序遍歷:
```
[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(Sark *sark, NSUInteger idx, BOOL *stop) {
[sark doSomething];
}];
```
2.使用block同時(shí)遍歷字典key,value
block版本的字典遍歷可以同時(shí)取key和value(forin只能取key再手動(dòng)取value),如:
```
NSDictionary *dict = @{@"a": @"1", @"b": @"2"};
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(@"key: %@, value: %@", key, obj);
}];[]()
```
## 基本輸入輸出和文件處理,輸入輸出流類的組織
- 輸入輸出
兼容C的scanf,printf,不再敘述。
- 文件處理
- 基本讀寫(xiě)操作
Objective-C使用NSFileHandle類對(duì)文件進(jìn)行基本操作,iOS文件操作
NSFileHandle類中得方法可以對(duì)文件進(jìn)行基本的讀寫(xiě),偏移量的操作。
NSFileHandle基本步驟:
1. 打開(kāi)文件,獲取一個(gè)NSFileHandle對(duì)象。
2. 對(duì)打開(kāi)NSFileHandle的文件對(duì)象行I/O操作
3. 關(guān)閉文件對(duì)象
- 簡(jiǎn)單對(duì)象的讀寫(xiě)(I/O)操作
iOS中提供四種類型(包括其子類型)可以直接進(jìn)行文件存?。?
1. NSString
2. NSDictionary
3. NSArray
4. NSData
其基本操作如下:
```
// 在Documents下面創(chuàng)建一個(gè)文本路徑,假設(shè)文本名稱為objc.txt
NSString *txtPath = [docPath stringByAppendingPathComponent:@"objc.txt"]; // 此時(shí)僅存在路徑,文件并沒(méi)有真實(shí)存在
NSString *string = @"Objective-C";
// 字符串寫(xiě)入時(shí)執(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)可以實(shí)現(xiàn)對(duì)文件進(jìn)行操作(創(chuàng)建、刪除、改名等)以及文件信息的獲取
- 流
使用Cocoa框架中的輸入輸出流,可以從文件或應(yīng)用中內(nèi)存讀取數(shù)據(jù),也可以向文件/應(yīng)用中內(nèi)存寫(xiě)入數(shù)據(jù)。還可以用于socket的數(shù)據(jù)交互處理。
其主要類與方法如下:

## 序列化和反序列化
前面提到過(guò),NSArray,NSDictionary,NSString,NSNumber,NSDate,NSData以及它們的可變版本(指NSMutableArray,NSMutableDictionary...這一類) ,都可以方便的將自身的數(shù)據(jù)以某種格式(比如xml格式)序列化后保存成本地文件。
但是如果用于存放數(shù)據(jù)的類是自己定義的,并不是上面這些預(yù)置的對(duì)象,如自定以的Person類,像這種自定義的類是無(wú)法在程序內(nèi)部通過(guò)writeToFile這個(gè)方法寫(xiě)入到文件內(nèi)
既然復(fù)雜對(duì)象無(wú)法使用內(nèi)部方法進(jìn)行數(shù)據(jù)持久化,那么只能通過(guò)將復(fù)雜對(duì)象轉(zhuǎn)換成NSData,然后在通過(guò)上面的方法寫(xiě)入文件,而這種轉(zhuǎn)換的步驟就被稱為歸檔,從文件中讀取NSData數(shù)據(jù),將NSData轉(zhuǎn)換為復(fù)雜對(duì)象,這個(gè)步驟就是反歸檔。
- 要點(diǎn)
- 復(fù)雜對(duì)象寫(xiě)入文件的過(guò)程(復(fù)雜對(duì)象->歸檔->NSData->writeToFile)
- 從文件中讀取出復(fù)雜對(duì)象過(guò)程(讀取文件->NSData->反歸檔->復(fù)雜對(duì)象
- 實(shí)現(xiàn)步驟
1. 首先,復(fù)雜對(duì)象所屬的類要遵守<NSCoding>
2. 其次,實(shí)現(xiàn)協(xié)議中的兩個(gè)方法:
- -(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. 其次,實(shí)現(xiàn)協(xié)議中的兩個(gè)方法:
```
// 對(duì)person對(duì)象進(jìn)行歸檔時(shí),此方法執(zhí)行。
// 對(duì)person中想要進(jìn)行歸檔的所有屬性,進(jìn)行序列化操作。
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}
// 對(duì)person對(duì)象進(jìn)行反歸檔時(shí),該方法執(zhí)行。
// 創(chuàng)建一個(gè)新的person對(duì)象,所有屬性都是通過(guò)反序列化得到的。
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
// 準(zhǔn)備一個(gè)NSMutableData, 用于保存歸檔后的對(duì)象
NSMutableData *data = [NSMutableData data];
// 創(chuàng)建歸檔工具
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingMutableData:data];
// 歸檔
[archiver encodeObject:p] forKey:@"p1"];
// 結(jié)束
[archiver finishEncoding];
// 拼音寫(xiě)入沙盒路徑
NSString *caches = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [caches stringByAppendingPathComPonent:@"person"];
// 寫(xiě)入沙盒
[data writeToFile:filePath atomically:YES];
// 反歸檔
// 從filePath文件路徑讀取
NSData *data = [NSData dataWithContentsOfFile:filePath];
// 反歸檔工具
NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
// 反歸檔成對(duì)象
Person *p2 = [unArchiver decodeObjectForKey:@"p1"];
// 反歸檔結(jié)束
[unArchiver finshDeoding];
```
## 面向?qū)ο筇匦?
- 繼承&多態(tài)
objc的這兩個(gè)特性除了使用語(yǔ)法上,與其他oo語(yǔ)言沒(méi)有什么什么不同。基本語(yǔ)法之前也有提過(guò),@interface用于聲明一個(gè)類,@implementation用于類的定義。
不過(guò)需要值得注意的一點(diǎn)是,由于objc的動(dòng)態(tài)消息傳遞機(jī)制,objc中不存在真正意義上的私有方法。但是如果方法不再.h文件中聲明,而只在.m文件中實(shí)現(xiàn),基本上也和私有方法差不多。
所以在使用objc時(shí),一般會(huì)將公有的放到.h文件中,將私有的放到.m文件中。實(shí)現(xiàn)私有一般用類擴(kuò)展實(shí)現(xiàn)。
- 屬性
在屬性方面,objc相比于其他oo語(yǔ)言,有自己的語(yǔ)法糖,@Property和@synthesize。簡(jiǎn)單來(lái)說(shuō),.h文件中使用@Property, .m文件中使用@synthesize,編譯器會(huì)自動(dòng)創(chuàng)建成員屬性以及對(duì)應(yīng)的get和set方法。
比較值得關(guān)注的,是@Property的幾個(gè)特性。
- 原子性
1. atomic(默認(rèn)使用)。使用該選項(xiàng),保證調(diào)用者對(duì)變量的訪問(wèn)是線程安全的,在多線程環(huán)境下會(huì)返回一個(gè)多多個(gè)值(其他線程前后),而不是一個(gè)垃圾值。
2. noaomic。如其名,使用該關(guān)鍵字后,不能保證線程安全,但是性能及訪問(wèn)效率優(yōu)于前值。所以在單線程環(huán)境下一般指定該關(guān)鍵字。
- 寄存器控制
1. readwrite(默認(rèn)):readwrite是默認(rèn)值,表示該屬性同時(shí)擁有setter和getter。
2. readonly: readonly表示只有g(shù)etter沒(méi)有setter。
- 內(nèi)存管理
1. assign(默認(rèn)):assign用于值類型,如int、float、double和NSInteger,CGFloat等表示單純的復(fù)制。
其在set的實(shí)現(xiàn),是采用直接賦值來(lái)實(shí)現(xiàn)設(shè)值操作的
```
-(void)setVar:(int)newVar{
var= newVar;
}
```
2. retain:在set方法中,需要對(duì)傳入的對(duì)象進(jìn)行引用計(jì)數(shù)加1的操作
簡(jiǎn)單來(lái)說(shuō),就是對(duì)傳入的對(duì)象擁有所有權(quán),只要對(duì)該對(duì)象擁有所有權(quán),該對(duì)象就不會(huì)被釋放。如下代碼所示:
```
-(void)setName:(NSString*)_name{
if ( name != _name){
[name release];
name = [_name retain];
}
}
```
首先判斷是否與舊對(duì)象一致,如果不一致進(jìn)行賦值。之所以要增加if判斷,是因?yàn)槿绻峭粋€(gè)對(duì)象的話,進(jìn)行if內(nèi)的代碼會(huì)造成一個(gè)極端的情況:當(dāng)此name的retain為1時(shí),使此次的set操作讓實(shí)例name提前釋放,而達(dá)不到賦值目的
3. strong:表示實(shí)例變量對(duì)傳入的對(duì)象要有所有權(quán)關(guān)系,即強(qiáng)引用。strong跟retain的意思相同并產(chǎn)生相同的代碼,但是語(yǔ)意上更好更能體現(xiàn)對(duì)象的關(guān)系。
4. weak:在set方法中,需要對(duì)傳入的對(duì)象不進(jìn)行引用計(jì)數(shù)加1的操作。
簡(jiǎn)單來(lái)說(shuō),就是對(duì)傳入的對(duì)象沒(méi)有所有權(quán),當(dāng)該對(duì)象引用計(jì)數(shù)為0時(shí),即該對(duì)象被釋放后,用weak聲明的實(shí)例變量指向nil,即實(shí)例變量的值為0。
5. copy:與strong類似,但區(qū)別在于實(shí)例變量是對(duì)傳入對(duì)象的副本擁有所有權(quán),而非對(duì)象本身
- Category
這算是oc的一個(gè)比較有意思特性。如果我們想給一個(gè)已存在的、很復(fù)雜的類添加一個(gè)新的方法(包括系統(tǒng)類)。一般來(lái)說(shuō),對(duì)于自定義類,我們會(huì)找源碼,然后添加新方法。但是如果我們新增的邏輯也很復(fù)雜,這樣就會(huì)擴(kuò)大原始設(shè)計(jì)的規(guī)模,有可能會(huì)打亂整個(gè)設(shè)計(jì)的結(jié)構(gòu)。
Category就是oc提供的為我們解決這一問(wèn)題的方法。它可以讓我們動(dòng)態(tài)的在已經(jīng)存在的類中添加新的方法。對(duì)類進(jìn)行擴(kuò)展時(shí)不需要訪問(wèn)其源碼,也不需要?jiǎng)?chuàng)建子類。
Category的實(shí)現(xiàn)很簡(jiǎn)單,舉個(gè)例子。
```
// Deck.h
#import <Foundation/Foundation.h>
#import "Card.h"
@interface Deck : NSObject
- (Card *)randomDrawCard;
@end
```
這是類Deck的聲明文件,其中包含一個(gè)實(shí)例方法randomDrawCard,如果我們想在不修改原始類、不增加子類的情況下,為該類增加一個(gè)drawCardFromTop方法,只需要定義兩個(gè)文件Deck+DrawCardFromTop.h和Deck+DrawCardFromTop.m,在聲明文件和實(shí)現(xiàn)文件中用()把Category的名稱括起來(lái)即可,聲明文件如下:
```
// Deck+DrawCardFromTop.h
#import "Deck.h"
#import "Card.h"
@interface Deck(DrawCardFromTop)
- (Card *)drawCardFromTop;
@end
```
實(shí)現(xiàn)文件如下:
```
// Deck+DrawCardFromTop.m
#import "Deck+DrawCardFromTop.h"
#import "Card.h"
@implementation Deck(DrawCardFromTop)
- (Card *)drawCardFromTop
{
//TODO.....
}
@end
```
DrawCardFromTop是Category的名稱。這里一般使用約定俗成的習(xí)慣,將聲明文件和實(shí)現(xiàn)文件統(tǒng)一采用”原類名+Category名”的方式命名。
使用也非常簡(jiǎn)單,引入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),不僅在團(tuán)隊(duì)協(xié)作開(kāi)發(fā)時(shí)帶來(lái)方便(至少不會(huì)因?yàn)橥瑫r(shí)更改一個(gè)文件,svn更新后需要自己解決沖突)。當(dāng)一些基礎(chǔ)類庫(kù)滿足不了我們的需求時(shí)我們還可以拓展基礎(chǔ)類庫(kù)。
舉個(gè)例子,如果我們要分割一個(gè)字符串,然后用一個(gè)NSArray記錄每個(gè)分割子串的長(zhǎng)度。也就是說(shuō),NSArray每個(gè)元素保存子NSstring的length。但是,如之前提到,NSArray只能保存NS對(duì)象,基本值類型(在這里是int)無(wú)法保存,扎心了。于是我們每次都要先獲取子Nstring的length,然后轉(zhuǎn)換為NSnumber,再保存于數(shù)組中。此時(shí),我們完全可以擴(kuò)展NSstring,使其在獲取長(zhǎng)度時(shí)返回NSnumber對(duì)象。
使用Category,還可以實(shí)現(xiàn)類擴(kuò)展。前面說(shuō)過(guò),objc中定義私有的屬性和方法,一般用class extension實(shí)現(xiàn)。其特點(diǎn)如下:
- 不需要名字
- 可以在自己的類中使用
- 可以添加實(shí)例變量
- 可以將只讀權(quán)限修改為可讀寫(xiě)權(quán)限
- 創(chuàng)建數(shù)量不限
舉個(gè)例子:
```
//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
```
我們使用了類擴(kuò)展,添加了私有實(shí)例變量和私有屬性,還修改了thing2的讀寫(xiě)權(quán)限,其對(duì)外只提供讀,對(duì)內(nèi)可讀寫(xiě)。
但是Category不是萬(wàn)能的,Category可以訪問(wèn)原始類的實(shí)例變量,但不能添加變量,如果想添加變量,可以考慮通過(guò)繼承創(chuàng)建子類。
- 類擴(kuò)展與類別的區(qū)別:
1. 類別中只能增加方法
2. 類擴(kuò)展不僅可以增加方法,還可以增加實(shí)例變量(或者合成屬性),只是該實(shí)例變量默認(rèn)是@private類型的(作用范圍只能在自身類,而不是子類或其他地方);
3. 類擴(kuò)展中聲明的方法沒(méi)被實(shí)現(xiàn),編譯器會(huì)報(bào)警,但是類別中的方法沒(méi)被實(shí)現(xiàn)編譯器是不會(huì)有任何警告的。這是因?yàn)轭悢U(kuò)展是在編譯階段被添加到類中,而類別是在運(yùn)行時(shí)添加到類中。
4. 類擴(kuò)展不能像類別那樣擁有獨(dú)立的實(shí)現(xiàn)部分(@implementation部分),也就是說(shuō),類擴(kuò)展所聲明的方法必須依托對(duì)應(yīng)類的實(shí)現(xiàn)部分來(lái)實(shí)現(xiàn)。
- 匿名函數(shù)(block)
objc中的block相當(dāng)于c中的函數(shù)指針。二者仍有一定區(qū)別,如下
- block的代碼是內(nèi)聯(lián)的,效率高于函數(shù)調(diào)用
- block對(duì)于外部變量默認(rèn)是只讀屬性
- block被Objective-C看成是對(duì)象處理
block聲明和定義語(yǔ)法如下圖所示。

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)兩個(gè)block,系統(tǒng)會(huì)在動(dòng)畫(huà)開(kāi)始以及動(dòng)畫(huà)結(jié)束的時(shí)候分別調(diào)用者兩個(gè) block。在實(shí)現(xiàn)動(dòng)畫(huà)的block內(nèi)部,代碼訪問(wèn)了上文中的center屬性——在動(dòng)畫(huà)開(kāi) 始的時(shí)候這個(gè)動(dòng)畫(huà)函數(shù)的生命周期早已結(jié)束,而block會(huì)捕獲代碼外的局部變量, 當(dāng)然這只局限于只讀操作。如果我們?cè)赽lock中修改外部變量,編譯器將會(huì)報(bào)錯(cuò)。
同時(shí),block在捕獲變量的時(shí)候只會(huì)保存變量被捕獲時(shí)的狀態(tài)(對(duì)象變量除外),之后即便變量再次改變,block中的值也不會(huì)發(fā)生改變。見(jiàn)下面代碼:
```
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)引用
前面說(shuō)過(guò),block在iOS開(kāi)發(fā)中被視作是對(duì)象,因此其生命周期會(huì)一直等到持有者 的生命周期結(jié)束了才會(huì)結(jié)束。另一方面,由于block捕獲變量的機(jī)制,使得持有 block的對(duì)象也可能被block持有,從而形成循環(huán)引用,導(dǎo)致兩者都不能被釋放:
```
@implementation LXDObject
{
void (^_cycleReferenceBlock)(void);
}
- (void)viewDidLoad
{
[super viewDidLoad];
_cycleReferenceBlock = ^{
NSLog(@"%@", self); //引發(fā)循環(huán)引用
};
}
@end
```
這種情況最后會(huì)導(dǎo)致內(nèi)存泄露,兩者都無(wú)法釋放。跟普通變量存在__block關(guān)鍵字 一樣的,系統(tǒng)提供給我們__weak的關(guān)鍵字用來(lái)修飾對(duì)象變量,聲明這是一個(gè)弱引用 的對(duì)象,從而解決了循環(huán)引用的問(wèn)題。
```
__weak typeof(*&self) weakSelf = self;
_cycleReferenceBlock = ^{
NSLog(@"%@", weakSelf); //弱指針引用,不會(huì)造成循環(huán)引用
};
```
## 異常、錯(cuò)誤處理、斷言、日志和調(diào)試支持,對(duì)單元測(cè)試的支持
- 異常
老生常談的try catch,與其他oo語(yǔ)言一樣,不再多述。
```
@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
}
```
- 錯(cuò)誤處理
NSError是objc的系統(tǒng)錯(cuò)誤信息類。其有三個(gè)較重要的私有變量:
- code
是一個(gè)整數(shù),最好是一個(gè)枚舉,和特定的錯(cuò)誤域是對(duì)應(yīng)的。
- domain
一個(gè)字符串,標(biāo)記錯(cuò)誤域。
- userInfo
一個(gè)字典,包括任意的鍵值對(duì)。其中有:
1. NSLocalizedDescriptionKey:本地化的錯(cuò)誤描述
2. NSLocalizedRecoverySuggestionErrorKey:本地化的恢復(fù)建議
3. NSLocalizedFailureReasonErrorKey:本地化的失敗原因
NSError主要有兩個(gè)用法:
- 獲取錯(cuò)誤信息
```
//獲取錯(cuò)誤
NSError *error = nil;
BOOL success = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"path" error:&error];
if (!success) {
NSLog(@"%@", [error localizedDescription]);
}
```
- 編輯錯(cuò)誤信息
```
//預(yù)定義信息
#define JohnnyErrorDomain @"com.JohnnyError.Domain"
typedef NS_ENUM(NSInteger, ErrorFail){
ErrorOne = 1,
ErrorTwo,
ErrorThree
};
```
```
//產(chǎn)生錯(cuò)誤信息
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()是一個(gè)宏,用于開(kāi)發(fā)階段調(diào)試程序中的Bug,通過(guò)為NSAssert()傳 遞條件表達(dá)式來(lái)斷定是否屬于Bug,滿足條件返回真值,程序繼續(xù)運(yùn)行,如果返回 假值,則拋出異常,并且可以自定義異常描述。
NSAssert()是這樣定義的
```
#define NSAssert(condition, desc)
```
NSAssert用法
```
int a = 1;
NSCAssert(a == 2, @"a must equal to 2"); //第一個(gè)參數(shù)是條件,如果第一個(gè)參數(shù)不滿足條件,就會(huì)記錄并打印后面的字符串
```
- NSParameterAssert
NSAssert和 NSParameterAssert的區(qū)別是前者是針對(duì)條件斷言, 后者只是針對(duì)參數(shù)是否存在的斷言, 調(diào)試時(shí)候可以結(jié)合使用,先判斷參數(shù),再進(jìn)一步斷言,確認(rèn)原因.
NSParameterAssert用法
```
- (void)assertWithPara:(NSString *)str
{
NSParameterAssert(str); //只需要一個(gè)參數(shù),如果參數(shù)存在程序繼續(xù)運(yùn)行,如果參數(shù)為空,則程序停止打印日志
//further code ...
}
```
- 自定義NSAssertionHandler
Objc中的斷言處理使用的是 NSAssertionHandler。
每個(gè)線程擁有它自己的斷言處理器,它是 NSAssertionHandler 類的實(shí)例對(duì)象。NSAssertionHandler實(shí)例是自動(dòng)創(chuàng)建的,用于處理錯(cuò)誤斷言。如果 NSAssert和NSCAssert條件評(píng)估為錯(cuò)誤,會(huì)向 NSAssertionHandler實(shí)例發(fā)送一個(gè)表示錯(cuò)誤的字符串。每個(gè)線程都有它自己的NSAssertionHandler實(shí)例。
我們可以自定義處理方法,從而使用斷言的時(shí)候,控制臺(tái)輸出錯(cuò)誤,但是程序不會(huì)直接崩潰。
```
#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];
//給當(dāng)前的線程
[[[NSThread currentThread] threadDictionary] setValue:myHandler
forKey:NSAssertionHandlerKey];
```
自定義NSAssertionHandler后,程序能夠獲得斷言失敗后的信息,但是程序可以繼續(xù)運(yùn)行,不會(huì)強(qiáng)制退出程序.
- 日志
objc中日志輸出處理主要使用的是NSLog,要在日志輸出信息中添加上下文信息,編譯器提供了常用的表達(dá)式。
| Expression | Format Specifier | Description |
|-----------------|------------------------|--- -------------|
|NSStringFromSelector(_cmd) | %@ | 當(dāng)前選擇器的名字 |
| NSStringFromClass([self class]) | %@ | 當(dāng)前對(duì)象類的名字 |
| [[NSString stringWithUTF8String:\__FILE__] lastPathComponent] | %@ | 源碼文件的名稱|
| [NSThread callStackSymbols] | %@ | 當(dāng)前棧信息的刻度字符串?dāng)?shù)組。僅用于調(diào)試,不用向終端用戶展示或者在代碼中用作任何邏輯。|
- 單元測(cè)試
objc中可以使用OCUnit(即用XCTest進(jìn)行測(cè)試)其實(shí)就是蘋(píng)果自帶的測(cè)試框架。
一般測(cè)試用例分為三個(gè)階段:排列資源、執(zhí)行行為、斷言結(jié)果。
- 排列資源
排列資源,便是提供一切測(cè)試方法所需要的東西,而這些東西便稱之為資源。這些資源包括:
1. 方法的輸入?yún)?shù)
2. 方法所執(zhí)行的特定上下文
這個(gè)階段相當(dāng)于準(zhǔn)備階段,一切都是為了這個(gè)用例中執(zhí)行行為而作準(zhǔn)備,如果沒(méi)有任何需要準(zhǔn)備的數(shù)據(jù),這個(gè)階段是可以被忽略的。
```
- (void)test_setObject$forKey {
// arrange
NSString *key = @"test_key";
NSString *value = @"test_value";
NSMutableDictionary *dic = [NSMutableDictionary new];
}
```
- 執(zhí)行行為
當(dāng)準(zhǔn)備階段完畢后,便進(jìn)入要測(cè)試行為的執(zhí)行階段,在這個(gè)階段,我們會(huì)使用準(zhǔn)備好的資源,并記錄下行為的輸出以供下個(gè)階段使用。這里的行為輸出不一定就是方法執(zhí)行的返回值,很多時(shí)候我們要測(cè)試的方法并沒(méi)有任何返回值,但一個(gè)方法執(zhí)行后,總歸會(huì)有一個(gè)預(yù)期的行為會(huì)發(fā)生,即便是空方法也是(什么都不會(huì)被改變),而這個(gè)預(yù)期行為便是測(cè)試行為的輸出。
加入執(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é)果
最后一步,也是最核心的一步,它決定著一個(gè)測(cè)試用例的成功與否,我們需要在這一步斷言執(zhí)行行為的輸出是否達(dá)到預(yù)期。確定一個(gè)行為的輸出,我們可能需要有多次斷言,這里需要遵循一個(gè)原則:**先執(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)錯(cuò)誤原因。
## RunTime
Objc是一門(mén)動(dòng)態(tài)語(yǔ)言,所以它總是想辦法把一些決定工作從編譯連接推遲到運(yùn)行時(shí)。也就是說(shuō)只有編譯器是不夠的,還需要一個(gè)運(yùn)行時(shí)系統(tǒng) (runtime system) 來(lái)執(zhí)行編譯后的代碼。
- 使用RunTime的場(chǎng)景
OC程序使用Runtime 系統(tǒng)有三種情景:Objective-C Source Code、NSObject Methods、Runtime Functions;
- Objective-C Source Code
- NSObject Methods
- Runtime Functions
- 消息機(jī)制
上面提到過(guò),RunTime的實(shí)質(zhì)就是消息的發(fā)送。在objc中,調(diào)用方法:
```
[receiver messge]
```
在編譯器中會(huì)轉(zhuǎn)換成消息機(jī)制里的消息發(fā)送形式
```
objc_msgSend(receiver, selector)
//帶參數(shù)
objc_msgSend(receiver, selector, arg1, arg2...)
```
消息功能為動(dòng)態(tài)綁定做了很多必要的工作:
1. 通過(guò)selector在消息接收者class里選擇方法實(shí)現(xiàn)(method implement)
2. 調(diào)用方法實(shí)現(xiàn),傳遞到接收對(duì)象with參數(shù)
3. 傳遞方法實(shí)現(xiàn)返回值
為了讓編譯器編譯時(shí),消息機(jī)制與類的結(jié)構(gòu)關(guān)聯(lián)上,每個(gè)類的結(jié)構(gòu)里添加了兩個(gè)基本的元素:
1. 指向父類的指針(isa指針)
2. 類調(diào)度表(A class dispatch table),通過(guò)Selector方法名在dispatch table里面匹配對(duì)應(yīng)的方法地址(class-specific address)
當(dāng)一個(gè)對(duì)象被創(chuàng)建并分配內(nèi)存時(shí),它的實(shí)例里的變量會(huì)初始化,里面有一個(gè)指向它的類的結(jié)構(gòu)體,isa指針。
消息發(fā)送到一個(gè)對(duì)象時(shí),通過(guò)class結(jié)構(gòu)體里的isa指針在dispatch table里尋找相應(yīng)的selector,如果找不到便進(jìn)入父類里找,一直找到NSObject,一旦定位到selector,便調(diào)用該方法,傳遞相關(guān)數(shù)據(jù)。為了提高效率,RunTime會(huì)緩存調(diào)用過(guò)的selector和方法地址,在到dispatch table查找之前,先到cache里查找。
## 如何進(jìn)行callback方法調(diào)用,如何支持事件驅(qū)動(dòng)編程模型
- 非正式協(xié)議
引用《Cocoa設(shè)計(jì)模式》
> 非正式協(xié)議通常定義為NSObject的類別。類別接口中指定的方法可能會(huì)或者可能不會(huì)被框架類實(shí)際地實(shí)現(xiàn)。非正式協(xié)議位于一種設(shè)計(jì)灰區(qū)中。正式協(xié)議由編譯器檢查并且代表一種關(guān)于對(duì)象能力的保證,但是非正式協(xié)議不會(huì)做出保證----而只會(huì)給出提示。
引用官方文檔
> 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é)議從概念上理解起來(lái)就簡(jiǎn)單的多了,它指的是一個(gè)以@protocol方式命名的方法列表,與非正式協(xié)議相比不同的是,它要求顯示的采用協(xié)議。
- 正式協(xié)議的聲明
1. @required 該類的方式在遵守相應(yīng)協(xié)議的類中是必須被實(shí)現(xiàn)的,不然編譯器會(huì)警告(顯然這是在編譯時(shí)做的檢查,而不是在運(yùn)行時(shí))
2. @optional 該類的方法在遵守相應(yīng)協(xié)議的類中是否實(shí)現(xiàn)是可選的,@optional已取代非正式協(xié)議
- 正式協(xié)議的繼承性
正式協(xié)議和類一樣,是可以繼承的,書(shū)寫(xiě)格式同類繼承相似:
```
@protocol NewProtocal <Protocal>
@end
```
- 委托方法
委托概念什么的這里略過(guò)。下面主要敘述如何在objc中定義和使用委托。
- 定義委托
```
#import <BlaClass/BlaClass.h>
@class MyClass; //定義類,這樣協(xié)議可以看到MyClass
@protocol MyClassDelegate //定義委托協(xié)議
- (void) myClassDelegateMethod: (MyClass *) sender; //定義在另一個(gè)類里實(shí)現(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è)置委托至自身的某個(gè)地方
```
- 使用委托, 如下形式
```
if([[self delegate] respondsToSelector:@selector(windowDidMove:)]) {
[[self delegate] windowDidMove:notification];
}
```
- 響應(yīng)選擇器selector
selector,實(shí)際上是函數(shù)指針的一種實(shí)現(xiàn)形式,我們用一個(gè) C string 來(lái)表示對(duì)象中的某個(gè)函數(shù),所以就可以把這個(gè)函數(shù)作為參數(shù),傳到其他的方法中去進(jìn)行調(diào)用。
Objective-C 的 Class 在編譯時(shí)會(huì)變成 C struct,Class 中包含的方法也會(huì)轉(zhuǎn)換成 C function。之后在運(yùn)行的時(shí)候,runtime 會(huì)建立起從 Objective-C Method 到 C function 的映射(可以認(rèn)為是一個(gè) virtual table)。
Runtime 會(huì)為每個(gè)類準(zhǔn)備一個(gè) virtual table,里面是一個(gè)個(gè)鍵值對(duì),key 稱為 selector,類型是 SEL,value 實(shí)際上是 C function 的函數(shù)指針,類型是 IMP。而這里的 SEL 類型實(shí)際上就是 C string。
因此 selector 可以看做是函數(shù)的另一個(gè)名字,所以很多需要調(diào)用函數(shù)或者建立連接的地方,都可以用到。
- objc的回調(diào)實(shí)現(xiàn)
- Run loop
objc提供的NSRunLoop實(shí)例會(huì)持續(xù)等待著,當(dāng)特定事件發(fā)生時(shí),觸發(fā)回調(diào)(callback)。
調(diào)用以下方法,即可得到一個(gè)run loop。
```
[[NSRunLoop currentRunLoop] run];
```
- target-action/目標(biāo)-動(dòng)作對(duì)
實(shí)例:
```
// 為按鈕添加回調(diào)——Target-action/目標(biāo)-動(dòng)作對(duì)
// 第一個(gè)參數(shù):發(fā)送消息給誰(shuí)
// 第二個(gè)參數(shù):事件發(fā)生后,執(zhí)行什么代碼(回調(diào))
// 第三個(gè)參數(shù):發(fā)生哪類型的點(diǎn)擊事件會(huì)觸發(fā)回調(diào)
[button addTarget:self
action:@selector(click:)
forControlEvents:UIControlEventTouchUpInside];
```
目標(biāo)-動(dòng)作對(duì),就是當(dāng)事件發(fā)生時(shí),像指定的對(duì)象發(fā)送指定的消息。對(duì)target,action的對(duì)應(yīng)理解,可以這樣認(rèn)為,執(zhí)行某個(gè)類(target)的某個(gè)方法。
- Helper object/委托
委托使用如上文所示。
- Notification/通告
objc提供了一個(gè)叫做「通告中心」的對(duì)象,可以通過(guò)[NSNotificationCenter defaultCenter]獲得,利用這個(gè)通告中心,我們可以「發(fā)通告」、「監(jiān)測(cè)(接收)通告」,利用這個(gè)機(jī)制,實(shí)現(xiàn)回調(diào)。
- Block
在objc中使用block實(shí)現(xiàn)回調(diào),除了基本聲明語(yǔ)法,其他與大多oo語(yǔ)言相同,不再敘述。
```
#import <Foundation/Foundation.h>
@import CoreBluetooth;
// 步驟1:
// 將Block重新定義為一種新的數(shù)據(jù)類型
// 這個(gè)Block無(wú)返回值;有一個(gè)參數(shù)(類型為NSUInteger)
typedef void(^AllDevicesDidConnectedBlock)(NSUInteger divicesCount);
@interface MyCnetralManager : NSObject
// 步驟2:
// 聲明一個(gè)(Block)變量
@property (nonatomic, strong) AllDevicesDidConnectedBlock callbackForAllDevicesDidConnected;
@end
```
然后,就可以使用了。
- 總結(jié)
1. 當(dāng)只發(fā)生單個(gè)事件(event),只需要完成一件事情進(jìn)行響應(yīng),建議用「Target-action/目標(biāo)-動(dòng)作對(duì)」。比如NSTimer、UIButton等。
2. 當(dāng)會(huì)發(fā)生若干事件(event),要完成多件事情進(jìn)行響應(yīng),建議使用「Helper objects/輔助對(duì)象」,當(dāng)然了,最常見(jiàn)的是「delegate/委托」(另外還有「data sources/數(shù)據(jù)源」)。
3. 當(dāng)發(fā)生單個(gè)事件(event),多個(gè)對(duì)象要進(jìn)行響應(yīng),建議使用「Notifications/通告」
4. 使用Block,可以寫(xiě)出更簡(jiǎn)潔的代碼、更好的代碼結(jié)構(gòu)。