Objective-C快速上手

最近在開發iOS程序,這篇博文的內容是剛學習Objective-C時作的筆記,力圖達到用最短的時間瞭解OC並使用OC。Objective-C是OS X 和 iOS平臺上面的主要編程語言,它是C語言的超集,在C語言的基礎上增長了面向對象的特性。程序員

Note: 文中代碼所使用的編輯工具是Xcode5編程

一些主要概念

  • 類(Class) :一個應用是由大量的相互關聯的類組成的:包括框架類庫(如Cocoa、Cocoa Touch等)中的類,還有程序員自定定義的類。一個類文件包含兩部分:interface( .h文件)和implementation(.m文件),前者用來申明公開的屬性和方法(嚴格地說,屬性也是方法),後者實現前面聲明的屬性和方法。安全

  • 類別(Categories):在不更改某個Class的代碼狀況下可使用類別對該類的功能進行擴展。擴展的代碼能夠放在單獨的文件或者放在被擴展類的implementation部分(若是能夠看到的話,框架中的類如NSString,程序員是看不到其實現部分的)框架

  • 協議(Protocols):協議相似於C#或Java語言的接口,用來聲明一組相關的方法,這些方法能夠被申明爲可選的(optional)或是必須的 (required),若是一個類遵循了一個協議,可選的方法不是必需要實現的。另外,協議能夠繼承於另外一個協議(一般都是繼承於NSObject這個協議的)。編程語言

  • 塊(Blocks) :這個東西和匿名函數很相似函數

定義類

在.h文件中聲明類的名稱、屬性和方法,放在.h中的屬性和方法都是公開的,可以在其餘的導入(import)了該.h文件的類裏使用。在.m文件中實現具體的代碼,下面是一個類的定義:工具

  • Person.h文件:
#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (strong, nonatomic)NSString *name;
@property NSInteger age;

- (void) work;
- (void) workFor:(NSString*)companyName;
- (void) buy:(NSString*)something from:(NSString*)store at:(NSDate*)time;
+ (NSString*) whoIsYourDad;

@end
  • Person.m文件:
#import "Person.h"

@implementation Person
{
     int instanceVar;
}

- (void) work
{
      NSLog(@"work work");
}

 - (void) workFor:(NSString*)companyName
{
      NSLog(@"I am working for %@", companyName);
}

- (void) buy:(NSString*)something from:(NSString*)store at:(NSDate*)time
{
      NSLog(@"I love shopping");
}

+ (NSString*) whoIsYourDad
{
      return @"I like WARIII";
}
@end

其中,person.h文件中聲明瞭類名、屬性名稱和方法名稱。person.m文件中實現了具體的代碼。post

Note: #import是一個預處理指令,所謂預處理是指編譯器編譯代碼前先要把源文件處理一次,好比把Foundation/Foundation.h的內容包括進來,這樣才能作下一步的編譯工做。當要包含SDK中的類庫時,使用尖括號,如:#import <Foundation/Foundation.h>;當要包含項目中本身寫的.h文件時用引號,如:#import "Person.h"。用#include也能夠達到一樣的目標,可是不建議使用,聽說是後者會把同一個.h包含屢次,而前者只包含一次。學習

類的聲明

類的申明部分放在Person.h文件中,以@interface開頭,以@end結尾測試

@interface Person : NSObject
//code
@end

Person是類的名稱,NSObject爲父類名稱,用冒號表示繼承關係,Objective-C和C#、Java是同樣的,不支持多繼承。

實例變量

實例變量寫在一對大括號內,如本例中的instanceVar就是實例變量:

@implementation Person
{
    int instanceVar;
}

Note:放置類變量的大括號謂語關鍵字@implementation@interface 下方,便可以在.h文件中,也能夠在.m文件中,具體有什麼區別,我也沒研究,知道的仁兄請賜教我吧。

類名有講究

Objective-C沒有命名空間或包的概念,能夠經過在類名前面加上前綴以達到區分彼此的做用,如NSString類的NS就是前綴,可能表示NeXTstep系統,熟悉喬布斯的人應該知道這段淵源。

屬性

@property (strong, nonatomic)NSString *name;
@property NSInteger age

屬性的聲明語句寫在關鍵字@interface下面,@end以前。關鍵字@property用來申明屬性,編譯器會根據這個關鍵字自動生成一個實例變量_age以及相關的get和set方法。也能夠手動完成這些工做,效果是同樣的,須要注意的是:在調用get和set方法時,get方法名稱和屬性名稱同樣,不須要get前綴,但set方法須要寫set前綴,不過一般也不用調用這個兩個方法,直接點號.後面加屬性名稱就好了。

