Objective-C 學習筆記(Day 3,上)

———————————————————————————————————————————
類方法


  ①類方法:
 
     + 開頭的方法(定義的過程形式和對象方法同樣,只不過 + 開頭,這是惟一的區別)
 
  類方法的調用:
 
     [類名   方法名];
 
  ②對象方法:

     - 開頭的方法

  對象方法的調用:

    [實例對象名   方法名];
 
  ★ 代碼對比學習:
 
    有一個 Dog類,在裏面有這麼一個對象方法:
 
     -(void)run;  //對象方法
 
     想調用 run方法,
 
     Dog *dog = [Dog new];
     [dog run];
 
 
    在 Dog類 裏面還存在另一個類方法:
     
     +(void)run;  //類方法
     
     想調用 run 方法,

     [Dog run];


★★★ 在上面的描述中,咱們能夠看到,兩個方法的名稱都是  run ,可是隻是前面的符號不一樣,一個是  +  ,另外一個是  -  ,這樣子的寫法是能夠的,他表示的是不一樣的方法,咱們能夠形象的看做兩個方法的「性別」差別。對象方法須要建立實例對象進而調用,可是類方法卻能夠直接經過類名調用。類方法的好處就是不須要給他分配堆內存(由於類方法調用不須要建立空間),節省了內存空間。


———————————————————————————————————————————
類方法使用注意事項

①類方法 能夠和 對象方法(實例方法) 同名,並不互相影響使用。
②類方法 能夠從 父類 繼承而來,子類能夠重寫類方法。
③類方法 和 對象方法 同樣從 @interface…@end 裏面聲明,在 @implementation…@end 裏面實現。
④類方法 只能類名調用,實例對象名調用是不能夠的。 對象方法 也是這樣,對象方法只能被 實例對象名調用,而不能被類名調用。
⑤在類方法裏使用了self,這個self執行的是類對象(class object)而不是實例對象(instance object)

例子:(這裏爲了方便觀察,我將頭文件和源文件寫在了一塊兒)

#import <Foundation/Foundation.h>

@interface Caculator : NSObject
-(int)add:(int)num1 andNum2:(int)num2;
+(int)add:(int)num1 andNum2:(int)num2;
@end

@implementation Caculator
-(int)add:(int)num1 andNum2:(int)num2
{
    return num1+num2;
}
+(int)add:(int)num1 andNum2:(int)num2
{
    return num1+num2;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Caculator *caculator=[Caculator new];
        int result1=[caculator add:12 andNum2:13];
        
        int result2=[Caculator add:22 andNum2:15];
        
        NSLog(@"%d,%d",result1,result2);
    }
    return 0;
}


分析:上面是一個Caculator類,而後裏面分別寫了一個類方法和一個對象方法,顯然除了方法名頭上標識它們究竟是什麼方法的符號外,這兩個方法是同名的(方法名都是 add: andNum2:),咱們用一個實例對象和類名分別調用這兩個方法,顯然,是正確的,也沒有互相影響。

而後咱們再在上面的方法聲明中添加一個對象方法(上面沒有羅列,須要讀者本身去寫),如:    -(int)jian:(int)num1 andNum2:(int)num2;   這個咱們認爲是一個減法的對象方法。(實現部分本身寫) 而後咱們用類名去調用它:      [Caculator jian:14 andNum2:12];
顯然這是錯誤的,系統報錯: No known class method for selector ‘jian:andNum2:' 。這句話告訴咱們,系統不知道有名爲 ‘jian:andNum2:’ 的類方法(class method)。


———————————————————————————————————————————
類方法的調用和要點彙總

這一部分,設計並驗證了類方法的調用這個知識點。
首先咱們應該清楚,類方法是靈活的,咱們設計了兩個大的方面去研究類方法的調用。

(1)★對於同一個類而言★
①對象方法的內部能夠調用類方法(對—>類)
②類方法的內部能夠去調用同類的另一個類方法(類—>類)
③類方法的內部能夠調用對象方法(類—>對)

(2)★對於不一樣的類而言★(舉例兩個類:A類和B類)
①A的對象方法的內部能夠調用B的類方法(對—>類)
②A的類方法的內部能夠調用B的類方法(類—>類)
③A的類方法的內部能夠調用B的對象方法(類—>對)

下面爲了驗證以上的6條,我設計並實現了下面的程序。(這個程序將兩個類(Car和Dog)的聲明和實現都寫在了一塊兒,方便觀察)

#import <Foundation/Foundation.h>

@interface Car : NSObject
-(void)c1;
+(void)c2;
+(void)c3;
+(void)test:(Dog *)dog;
@end

@implementation Car
-(void)c1
{
    NSLog(@"這裏是Car的對象方法c1。咱們會在下面調用Car的其中一個類方法c2。");
    [Car c2];
}
+(void)c2
{
    NSLog(@"這裏是Car的類方法c2。咱們會在下面調用Dog的類方法d1");
    [Dog d1];
}

