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文件中實現了具體的代碼。

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語法在博客園撰寫文章,總體感覺不錯,希望能加入流程圖、序列圖


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM