2013年5月31日 星期五

Objective-C if, switch and conditional operator

程式中最常做的事情莫過於判斷流程了,什麼條件下做什麼事。
Objective-C中,有這三個Statement:

if 

if (true){
    do something;
}

當中假設有很多條件判斷時,使用 && (AND)    || (OR) 作複合判斷,如果判斷有優先順序,用小括弧刮起來。

if (temperature > 0 && temperature < 100)
    NSLog(@"目前狀態:液體")
if (temperature < 0 || temperature > 100)  
    NSLog(@"目前狀態不是液體")

條件如果不成立時,可以用else

if (條件判斷){
    真
}else{
    非
}

如果要做的事情只有一行,可以把大括號去掉,而 if 中還可以有 if

if (條件判斷一)
    statement
else
    if (條件判斷二)
        statement
    else
        statement

複雜一點,有可能變這樣


if (條件判斷一)
    statement1
else
    if (條件判斷二)
        statement2
    else
        if (條件判斷三)
            statement3
        else
            statement4


這樣一直縮排下去,可讀性越來越差,你可以改成這樣,效果一樣


if (條件判斷一)
    statement1
else if (條件判斷二)
    statement2
else if (條件判斷三)
    statement3
else
    statement4


switch

當情況變成上述那樣時,就可以考慮用switch。

switch ( value)
{
     case value1:
         statement_A
         .......
         break;

     case value2:
         statement_B
         .......
         break;

     case value3:
         statement_C
         .......
         break;

     case value4:
         statement_D
         .......
         break;

     default:
         statement_Z
         .......
         break;
}

要注意的是value值只能用可以轉成int的型別,如果你要判斷的值無法轉時,試試看Enum。

enum Month{JAN=1,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC};

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        
        enum Month thisMonth = OCT;
        
        switch (thisMonth) {
            case JAN:
                NSLog(@"一月");
                break;
            case FEB:
                NSLog(@"二月");
                break;
            case MAR:
                NSLog(@"三月");
                break;
            case APR:
                NSLog(@"四月");
                break;
            case MAY:
                NSLog(@"五月");
                break;
            case JUN:
                NSLog(@"六月");
                break;
            case JUL:
                NSLog(@"七月");
                break;
            case AUG:
                NSLog(@"八月");
                break;
            case SEP:
                NSLog(@"九月");
                break;
            case OCT:
                NSLog(@"十月");
                break;
            case NOV:
                NSLog(@"十一月");
                break;
            case DEC:
                NSLog(@"十二月");
                break;
            default:
                break;
        }
        
    }
    return 0;
}

Conditional operator

condition ? expression1 : expression2

三元運算子用的好可以讓程式更簡潔,上面的condition 為True時,執行expression1,為False,則執行expression2,看個例子。

NSLog(@"%@", boolVar ? @"True" : @"False" );

可以快速列印出booVar 的值。

Objective-C while Statement, do, break, continue

for (initial expression; loop condition; loop expression)
   program statement


initial expression;
while (loop condition)
{
   program statement
   loop expression;
}

for, while 相似,如果能用for寫出來,也可以用while改寫,依照不同的狀況選擇用哪一個,讓程式可讀性較佳。大原則是如果事前知道要做幾次,那用for,反之則使用while。

看個例子,先用while來寫

        int theSecretNumber = 77;
        int yourAnswer = 0;
        
        while (yourAnswer != theSecretNumber) {
            NSLog(@"請猜一個介於1~100的數字");
            scanf("%i",&yourAnswer);
        }
    
        NSLog(@"恭喜你猜到了");

改用for,你覺得那個可讀性較好呢??

        for (int theSecretNumber = 77,yourAnswer = 0; 
             theSecretNumber != yourAnswer; ) {
            NSLog(@"請猜一個介於1~100的數字");
            scanf("%i",&yourAnswer);
        }
        NSLog(@"恭喜你猜到了");

這個例子很像都差不多,Orz.....
就邏輯上來說,因為事先不知道到底猜幾次才會猜中,所以選擇while應該是比較適合的。

do statement
先做了再說,不管while裡頭的判斷如何,起碼做一次

do
    program statement
