2012/04/14

Objective-C:雖然NSString是唯讀的,但內容還是可能改變

在Objective-C裡,很多類別常常有唯讀版與可變版,譬如NSString與NSMutableString、NSArray與NSMutableArray、NSDictionary與NSMutableDictionary,一般來說,唯讀版的物件建立之後就不能改變,可變版的物件建立後,可以隨心所欲地進行修改。

一旦建立出NSString物件,NSString類別並沒有提供任何方法讓我們修改其中的字串,但要小心,裡頭的字串還是有可能會改變的,以底下範例說明。

假設有個Person類別,有兩個屬性name(名字)與address(住址)。其中name屬性使用retain,address使用copy,也就是說,當你指定一個NSString物件給name時,它只會記住該物件指標並增加參考計數,而指定一個NSString物件給address時,它會傳送copy訊息給該物件,得到一份拷貝。

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

@interface Person : NSObject
{
    NSString *name;
    NSString *address;
}

@property (nonatomic, retain) NSString *name;
@property (nonatomic, copy) NSString *address;

@end


// Person.m
#import "Person.h"

@implementation Person
@synthesize name;
@synthesize address;

-(NSString *)description
{
    return [NSString stringWithFormat:@"name(%@), address(%@)", name, address];
}

@end


// main.m
#import <Foundation/Foundation.h>
#import "Person.h"

int main (int argc, const char * argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   
    NSMutableString *sname = [NSMutableString stringWithString:@"Amanda"];
    NSMutableString *aname = [NSMutableString stringWithString:@"Taiwan"];
   
    Person *person = [[Person alloc] init];

    person.name = sname;
    person.address = aname;
    NSLog(@"1: person=%@", person);
   
    [sname appendString:@"XXX"];
    [aname appendString:@"YYY"];
    NSLog(@"2: person=%@", person);
   
    [person release];
    [pool release];
    return 0;
}

首先建立兩個NSMutableString物件sname與aname,分別指定給person物件的name與address,然後印出person物件。接下來以NSMutableString介面修改sname與aname,然後再印出person物件,你會發現,person的name變了,address沒變。

程式輸出如下:

2012-04-14 23:01:47.603 TestNSString[24655:903] 1: person=name(Amanda), address(Taiwan)
2012-04-14 23:01:47.606 TestNSString[24655:903] 2: person=name(AmandaXXX), address(Taiwan)

結論,不要以為的物件的類別為NSString就不會改變,若要確保不變,屬性可使用copy宣告。

2012/04/09