+(void)c3
{
    NSLog(@"這裏是Car的類方法c3。咱們會在下面調用Dog的對象方法d4");
    Dog *dog11=[Dog new];
    [dog11 d4];
}

+(void)test:(Dog *)dog
{
    [dog d2];
    
    Dog *dd=[Dog new];
    [dd d2];
}
@end

@interface Dog : NSObject
+(void)d1;
-(void)d2;
+(void)d3;
-(void)d4;
@end

@implementation Dog
+(void)d1
{
    NSLog(@"這是Dog的類方法d1。咱們會在下面調用Dog的類方法d3");
    [Dog d3];
}
-(void)d2
{
    NSLog(@"這是Dog的對象方法d2。咱們會在下面調用Car的類方法c3");
    [Car c3];
}
+(void)d3
{
    NSLog(@"這是Dog的類方法d3。咱們會在下面調用Dog的對象方法d2");
    Dog *dd1=[Dog new];
    [dd1 d2];
}
-(void)d4
{
    NSLog(@"這裏是Dog的對象方法d4。");
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car *car=[Car new];
        [car c1];   //★★★斷點設置處★★★
    }
    return 0;
}


上面的程序讀者能夠拷貝去驗證一下,整個流程是這樣的:

調用Car的對象方法(c1)    在方法內      調用Car的類方法(c2)
調用Car的類方法(c2)    在方法內      調用Dog的類方法(d1)
調用Dog的類方法(d1)    在方法內      調用Dog的類方法(d3)
調用Dog的類方法(d3)    在方法內      調用Dog的對象方法(d2)
調用Dog的對象方法(d2)    在方法內      調用Car的類方法(c3)
調用Car的類方法(c3)    在方法內      調用Dog的對象方法(d4)

咱們能夠將斷點設置在main函數中合適位置,而後Step into去一步一步的來看整個程序的流程,觀察跳躍的過程。便於理解。


再說一點,由於咱們這裏學習的是類方法的調用,因此關於「在A的對象方法 內部調用 A的另外一個對象方法」(對—>對)和「在A的對象方法 內部調用 B的對象方法」(對—>對)這兩個狀況咱們就不作驗證了,固然結果也是必定的,一樣能夠互相調用。有興趣的同窗能夠去驗證一下。


★★★另外說明一下這些狀況:

★//對象方法中能夠調用其餘的對象方法
    // (1)在當前對象方法內部建立對象(能夠是當前類實例對象,也能夠是其餘類的實例對象),使用新建立的對象調用對象方法
    // (2)可使用self
    // (3)對象做爲方法的參數傳遞過來,可使用傳遞過來的對象調用方法

★//在類方法中能夠調用其餘類方法
    //(1)能夠直接使用本類類名(或者其餘類名)調用類方法
    //(2)可使用self

  (另外,在類方法中不容許訪問實例變量(也就是不容許出現類的聲明中屬性成員),以下圖)面試


_speed是Car類聲明的一個屬性成員,這是不容許出如今類方法中的。由於調用類方法沒有分配存儲空間,堆區是空的,因此無法存儲值,因此不能訪問。

★//在類方法中能夠調用對象方法
    //(1)對象做爲方法的參數傳遞過來
    //(2)能夠建立一個對象再調用
(以下圖)


最後一點須要說明的,那就是類方法不能調用自身,不然就會出現死循環(沒法靠自身控制終止循環)。(以下圖,在類方法 t1 的內部再次調用了 t1 ,那麼一旦在main函數中 [Car t1];   ,那麼循環就不會終止)


我還去驗證了一下對象方法是否能在內部去調用自身,會出錯,這個很無聊,你們不用去驗證了~
函數



———————————————————————————————————————————
類方法的一個應用(IPhone 顏色)

#import <Foundation/Foundation.h>
typedef enum {kColorWhite,kColorBlack,kColorTHJ} Color;

@interface IPhone : NSObject
{
    @public
    Color _color;
}

+(NSString *)toGetIPhoneColor:(Color)color;
//返回值類型須要是 字符串類型

@end

@implementation IPhone
+(NSString *)toGetIPhoneColor:(Color)color
{
    NSString *str;//咱們須要一個字符串變量來接收返回的值
    //這裏用了 switch語句 用來分別判斷代碼表示的什麼顏色,而後轉化爲漢字字符串返回
    switch (color) {
        case kColorWhite:
            str=@"白色";
            break;
        case kColorBlack:
            str=@"黑色";
            break;
        case kColorTHJ:
            str=@"土豪金";
            break;
        default:
            break;
    }
    return str;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        //咱們須要顯示手機顏色的一個方法,因此這個方法只須要往裏面傳值就能夠了,咱們須要把咱們定義的枚舉類型的值轉化爲能讓人理解的漢字去表示顏色。這個地方不須要建立對象,因此要用類方法。咱們調用類方法去接收並處理一個顏色,最後返回一個字符串類型的值,而後用一個字符串變量去接收這個值,而後輸出這個值,這就是整個思路
        NSString *str=[IPhone toGetIPhoneColor:kColorTHJ];
        NSLog(@"%@",str);
    }
    return 0;
}