while ( expression);


看個範例:
int main(int argc, const char * argv[])
{
    @autoreleasepool {
        
        BOOL (^ask) (void)=  ^{
            BOOL answer;
            char c;
            NSLog(@"請問你願意被我揍嗎??");
            scanf("%c%*c",&c);
            if (c == 'y') {
                answer = YES;
            }else{
                answer = NO;
            }
            return answer;};
        
        
        int hitNumber = 0;
        
        do {
            hitNumber++;
            NSLog(@"揍了第%d拳",hitNumber);
        } while (ask()==YES);
        
        NSLog(@"Game over");
        
    }
    return 0;
}


這個例子中,在還沒有詢問他要不要被揍之前,就先打了,之後才根據他的回答做動作,這邊用了^{Block}實作詢問的行為,以後會再提怎麼用。


break,  continue

不管在for 或是while迴圈,都可以使用這兩個關鍵字去控制流程,break直接跳離開迴圈,continue則是以下的這次不執行,但繼續迴圈。
        for (int i = 1; i<=5; i++) {
            NSLog(@"第%d拳",i);
            if (i==3) {
                break;
            }
            NSLog(@"打中");
        }



        for (int i = 1; i<=5; i++) {
            NSLog(@"第%d拳",i);
            if (i==3) {
                continue;
            }
            NSLog(@"打中");
        }



這一篇筆記有點暴力了

Objective-C for Statement


for( initial expression ; loop condition ; loop expression ){
     statement 1;
     statement 2;
}

如果statement只有一句,大括號可以省略。

看個簡單的例子,從1加到100
int sum = 0;
for (int i = 1; i <= 100; i++) {
    sum = sum + i;
}

如果停止的判斷語句沒寫或沒寫好,就變成無窮迴圈 :)
for ( ; ; )
   NSLog(@"I'm infinitive loop!!");

for (int i = 1; i > 0 ; i++)
   NSLog(@"I'm infinitive loop!!");

在判斷時可以使用的 Operator:
==   等於
!=    不等於
<     小於
<=   小於等於
>     大於
>=   大於等於

需要注意的是 == 不要寫成了=

而有些寫法可以讓程式看起來更簡潔一些
sum += i;   (sum = sum + i;)
i++;           ( i = i +1; )
i--  ;           ( i = i - 1; )

賦值時:
i++     先賦值後加一
 ++i    先加一後賦值

        int x = 0;
        int y = 0;
        
        y = x++;
        
        NSLog(@"x is %d",x);
        NSLog(@"y is %d",y);

        int x = 0;
        int y = 0;
        
        y = ++x;
        
        NSLog(@"x is %d",x);
        NSLog(@"y is %d",y);



可以用 scanf這個函式去讀取使用者從鍵盤上的Keyin
        int guessNumber;
        NSLog(@"這期大樂透你選的數字是:");
        scanf("%i",&guessNumber);
        NSLog(@"您選的數字是:%i",guessNumber);

for裡頭的變數與更新運算可以不只一個
for (int i=1, j=10; i<j; i++,j--) 
    NSLog(@"i = %i, j = %i",i,j);



之後可能會常見到這種寫法,很像其他語言中的foreach,非常好用
NSArray *box = [[NSArray alloc]initWithObjects:@"第一個物件",
                                                   @"第二個物件",
                                                   @"第三個物件", nil];
    for (id i in box) 
        NSLog(@"%@",i);
    


最後提一下巢狀的for loop,簡單的九九乘法表

for (int i = 2; i <=9; i++) {
        for (int j = 2; j<=9; j++) {
            NSLog(@"%d * %d = %d",i,j,i*j);
        }
}


2013年5月30日 星期四

Objective-C 物件導向基礎

Objective-C 是架構於C語言之上的,導入了物件的觀念,Object-oriented Programming (OOP)有許多的特色,如繼承,封裝,多態等等,這不是三言兩語可以說完的,還是回歸正題,簡單地介紹在objective-c 中的類別與物件基本語法。

看個例子:

Wolf.h
#import <Foundation/Foundation.h>
@interface Wolf : NSObject
{
    NSString *_name;
    int _age;
}

-(void)SetName:(NSString *)name;
-(NSString *)name;
-(void)SetAge:(int)age;
-(int)age;
+(void)bray;

@end

在@interface與@end中間宣告這個類別有哪些{變數}與方法,而Wolf是這個類別的名稱,後面的冒號表示繼承於誰,NSObject是所有類別的最源頭,方法前面的符號 - 表示物件方法, + 表示類別方法。

類別命名規則,第一個字通常是大寫,常看到的NS表示NextStep,你也可以取自己的前綴字,如BH, WTF....。變數的命名開頭習慣小寫,不可以使用數字為開頭,名稱不能用保留關鍵字(int, float....),特殊符號(%,#..... 註一)與空格也是禁止的。另外在Objective-C 中,是區分大小寫的。

 - (void)SetName:(NSString *)name;
這是個物件方法,沒有回傳值,方法名稱是SetName:,如果有接受參數必須接著冒號,接受一個NSString參數。

為了維持物件導向的封裝性(Encapsulation),讓外部的類別不能直接存取與修改變數,所以我們不得不設計這麼多的getter and setter,這也是為什麼後來會有@property來減輕工作量的原因。

Wolf.m
#import "Wolf.h"

@implementation Wolf
-(void)SetName:(NSString *)name
{
    _name = name;
}
-(NSString *)name
{
    return _name;
}
-(void)SetAge:(int)age
{
    _age = age;
}
-(int)age
{
    return _age;
}
+(void)bray
{
    NSLog(@"Wu Wu.......!!!");
}

@end

在@implementation與@end中,把宣告的方法都實作出來,這裡可以看到為什麼很多人的習慣是把變數前面加下引號,因為如果不加下引號,會寫成 name = name,容易混淆,可讀性變差。
main.m
#import <Foundation/Foundation.h>
#import "Wolf.h"
int main(int argc, const char * argv[])
{
    @autoreleasepool {
        [Wolf bray];
        Wolf *w = [[Wolf alloc]init];
        [w SetName:@"BloodX"];
        [w SetAge:7];
        
        NSLog(@"%@ is a wolf, and he is %d years old.",[w name],[w age]);
    }
    return 0;
}

[Wolf bray],類別方法可以直接呼叫,而不需要實體化一個物件。
Wolf *w = [[Wolf alloc] init], 宣告且生成一個物件,alloc 配置一塊記憶體,init初始化。這些方法都是繼承至NSObject。

當然你也可以這樣寫,如果時間多的話。
Wolf *w;
w = [Wolf alloc];
w = [w init];

時間少的話,可以偷懶一下
Wolf *w = [Wolf new];

方法的呼叫是用中括號,你也可以一直刮下去,例如:[[[[Wolf allo ]init ]play ],但可讀性就變差了。



@property  and @synthesize

在Objective-C 2.0之後開始可以使用@property來幫忙我們少寫一點getter and setter,使用方式很簡單,在.h 檔中宣告,.m 合成。

Wolf.h
@interface Wolf : NSObject
@property NSString *name;

-(void)bray;

@end



Wolf.m
@implementation Wolf
@synthesize name;
-(void)bray
{
    NSLog(@"Wooo Wooo....");
}


這樣就幫你設定好並實作出兩個方法,
-(NSString *)name;
-(void)setName;

如果你不想讓別人知道實際修改的是什麼變數,可以改成這樣。

.h
@interface Wolf : NSObject
{
    NSString *name;
}
@property NSString *secret;

-(void)bray;

@end

.m
@implementation Wolf
@synthesize secret = name;
-(void)bray
{
    NSLog(@"Wooo Wooo....");
}

@end


. dot operator  
方便直接呼叫 getter and setter

Wolf *w = [Wolf new];        
w.name = @"Jon";               //  在等號左邊,呼叫setter  [w setName:(@"Jon")]
NSLog(@"%@",w.name);  //                           呼叫getter  [w name]

註一:在書中說 $ 字元不可以於命名中使用,不過實際在編譯時卻可以 :) 。

2013年5月29日 星期三

Class Extensions Example -- add private function

上一篇講到Categories的用法時,提到了它無法新增var,那如果我要新增var又該怎麼辦,這時候class extension就派上用場了,class extension 和category很像,但又有一些不同,class extension又稱為匿名的category。

相同點:都用來修改現有的類別
不同點:
  1. Category 只能新增或修改方法,無法新增變數
  2. Category 中所宣告的方法不用一定要全部實作,但是class extension宣告的方法都必須實現
  3. Category 方法的implement code必須放在原本類別的.m檔中
  4. Class extension 所宣告的var and function都是private

使用時機:引用Apple官方文件,“Class extensions are often used to extend the public interface with additional private methods or properties for use within the implementation of the class itself.”

以上篇的Dog為基底,我們來擴充一下,新增一個Class extension檔,輸入Extension name


這時候只會產生一個.h檔 Dog_Identification.h,因為要在原本的類別中實作


Dog_Identification.h
#import "Dog.h"

@interface Dog ()
{
    NSString *_secret;
}

-(NSString *)secret;
-(void)setSecret:(NSString*)s;

@end

括弧內沒有東西,所以這也是為什麼它稱為 匿名的Category的原因。


在原本的Dog.m檔中import此標頭檔,記得不是在Dog.h,因為如果你在Dog.h import Dog_Identificaton.h,又在Dog_Identification.h import Dog.h,這樣互相import會出錯的。


Dog.m
#import "Dog.h"
#import "Dog_Identification.h"

@implementation Dog

-(id)initWithName:(NSString *)name
{
    self = [super init];
    if (self) {
        _name = name;
        [self setSecret:@"XXX"];
    }
    return self;
}

-(void)play
{
    NSLog(@"%@ is playing.",_name);
}

-(NSString *)secret
{
    return _secret;
}
-(void)setSecret:(NSString*)s
{
    _secret = s;
}

-(NSString *)giveMeTheSecret
{
    return [self secret];
}

@end

實作完Class extension functon,另外在Dog.h宣告一個 giveMeTheSecret方法,去調用 -(NSString *)secret 這個private function,因為外部的類別是無法使用private function的。 main.m
#import <Foundation/Foundation.h>
#import "Dog.h"
#import "Dog+Bark.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        
        Dog *d = [[Dog alloc]initWithName:@"Tom"];
        [d play];
        [d barkWaWa];
        [d barkMeMe];
        NSLog(@"The secret is %@.",[d giveMeTheSecret]);
    }
    return 0;
}