[廣告] iOS SDK 開發範例大全(The iOS 5 Developer's Cookbook: Core Concepts and Essential Recipes for iOS Programmers, 3/e)

嗨,我翻譯了一本書,在這裡打打廣告。

書名:iOS SDK 開發範例大全
原書名:The iOS 5 Developer's Cookbook: Core Concepts and Essential Recipes for iOS Programmers, 3/E
作者:Erica Sadun
譯者:我
出版社:悅知
頁數:864



這本是iOS(iPhone、iPod touch、iPad)軟體開發的入門書,更新到iOS 5、Xcode 4.2、Objective-C 2.0與block與ARC(automatic reference counting),也有講解Interface Builder(已經整合進Xcode)的新玩意兒storyboard,範例很多,內容涵蓋UI介面設計、各種視覺元件、文字標籤、表格、畫面轉換的過場動畫、自製控制項、記憶體管理、資料儲存、App Store上架、多點觸控、手勢、臉部辨識、裝置規格設定、查詢網路連線狀態與非同步下載、等等等等,極為豐富。

我在翻譯時,還發生一件事情。我拿到原文書後,發現裡面有很多地方缺失漏掉了,但不是作者的錯,應該是出版社進行編輯排版時發生錯誤,這讓我很驚訝,原文書出版社Addison-Wesley,是我心中覺得不錯的出版社,居然也會搞這種烏龍。後來更正後,也把之前印錯的書通通回收,重新出版,嗯,處理的還不錯。

關於iOS的入門書有很多,原文書中另外一本有名的是"探索 iPhone 4 程式開發實戰"(Beginning iPhone 4 Development: Exploring the iOS SDK),已經有iOS 5的版本(Beginning iOS 5 Development: Exploring the iOS SDK),但中文翻譯還沒出

(2012.06.23更新:Beginning iOS 5 Development: Exploring the iOS SDK的繁體中文翻譯本已經出版了,書名是"探索 iOS 5 程式開發實戰")


相關連結:
1. 悅知網站關於此書的介紹
2. 天瓏書局
3. 博客來金石堂TAAZE讀冊生活
4. Amazon(原文書)。

關於其他學習Objective-C的iOS的資源,可以參考我寫的這篇

2012/04/05

Arduino練習:猜猜哪一個

有5個開關,一開始會隨機選出一個當做正確答案,然後讓使用者猜猜看按按看哪個才是對的,若按中正確的就點亮LED;另外有個開關是重置鍵。

接線:
從Arduino板的5V與GND接到麵包板。

Arduino腳位2、3、4、5、6、9,各接到開關的右上腳,
開關的右下腳接電阻後接地,
開關的左下腳接5V。

Arduino腳位8接電阻後接LED長腳,LED短腳接地。



程式碼如下(可到這裡下載):

/* inpin1到inpin5,讓使用者猜猜哪個才是正確的 */
int inpin1=2;
int inpin2=3;
int inpin3=4;
int inpin4=5;
int inpin5=6;

int led=8; /* LED的腳位 */
int inpinReset = 9; /* 重置 */

int randnum; /* 代表哪個才是正確答案 */

/* 重置,以亂數從新選出一個正確答案 */
/* 並熄滅LED */
void reset() {
        randnum = random(1, 6);
        digitalWrite(led, LOW);
        Serial.print("randnum=");
        Serial.println(randnum);
        delay(500);
}

void setup() {
    pinMode(inpin1, INPUT);
    pinMode(inpin2, INPUT);
    pinMode(inpin3, INPUT);
    pinMode(inpin4, INPUT);
    pinMode(inpin5, INPUT);


    pinMode(led, OUTPUT);
    pinMode(inpinReset, INPUT);
    Serial.begin(115200);
   
    reset();
}

void loop() {
    /* 檢查使用者有沒有按下正確的答案(開關) */
    switch (randnum) {
        case 1:
            if(digitalRead(inpin1) == HIGH){
                digitalWrite(led, HIGH);
                Serial.println("inpin1");
            }
            break;
        case 2:
            if(digitalRead(inpin2) == HIGH){
                digitalWrite(led, HIGH);
                Serial.println("inpin2");
            }
            break;
        case 3:
            if(digitalRead(inpin3) == HIGH){
                digitalWrite(led, HIGH);
                Serial.println("inpin3");
            }
            break;
        case 4:
            if(digitalRead(inpin4) == HIGH){
                digitalWrite(led, HIGH);
                Serial.println("inpin4");
            }
            break;
        case 5:
            if(digitalRead(inpin5) == HIGH){
                digitalWrite(led, HIGH);
                Serial.println("inpin5");
            }
            break;
    }

    /* 若按下inpinReset就重置 */
    if(digitalRead(inpinReset) == HIGH){
        Serial.println("inpinReset");
        reset();
    }
}

上面這份程式碼,還有很多可以改進的地方。

首先是亂數的部分,應該要先用randomSeed(analogRead(0)),才不會每次產生出來的亂數序列都是一樣的。

然後是switch的部份,可利用陣列儲存腳位,就不需要重複寫那麼多個case了。

另外,開關會有bounce問題,不過就這裡的情況來說,沒關係。

另外,可以增加「最多猜三次」這樣的行為,三次都猜錯後就失敗、重置。

可以增加揚聲器,發出猜對、猜錯的音效