標籤:類方法 匿名類 new和allocinit的區別
———————————————————————————————————————————
類方法
①類方法:
+ 開頭的方法(已定義流程形式和對象方法一樣,只不過 + 開頭,這是唯一的區別)
類方法的調用:
[類名 方法名];
②對象方法:
- 開頭的方法
對象方法的調用:
[執行個體對象名 方法名];
★ 代碼對比學習:
有一個 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:
[email protected]"白色";
break;
case kColorBlack:
[email protected]"黑色";
break;
case kColorTHJ:
[email protected]"土豪金";
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的方式可以用其他定製的初始化方法★★★
———————————————————————————————————————————
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
Objective-C 學習筆記(Day 3,上)