BTW,很多人在使用Class Extension時,是不會去產生一個.h檔的,而是直接在原本類別的.m檔中去做宣告,所以我們這個範例可以改為如下,把Dog_Identification.h刪掉

Dog.m

#import "Dog.h"

@interface Dog ()
{
    NSString *_secret;
}

-(NSString *)secret;
-(void)setSecret:(NSString*)s;

@end


@implementation Dog

-(id)initWithName:(NSString *)name
{
    self = [super init];
    if (self) {
        _name = name;
        [self setSecret:@"XXX"];
    }
    return self;
}

-(void)play
{
    NSLog(@"%@ is playing.",_name);
}

-(NSString *)secret
{
    return _secret;
}
-(void)setSecret:(NSString*)s
{
    _secret = s;
}

-(NSString *)giveMeTheSecret
{
    return [self secret];
}

@end




Categories -- 修改現有的類別

使用時機:

想為已經存在的類別新增或修改方法(不建議修改原本的方法,因為繼承他的子類別也會受影響),你或許會問,那為什麼不在原本的.h .m檔中新增就好,幹嘛這麼麻煩使用categories,因為你有可能拿不到原本source code或是沒有權限修改(例如 NSString),而且如果大家都在同一份中作修改,那會改的亂七八糟的,這時候依功能把它獨立出來,個人寫個人的,就方便許多。


Dog.h
#import <Foundation/Foundation.h>

@interface Dog : NSObject
{
    NSString *_name;
}
-(id)initWithName:(NSString *)name;
-(void)play;

@end

Dog.m
#import "Dog.h"

@implementation Dog

-(id)initWithName:(NSString *)name
{
    self = [super init];
    if (self) {
        _name = name;
    }
    return self;
}

-(void)play
{
    NSLog(@"%@ is playing.",_name);
}

@end

這隻狗只會玩,幫他添加點聲音,新增Category檔案
選擇Category on “Dog”
Dog+Bark.h
#import "Dog.h"

@interface Dog (Bark)

-(void)barkWaWa;
-(void)barkMeMe;

@end

Dog+Bark.m
#import "Dog+Bark.h"

@implementation Dog (Bark)

-(void)barkWaWa
{
    NSLog(@"%@ WaWa !!",_name);
}
-(void)barkMeMe
{
    NSLog(@"%@ MeMe !!",_name);
}


@end

從檔名可以看出來Category是附加在原本的類別上的,我們把關於叫聲的方法都放在Bark這個Category,要注意的是 Dog (Bark)的寫法,是用小刮號表示,另外在Category無法新增var,只能增加方法(可以宣告不一定每個都實作出來),如果要add instance variable,請用extensions。

main.m
#import <Foundation/Foundation.h>
#import "Dog.h"
#import "Dog+Bark.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        
        Dog *d = [[Dog alloc]initWithName:@"Tom"];
        [d play];
        [d barkWaWa];
        [d barkMeMe];
    }
    return 0;
}

現在它除了會玩之外還會叫了,特別要注意的是,所有繼承於Dog類別的子類別也都學會叫囉。


Informal Protocols

category 有時也稱作 informal protocol,常常我們在 root class 時,會一併宣告 category,此時並不實作出這些方法(category.m 沒有 implement),而是等到子類別時,才選擇要實現哪些方法,在子類別的 .h中重新宣告,.m 中實作。那可能有人會問,這麼麻煩幹麼,都要在子類別重新宣告了,category 還有用的必要嗎,其實這時候 category 有點像是把這些方法先文件化、模組化,統一放在一個根類別加以說明,而不要四散在各處,這讓以後的人比較容易了解此類別的用途。

Private Method

在Objective-C裡頭,方法的修飾不像 instance有@public, @private, @protected,宣告時放在.h 的是public, 放在 .m的是private,That's all,那這個跟Category有什麼關係,原來是放在.m的寫法需要用到,category的名稱是空白的,匿名Category,其實這就是 Class Extension

@interface Dog()

 //private method
-(void) privateMethod;

@end

2013年5月28日 星期二

Hello, Objective-C


怎麼下載與安裝Xcode已經不是問題,開啓一個新的專案,選擇Command Line Tool作為Hello world 的範本。

Product Name, Organization Name, Company identifier 隨便取,Xcode會結合company identifier and product name 成為識別這個專案的bundle identifier,記得勾選 Use Automatic Reference Counting,幫我們自動管理記憶體。 


Project裡面有個main.m檔,其中有個main function,這是專案在執行時的入口,通常在開發app時不太會動到這個檔案。已經幫你寫好Hello, World了,按下左上角的Run,在下方的訊息窗口,即跑出Hello, World!


// 表示注解,在它右邊的文字,編譯器會忽略,如果有多行注解可以用
/*
我是注解第一行
我是注解第二行
......
......
*/

註解要寫得清楚,最好是能即時就寫下來,不要想說以後會補,因為通常就是不會補了。

#import表示載入這個檔案,好讓你可以使用定義在其中的一些成員或函式,使用<>刮起來的是系統標頭檔,如果是自己寫的,用雙引號" "刮起來。而Fundation.h這個標頭檔包含了一堆標頭檔,你可以反白按下右鍵點選jump to definition,就可以看到他的內容。


而在@autoreleasepool {  } 括弧中的變數與物件,會自動釋放記憶體,不用自己去操作。
NSLog是一個函式,可以在寫程式的過程中,印出一些資訊,讓你可以追蹤與除錯,而它接受一個NSString物件,在Objective-C中,以@""表示。

return 0 表示程式結束正常,任何非零表示有問題發生。


讓我們來修改一下,看看有什麼變化。
int main(int argc, const char * argv[])
{

    @autoreleasepool {
        
        NSLog(@"第一行\n");
        NSLog(@"第二行\n");
        NSLog(@"\"結束\"");
        
    }
    return 0;
}