若是屬性的數據類型是基本類型,如本例中的age屬性,屬性前不用加上星號(固然,加上也不錯,加上的話就表示是一個指針形的變量),不然若是數據類型是對象型的就必須加上星號。

strongnonatomic關鍵字代表了屬性的某些特徵,其中 strong表示屬性是強引用類型的,於strong相對應的是weak,表示弱應用類型。在Objective-C運行過程當中,經過被一個被稱爲自動引用計數器(ARC)的技術手段監控,若是指向一個在堆內存中的對象的強引用數量爲0時,系統自動釋放這個對象所佔據的內存,storngweak關鍵字用來表示強引用和弱引用。此外還有一些經常使用修飾屬性的關鍵字,列表以下:

關鍵字 描述 是否默認
strong 強引用
weak 弱引用
readonly 只讀
readwrite 可讀可寫
natomic 具備原子性、線程安全的
nonatomic 不具備原子性,非線程安全的,若是屬性不會被多個線程訪問,建議定義成nonatomic類型,這樣執行效率高
getter 強行指定屬性對應的getter方法
setter 強行指定屬性對應的setter方法

Note:若是指向一個對象的強引用的變量個數減小到0個時,系統自動收回它所佔據的內存。若是一個變量不加strongweak修飾時,默認是strong型的。當出現以下狀況時,須要使用weak關鍵字,對象A強引用了對象B,這是對象B若是要引用對象A的話只能用弱引用,由於若是A和B互相強引用,那麼這兩個對象就永運不會被從內存中清除,這是不合理的。另外,局部變量默認是強引用類型,使用關鍵字 __weak可定義弱引用類型的局部變量。

實例方法

- (void) work;
- (void) workFor:(NSString*)companyName;
- (void) buy:(NSString*)something from:(NSString*)store at:(NSDate*)time;

實例方法的聲明以-開頭,聲明語句位於關鍵字@interface和關鍵字@end之間,和屬性是同樣的(屬性本質上也是方法),以第二個方法爲例:(void)表示返回值,workFor是方法的名稱,(NSString*)表示參數的類型,companyName是參數,能夠在方法體內訪問。workFor方法在.h文件中申明而且在.m文件中實現。

Objective-C的方法命名格式以一種接近天然語言形式定義,如上述最後一個那樣,它的方法名稱是 buy:from:at須要注意的是:冒號也是方法名稱的組成部分。Objective-C不存在重載方法的概念,好比以下三個方法是徹底獨立的方法,而不是重載方法:

實例方法的實如今.m文件中,請參考上面的Person.m文件。

-(void) init;
-(void) init:(NSStrng*);
-(void) init:(NSString*) with(int)parm;

其中比較特殊的是init方法,該方法繼承至NSObjectNSObject中包含了不少實用的方法,程序員自定義的類最好繼承於它)。若是須要在初始化類的時候作一些額外的操做(好比給變量賦默認值),能夠重寫init方法,或添加形如initXXX之類的方法(約定俗成的命名方式)像以下這樣:

- (id)init
{
    self = [super init];
    if (self) {
        //code
    }
    return self;
}

- (id)initWithP:(id)p
{
    self = [super init];
    if (self) {
        //code
    }
    return self;
}

類方法

+ (NSString*) whoIsYourDad;

類方法和實例方法的區別是前綴是+,而非-。一樣聲明語句位於關鍵字@interface和關鍵字@end之間,具體實如今.m文件中,請參考上面的Persion.m文件。

使用類

初始化類

因爲全部的類都繼承於NSObject,可使用NSObject中的兩個方法來初始化類,下面的表達式用來實例化一個Person對象:

Person *person = [[Person alloc]init];

alloc方法用來分配內存,init方法用來作一些初始化操做,相似於於Java和C#的構造函數。

調用方法

不少資料裏不叫「調用方法」而叫「發送消息」,這裏統一叫「調用方法」。
調用方法的格式以下:

[類名 類方法名]; //方法沒有參數
[類名 類方法名:參數...];//方法有參數
[實例名 實例方法名];//方法沒有參數
[實例名 實例方法名:參數...];//方法有參數

Note:若是一個實例變量爲空(nil),調用其方法不會報錯,結果是do nothing。

屬性是特殊的方法,因此也能夠像上面那樣調用,可是也能夠像Java和C#那樣使用點號.訪問,以下面兩種寫法是等價的:

person.age = 50;
[person setAge:50];

var = person.age;
var = [person age];