———————————————————————————————————————————
 匿名類的概念及使用

#import <Foundation/Foundation.h>
#import "Car.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //①使用匿名類能夠調用方法
        [[Car new] start];//使用匿名類能夠調用方法(其實這裏理解起來可能有些彆扭,這裏爲何叫作匿名類而不是別的。咱們能夠暫時這樣理解:以前咱們見到new的時候是建立實例對象的時候,而如今咱們申請內存空間了也初始化了,可是卻沒有實例對象,因此不能叫匿名對象,那麼這個東西在類裏面出現的,就暫且叫爲匿名類吧。new是一個方法)
        
        [[[Car alloc]init]start];//這句話等價於上面的 [[Car new] start];  ,咱們知道new作了兩個事情,一個是分配存儲空間(alloc),另一個就是初始化(init)。new是alloc和init的結合,平時由於方便,咱們一直用new,其實兩種寫法都同樣。
        [[[Car alloc]init]stop];
        
        //②使用匿名類能夠訪問實例變量(成員變量)
        [Car new]->_speed=100;
        NSLog(@"speed:%d",[Car new]->_speed);
        //輸出 speed:0
        //對於用匿名類訪問實例變量(成員屬性),★只能訪問一次★,咱們第一次訪問設置_speed值爲 100 ,可是輸出的時候至關於又訪問了一次,此時沒有設置值,因此就輸出系統自動初始化的值,也就是 0
    }
    return 0;
}

//  匿名類是不能有名字的類,它們不能被引用,只能在建立時用New語句來聲明它們。匿名類的聲明是在編譯時進行的,實例化在運行時進行。這意味着for循環中的一個new語句會建立相同匿名類的幾個實例,而不是建立幾個不一樣匿名類的一個實例。
//  ★上面這句話說的很好,總結爲:編譯時聲明,運行時實例化!


———————————————————————————————————————————
new 和 alloc/init 的區別

在上面一小節咱們對new有了新的認識,如今我重點去解釋一下這兩個的相同於不一樣。

1.在實際開發中不多會用到new,通常建立對象看到的全是[[className alloc] init]
可是並不意味着你不會接觸到new,在一些代碼中仍是會看到[className new],
還有去面試的時候,也極可能被問到這個問題。
2.那麼,他們二者之間到底有什麼區別呢
咱們看源碼:

+ new
{
    id newObject = (*_alloc)((Class)self, 0);
    Class metaClass = self->isa;
    if (class_getVersion(metaClass) > 1)
        return [newObject init];
    else
        return newObject;
}

而 alloc/init 像這樣:
+ alloc
{
    return (*_zoneAlloc)((Class)self, 0, malloc_default_zone());
}
- init
{
    return self;
}

經過源碼中咱們發現,[className new]基本等同於[[className alloc] init];
區別只在於alloc分配內存的時候使用了zone.
它(zone)是給對象分配內存的時候,把關聯的對象分配到一個相鄰的內存區域內,以便於調用時消耗不多的代價,提高了程序處理速度;

3.而爲何不推薦使用new?
不知你們發現了沒有:若是使用new的話,初始化方法被固定死只能調用init.
而你想調用initXXX怎麼辦?不可能!聽說最初的設計是徹底借鑑Smalltalk語法來的。
傳說那個時候已經有allocFromZone:這個方法,
可是這個方法須要傳個參數id myCompanion = [[TheClass allocFromZone:[self zone]] init];
這個方法像下面這樣:

+ allocFromZone:(void *) z
{
    return (*_zoneAlloc)((Class)self, 0, z);
}

//後來簡化爲下面這個:
+ alloc
{
    return (*_zoneAlloc)((Class)self, 0, malloc_default_zone());
}

可是,出現個問題:這個方法只是給對象分配了內存,並無初始化實例變量。
是否是又回到new那樣的處理方式:在方法內部隱式調用init方法呢?
後來發現「顯示調用總比隱式調用要好」,因此後來就把兩個方法分開了。

★★★歸納來講,new和alloc/init在功能上幾乎是一致的,分配內存並完成初始化。
差異在於,採用new的方式只能採用默認的init方法完成初始化,
採用alloc的方式能夠用其餘定製的初始化方法★★★


———————————————————————————————————————————

版權聲明:本文爲博主原創文章,未經博主容許不得轉載。學習

相關文章
相關標籤/搜索