2012年12月2日 星期日

Objective-C naming convention 的重要性 - ARC class 呼叫 non-ARC Class method

Michael 上課的時候都會和學員提到命名的習慣,每個語言都有其命名的習慣。了解習慣可以幫助初學者很快的進入這個環境想要傳答的意圖是什麼。
命名的範圍很廣,從 Source Code 的檔案名稱到 Class 的名稱,Variable 的命稱,Method 的名稱等等都有其命名的習慣。 打個比方,在寫 Java 時,Java 的 Class 如下定義
class Car {
}
產生一個 Car class 其第一個字大寫是 Java 的命名習慣,而相對應這個 Source code 的檔案,也常命名為 Car.java,習慣就是 Java 的檔案名稱和其內容的第一個 Class 命稱一樣,大寫字開頭,這個是 Java 語言中的習慣。
習慣指的是,即使語法沒有強制說不行,但是大家都這樣做。
而 Objective-C 沒有這樣的習慣。

上述的 class 在 Objective-C 是這樣命名


@interface Car
@end

@implementation Car
@end

Class 的定義在 Objective-C 是分開的分為

@interface Car
@end


@implementation Car
@end
兩個部分。
再來看看另一個語言  C# 是如下定義
 
class Car{
} 
看起來和 Java 有點像,也沒有檔案名稱要和 class 命稱一樣命名的習慣。
我們再來看看 Class 的內部命名習慣。
把剛剛的 class 加上 ivar 和 method.
Java 部分
 
class Car {
  String name;
  String getName(){
    return name;
  }
  void setName(String newName){
    name = newName;
  }
}
新增了 name 這個變數 和 getName 還有 setName 這兩個 method。 getName 稱為 getter 而 setName 則為 setter 。
Java 的命名習慣就是,第一個字小寫,不論 variable 或是 method 的名稱。名詞和名詞之間後面接的名詞第一個字大寫,這種命西習慣稱為 CamelCase 駝峰式大小寫
再來看看 Objective-C 的命名習慣。

@interface Car:NSObject{
  NSString * name;
}
@end

@implementation Car
-(NSString *) name{
  return name;
}

-(void) setName:(NSString *) newName{
  name = newName;
}
@end
 
Objective-C 的命名習慣和 Java 差不多,但是 getter 的地方不一樣,在 Objective-C getter 的名稱和要讀的變數名稱一樣。在這個例子兩者都叫 name。
我們再來看看類似的 class 在 C# 如何表現?
 
class Car{
  string name;
  public string GetName(){
    return name;
  }
  public void SetName(string newName){
    name = newName;
  }
} 
在 C# 中新增了 name 這個變數和 GetName 還有 SetName 這兩個 method。在這邊可以注意到 C# 的變數名稱是小寫開頭而 method 的名稱習慣用大寫字開頭。
三種語言都有其個自的習慣,雖然不按照這個習慣,大部分情況下程式也是可以執行,但是在 Objective-C 如果不照者命名的習慣會造成一些奇怪的問題產生。
就讓我們來進入這篇文章的主題,開啟一個 ARC enable 的 Project 再加入一個 none ARC 的 class 就用上述的 Car 再加上兩個 method,讓我們一步一步來。
首先開啟一個 Single View Application 的專案如下
記得要勾選 Automatic Reference Counting
新增一個 Class 名為 Car 繼承 NSObject 如下是 Car.h
 
@interface Car : NSObject
-(id) newName;
-(id) newname;
@end
Car.m 如下
 
@implementation Car
-(id) newName{
    return [NSString stringWithFormat:@"Honda"];
}

-(id) newname{
    return [NSString stringWithFormat:@"Honda"];
}
@end
新增兩個 method 這兩個 method 的名稱很像,一個叫 newName 一個叫 newname 差別在於一個 name 有大寫的 N 另一個沒有。換句話說,newName 是有依照習慣命名,newname 是沒有的。 接下我們要把這個 Car.m 設定為不使用 ARC。
如下設定。
再新增一個 Button 在 畫面上,然後 Button 按下去 action 如下
 
- (IBAction)testCar:(id)sender {
    Car * car = [Car new];
    NSLog(@"Car name %@", [car newName]);
}
我們先使用這個 newName method。一執行,發現馬上就 crash。 但是如果換成 newname 這個 method 就是正常執行。 為什麼呢? 因為對於 non-ARC 的 method,
ARC 會看其 method 的開頭是不是 alloc, new 或 copy (所謂開頭指的是符合 CamelCase 的第一個詞)
或是
第一個是 mutable 第二個是 copy ,也就是寫成 mutableCopy 。
此時 ARC 會自動認為這個 method 是會產生一個沒有加到 autorelease pool 的物件,然後就自行決定加上 release 在這個例子使用 newName 就 crash 是因為 newName 回傳的是
[NSString stringWithFormat:@"Honda"] 
這個有加到 autorelease pool 的物件。而 ARC 自動多 release 一次了。這個例子是要提示大家,在寫 method 的時候要注意 CamelCase 第一個字,這個就是 ARC 的習慣。

沒有留言:

張貼留言