Note 推薦使用.調用屬性,看上去一目瞭然,無論用那種方式,先後統一仍是很重要的

類別

使用類別能夠對已經存在的類的行爲進行擴展,相似於C#中的擴展類,下面的例子用來給NSString類增長方法:

  • NSString+NSStringAddition.h文件:
#import <Foundation/Foundation.h>

@interface NSString (NSStringAddition)

-(BOOL)islongerThan:(NSString*)str;

@end
  • NSString+NSStringAddition.m文件:
#import "NSString+NSStringAddition.h"

@implementation NSString (NSStringAddition)

 -(BOOL)islongerThan:(NSString*)str
{
    return [self length] > [str length];
}

@end

在main方法中測試:

#import <Foundation/Foundation.h>
#import "NSString+NSStringAddition.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool 
   {
         NSString *strS = @"abc";
         NSString *strL = @"abcd";
         BOOL result = [strL islongerThan:strS];
         if (result)
         {
             NSLog(@"you are long");
         }
    }
    return 0;
}

Note:你們在作測試的時候,用XCode建立項目,最後選擇OS X->Applcation->Command Line Tool,這樣運行的時候不會啓動模擬器,速度快些。

另外一種使用類別的方法是針對本身編寫的能夠看到源碼的類進行擴展,在此狀況下,無需新的.h和.m文件,只要在原來的類的 .m文件中開頭部分加入擴展的方法,如擴展前面的Person類:

#import "Person.h"

@interface Person()

@property NSString* privatePro;

@end

@implementation Person
{
    int instanceVar;
}

- (void) work
{
    NSLog(@"work work");
}
- (void) workFor:(NSString*)companyName
{
     NSLog(@"I am working for %@", companyName);
}

- (void) buy:(NSString*)something from:(NSString*)store at:(NSDate*)time
{
     NSLog(@"I love shopping");
}

+ (NSString*) whoIsYourDad
{
     return @"I like WARIII";
}
@end

此例中增長了一個屬性:privatePro。也能夠增長方法,在「@implementation Person」和「@end」之間實現便可,和其餘方法同樣。在iOS開發中,ViewController一般使用這種擴展方法,將不對外公開的控件聲明在擴展部分。

協議

協議至關於C#、Java中的接口。協議的聲明在.h文件中,現聲明一個協議以下:

#import <Foundation/Foundation.h>

@protocol FlyProtocal <NSObject>

@required

- (void)fly;
- (void)flyTo:(NSObject*)space;

@optional

+ (void)canGodFly;

@end

其中,@required下面的方法是遵循協議的類必須實現的,@optional下面的方法是可選的。能夠看到,canGodFly方法是類方法,協議裏不只能夠申明實例方法,類方法也是能夠的。下面編寫一個遵循協議FlyProtocalBird類:

  • Bird.h
#import <Foundation/Foundation.h>
#import "FlyProtocal.h"
@interface Bird : NSObject <FlyProtocal>

@end
  • Bird.m
#import "Bird.h"

@implementation Bird

- (void)fly
{
     NSLog(@"I can fly");
}

- (void)flyTo:(NSObject*)space
{
     NSLog(@"fly to %@", space);
}

+ (void)canGodFly
{
     NSLog(@"God can fly");
}
@end

觀察Bird.h文件,在類名後加<協議名>即表示類遵循協議。觀察Bird.m,三個接口中定義的方法已經被實現,其中flyflyTo是必須實現的,canGodFly是可選的。

下面編寫測試類ProtocalTest:

  • ProtocalTest.h
#import <Foundation/Foundation.h>
#import "FlyProtocal.h"

@interface ProtocalTest : NSObject

@property id<FlyProtocal> delegate;
-(void)testFly;

@end
  • ProtocalTest.m
#import "ProtocalTest.h"

@implementation ProtocalTest

- (void)testFly
{
    [self.delegate fly];
    [self.delegate flyTo:@"BeiJing"];
}
@end

在ProtocalTest中定義了一個屬性:@property id<FlyProtocal> delegate;,該屬性能夠賦值一個遵循了協議FlyProtocal的類的實例。testFly方法試圖調用屬性delegate所指向的對象的flyflyTo方法。現編輯main方法以下:

#import <Foundation/Foundation.h>
#import "ProtocalTest.h"
#import "Bird.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
         ProtocalTest *test = [[ProtocalTest alloc]init];
         test.delegate = [[Bird alloc]init];
         [test testFly];
    }
    return 0;
}

和C#、Java的不一樣是,協議不能夠當成一個數據類型,上面的例子中 @property id<FlyProtocal> delegate;不能夠寫爲:@property FlyProtocal delegate;