\n是換行字元,如果要在輸出中表示雙引號,再多加個反斜線。

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        
        int sum;
        
        sum = 7 + 11;
        NSLog(@"7 + 11 = %i ",sum);
        
    }
    return 0;
}


%i     表示一個整數,常用的還有
%@   objective-C 物件
%f     浮點數
..........
..........

更多地表示符號參考

Objective-C 學習筆記


我想很多人會學習Objective-C都是因為要開發手機應用程式,當然我也不例外,上圖引用Tiobe網站目前各程式語言熱門的程度,可以清楚地看出2009年以後,學習Objective-C的人大爆發,今年是2013年,不知道這股浪潮可以持續到什麼時候,不過未來自有未來的煩惱,現在想太多也沒有用,只希望此刻可以快樂地學習。以下的文章是我閱讀 Stephen G. Kochan   Programming in Objective-C 2.0 的讀書筆記,藉著自己不佳的英文程度去理解文章,如果有誤,非常感謝您的指正。

入門
           for 迴圈

框架(Framework)


2013年5月1日 星期三

objective-c function 根據參數類型,有不同的行為

在objective-c 函式中,如果傳入的是簡單型別或是常數,函式會自動複製一份傳入的參數,所以之後如果有更改原先的變數,物件內的成員不會受影響;反之如果傳入的不是簡單型別或是常數,函式只會將物件的成員指向同一個位置,如果有更改變數,物件內的成員也跟著變動,這樣解釋有點模糊,看個例子比較清楚。
//XYPoint為自訂的一個類別
@interface XYPoint : NSObject
{
    int x;
    int y;
}
@property int x, y;

-(void)setX:(int) xVal andY:(int) yVal;

@end

@implementation XYPoint

@synthesize x,y;

-(void)setX:(int)xVal andY:(int)yVal
{
    x = xVal;
    y = yVal;
}

@end


//主要的測試類別,成員包含自定類別XYPoint, NSMutableString, int
@interface Foo : NSObject
{
    XYPoint *o;
    NSMutableString *name;
    int X;
}
-(XYPoint *)o;
-(void)setO:(XYPoint *)pt;
-(NSMutableString *)name;
-(void)setName:(NSMutableString *)n;
-(int)X;
-(void)setX:(int)value;
@end

@implementation Foo

-(XYPoint *)o
{
    return o;
}
-(void)setO:(XYPoint *)pt
{
    o = pt;
}

-(NSMutableString *)name
{
    return name;
}
-(void)setName:(NSMutableString *)n
{
    name = n;
}
-(int)X
{
    return X;
}
-(void)setX:(int)value
{
    X = value;
}
@end


int main(int argc, const char * argv[])
{

    @autoreleasepool {
        
        //先準備等一會要傳入Foo的變數
        XYPoint *myPoint = [[XYPoint alloc]init];
        NSMutableString *myName = [[NSMutableString alloc]initWithString:@"Jon"];
        [myPoint setX:100 andY:200];
        int myValue = 10;
        
        //建立一個Foo物件,將成員設定為上述的變數
        Foo *f = [[Foo alloc]init];
        f.o = myPoint;
        [f setName:myName];
        [f setX:myValue];

        NSLog(@"Before change myPoint, f.o.x : %d f.o.y : %d",f.o.x,f.o.y);
        NSLog(@"Before change myName, f.name : %@",f.name);
        NSLog(@"Before change myValue, f.X : %d",f.X);
        
        //更改這些變數,看看是否為影響f內的成員
        [myPoint setX:50 andY:50];
        [myName setString:@"Tom"];
        myValue = 5;

        NSLog(@"After change myPoint, f.o.x : %d f.o.y : %d",f.o.x,f.o.y);
        NSLog(@"After change myName, f.name : %@",f.name);
        NSLog(@"After change myValue, f.X : %d",f.X);
    }
    return 0;
}


結果如下:
Before change myPoint, f.o.x : 100 f.o.y : 200
Before change myName, f.name : Jon
Before change myValue, f.X : 10
After change myPoint, f.o.x : 50 f.o.y : 50
After change myName, f.name : Tom
After change myValue, f.X : 10