ObjectC 上手

本文結(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>

http://ios.jobbole.com/83082/

  • 內(nèi)存

http://blog.devtang.com/2016/07/30/ios-memory-management/

  • 單元測(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

  • 類別

https://tech.meituan.com/DiveIntoCategory.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)

    1. 減號(hào)表示一個(gè)函數(shù)或消息的開(kāi)始

      舉個(gè)例子,在c#中,一個(gè)方法的寫(xiě)法是

      private void add(bool isAdd){
          ...
      }
      

      用oc寫(xiě)出來(lái)就是

      -(void)add:(Bool)isAdd{
          ...
      }
      
    2. 加號(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

    1. #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形式。

      詳見(jiàn)

    2. @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ù)的傳遞

    1. 多個(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ù)值名 ...

    2. 參數(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ù)交互處理。
     
     其主要類與方法如下:
     
     ![NSStream的主要類與方法](http://upload-images.jianshu.io/upload_images/6836572-9f75de0b6c31847d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
     
## 序列化和反序列化


前面提到過(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ǔ)法如下圖所示。

 ![objc聲明與語(yǔ)法定義](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)兩個(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)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,083評(píng)論 0 9
  • *面試心聲:其實(shí)這些題本人都沒(méi)怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來(lái)就是把...
    Dove_iOS閱讀 27,655評(píng)論 30 472
  • 她說(shuō)要給我一個(gè)東西,還說(shuō)這東西代表我們的關(guān)系。然后在餐廳考試可我和李豪挨著,做完題后,我就讓他幫忙猜一下,然后我倆...
    小狐的冰山閱讀 236評(píng)論 0 0
  • 再一次聽(tīng)到老牛說(shuō)話實(shí)在yy里,
    飛云閱讀 308評(píng)論 0 15
  • 說(shuō)起林心如,一直以來(lái)給人的印象就是“古裝女神”、“清純玉女”。她在古裝劇《還珠格格》中塑造的紫薇,由于太過(guò)經(jīng)典,至...
    進(jìn)擊的小獅妹閱讀 1,003評(píng)論 0 0

友情鏈接更多精彩內(nèi)容