在實際開發中,不少控件的事件和數據源都是用協議來實現,某個ViewController如欲處理一個控件的某個事件,它可能要實現某個指定的協議裏的方法,控件會回調這些方法。

Note: id 類型的變量能夠指向任何對象,它自己已是一個指針,所以無需加上*

塊相似於C#、Java中的匿名函數,不用塊使用其餘舊的方法同樣能夠達到相同的效果,和C#、Java同樣,塊的使用可使代碼更緊湊、便於閱讀,顯得高大上。

定義塊

-(void)method
{
    NSInteger methodVar = 1;
    ^{
        //code
    };
    NSInteger (^myBlock)(NSInteger, NSInteger) = ^(NSInteger p1, NSInteger p2)
    {
        //code
        return methodVar + p1 + p2;
    };
}

如例子所示,塊的定義是這樣的格式:

^(參數類型 參數名稱, 參數類型 參數名稱...){
    代碼
};

申明一個塊類型的變量是這樣的格式:

返回值類型(^變量名稱)(參數類型, 參數類型...);

塊類型的變量便可申明爲方法內部的局部變量,也能夠聲明爲類變量。

塊代碼體內可以使用包含塊的方法內部的變量,如本例子中塊中使用了局部變量methodVar。需注意的是塊中代碼體只能使用methodVar,而不能改變methodVar的值,要想改變局部變量的值,須要在聲明局部變量的時候加上關鍵字__block

使用塊

塊能夠做爲方法的參數傳遞,以汽車爲例,汽車在啓動前和啓動後要加入一些行爲,這個行爲由駕駛員決定,以下爲代碼實例:

  • Car.h
#import <Foundation/Foundation.h>

@interface Car : NSObject

-(void)runWithAction:(void(^)(void))beforeRunBlock afterRun:(void(^)(void))afterRunBlock;

@end
  • Car.m
#import "Car.h"

@implementation Car

-(void)runWithAction:(void(^)(void))beforeRunBlock afterRun:(void(^)(void))afterRunBlock
{
    beforeRunBlock();
    NSLog(@"run");
    afterRunBlock();
}

@end

塊做爲方法參數時格式和聲明一個塊類型的變量相似,只是少了變量名稱

Note: 若是參數傳來的是空(nil),執行 beforeRunBlock()會致使程序崩潰。

寫一個Dirvier類以下:

  • Dirver.h
#import <Foundation/Foundation.h>
@interface Dirver : NSObject
- (void)drive;
@end
  • Dirver.m
#import "Dirver.h"
#import "Car.h"

@implementation Dirver

- (void)drive
{
    Car *car = [[Car alloc]init];

    void (^beforeRun)(void) = ^{
        NSLog(@"check tire");
    };

    void (^afterRun)(void) = ^{
        NSLog(@"close door");
    };

    [car runWithAction:beforeRun afterRun:afterRun];
}
@end

main方法:

#import <Foundation/Foundation.h>
#import "Dirver.h"
int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        Dirver *dirver = [[Dirver alloc]init];
        [dirver drive];
    }
    return 0;
}

運行結果以下:

2014-03-21 13:57:48.037 BlockTest[1211:303] check tire
2014-03-21 13:57:48.039 BlockTest[1211:303] run
2014-03-21 13:57:48.040 BlockTest[1211:303] close door

Driver類中能夠不定義塊變量,以一種相似於匿名方法的書寫格式書寫:

#import "Dirver.h"
#import "Car.h"
@implementation Dirver
- (void)drive
{
    Car *car = [[Car alloc]init];
    [car runWithAction:^{
        NSLog(@"check tire");
    } afterRun:^{
        NSLog(@"close door");
    }];
}
@end

回調

處理事件離不開回調,下面介紹Objective-C的三種回調模式。

arget-action模式

target指某個對象,action指對象的行爲,即某個方法,合起來就是回調某個對象的某個方法,關鍵字@selector用來獲得方法名稱的惟一標識,記做SEL。請看例子。

  • Car.h
#import <Foundation/Foundation.h>

@interface Car : NSObject

-(void) runWithAciton:(id)target selector:(SEL)oneSelector;

@end
  • Car.h
#import "Car.h"
@implementation Car
-(void) runWithAciton:(id)target selector:(SEL)oneSelector;
{
    if ([target respondsToSelector:oneSelector])
    {
        [target performSelector:oneSelector];
    }
    NSLog(@"run");
}
@end

場景:調用汽車跑起來前,要作一些額外的工做,這個工做將經過回調對象target中的方法oneSelector來完成。下面的代碼將指定target和action:

  • test.h
#import <Foundation/Foundation.h>

@interface Test : NSObject

- (void)driver;

@end
  • test.m
#import "Test.h"
#import "Car.h"

@implementation Test

- (void)driver
{
    Car *car = [[Car alloc]init];
    [car runWithAciton:self selector:@selector(doSomeThing)];
}

- (void)doSomeThing
{
    NSLog(@"check tire before running");
}

@end

在main函數中測試:

#import <Foundation/Foundation.h>
#import "Test.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        Test *test = [[Test alloc]init];
        [test driver];
    }
    return 0;
}

結果是:

2014-03-20 14:28:16.309 TELTest[1531:303] check tire before running
2014-03-20 14:28:16.311 TELTest[1531:303] run

協議模式

場景:在汽車跑起來前已經跑起來後須要作一些額外的工做,而且在某些狀況下能夠制止汽車跑起來(好比前方睡着一隻貓的時候),下面將這三個行爲封裝在一個協議中:

#import <Foundation/Foundation.h>

@protocol CarDelegate <NSObject>

-(BOOL)shouldCarRun;
-(void)didBeforeCarRun;
-(void)didAfterCarRun;

@end

在Car類中,增長代碼以下:

  • Car.h
#import <Foundation/Foundation.h>
#import "CarDelegate.h"

@interface Car : NSObject

@property id<CarDelegate> delegate;
-(void) run;
-(void) runWithAciton:(id)target selector:(SEL)oneSelector;

@end
  • Car.m
#import "Car.h"

@implementation Car

-(void) runWithAciton:(id)target selector:(SEL)oneSelector;
{
    if ([target respondsToSelector:oneSelector])
    {
        [target performSelector:oneSelector];
    }
    NSLog(@"run");
}

-(void) run
{
    if (![self.delegate shouldCarRun])
    {
        return;
    }
    [self.delegate didBeforeCarRun];
    NSLog(@"run");
    [self.delegate didAfterCarRun];
}
@end

修改Test類,使其遵循協議CarDelegate:

  • Test.h
#import <Foundation/Foundation.h>
#import "CarDelegate.h"

@interface Test : NSObject<CarDelegate>

- (void)driver;

@end
  • Test.m
#import "Test.h"
#import "Car.h"

@implementation Test

- (void)driver
{
    Car *car = [[Car alloc]init];
    car.delegate = self;
    [car run];
}

-(BOOL)shouldCarRun
{
    //NSLog(@"the cat is sleeping");
    //return NO;
    return YES;
}

-(void)didBeforeCarRun
{
    NSLog(@"check tire before running");
}

-(void)didAfterCarRun
{
    NSLog(@"close the door");
}

@end

再次運行main函數:

2014-03-20 15:08:03.517 TELTest[1709:303] check tire before running
2014-03-20 15:08:03.520 TELTest[1709:303] run
2014-03-20 15:08:03.521 TELTest[1709:303] close the door

消息中心模式

註冊觀察者到消息中心,消息中心發送消息給註冊者,並回調註冊者的方法。下面是一個觀察者的例子:

  • Observer.h
#import <Foundation/Foundation.h>

@interface Observer : NSObject

@end
  • Observer.m
#import "Observer.h"

@implementation Observer

-(id)init
{
    self = [super init];
    if (self)
    {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doSomething:) name:@"TestNotification" object:nil];
    }
    return self;
}

- (void)doSomething:(NSNotification*) aNotification
{
    NSString *message = (NSString*)aNotification.object;
    NSLog(@"the message is %@", message);
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end

在main方法中編寫測試代碼:

#import <Foundation/Foundation.h>
#import "Observer.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
         __unused Observer *observer = [[Observer alloc]init];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"TestNotification" object:@"hello world"];
    }
    return 0;
}

運行結果以下:
2014-03-20 16:12:32.017 ObserverTest[1888:303] the message is hello world

如何選擇

  • 若是是簡單的一個動做,選擇Target-action模式,如點擊一個按鈕所引起的動做
  • 若是是多個動做,使用協議模式,如給UIViewTable綁定數據,設置表格樣式
  • 若是是多個類須要和與之弱耦合的一個類交換信息,考慮使用消息中心模式

全文完

後記

本文章是我學習OC時的筆記,旨在快速上手,並無深刻學習,若有錯誤之處,還請指教。這是我首次用Markdown語法在博客園撰寫文章,整體感受不錯,但願能加入流程圖、序列圖

相關文章
相關標籤/搜索