2011年5月30日 星期一

NSMutableDictionary & NSKeyedArchiver

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"raw"];
    
    NSMutableDictionary *data = [[[NSMutableDictionary alloc]init]autorelease];
    [data setObject:[NSString stringWithFormat:@"aaa"] forKey:@"aaaKey"];
    [data setObject:[NSString stringWithFormat:@"bbb"] forKey:@"bbbKey"];
    [NSKeyedArchiver archiveRootObject:data toFile:path];
    
    NSMutableDictionary *data = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    NSLog(@"aaaKey : %@\n",[data objectForKey:@"aaaKey"]);
    NSLog(@"bbbKey : %@\n",[data objectForKey:@"bbbKey"]);

NSUserDefaults

[[NSUserDefaults standardUserDefaults] setObject:[NSString stringWithFormat:@"111"] forKey:@"myDefaultKey"];

pList 的存放路徑為
/Users/<User>/Library/Application Support/iPhone Simulator/<XCode Version>/Applications/<Application ID>/Library/Preferences/

2011年5月28日 星期六

NSKeyedArchiver sample

NSArray *myArray1 = ....;
NSArray *myArray2 = ....;
NSData *buffer = [NSKeyedArchiver archivedDataWithRootObject: myArray1];
myArray2 = [NSKeyedUnarchiver unarchiveObjectWithData: buffer];

2011年5月25日 星期三

Customize Application Name

1. Add a strings file in your project and the file's name is "InfoPlist.strings"
2. Add a property into info.plist, "Application has localized display name"
3. Edit the Info Page of "InfoPlist.strings" and add multi-language
4. Add string in different language file.
    ex: "English" file -> "CFBundleDisplayName"="MyApp";
    ex: "zh_TW" file -> "CFBundleDisplayName"="我的應用程式";
    ex: "zh_CN" file -> "CFBundleDisplayName"="...";
  ...

2011年5月23日 星期一

How to post message to Facebook from iPhone App

Step 1: Create a new application of facebook from here

Step 2: Download Facebook SDK
Step 3: Add Facebook SDK into your project
Step 4: Replace the Api Key Values in FBControl.m


static NSString* kApiKey = @"578b8cdff0fce3aad6b249162bca2e15";
static NSString* kApiSecret = @"09c0f53fa229b45318099aa728b496ef";

Step 5: Replace the definition of strings
#define POST_NAME               @"iOSApp test name message!"
#define POST_WEBSITE            @"http://www.erawppa.com/"
#define POST_CAPTION            @"iOSApp caption message!"
#define POST_PRODUCT_ICON       @"http://www.erawppa.com/notymoky/images/p5.jpg"
#define POST_PRODUCT_WEBSITE    @"http://www.erawppa.com/notymoky"
#define POST_MSGPROMPT_FMT      @"enter your message"

Step 6: Using Facebook SDK
    FBControl *fbControl_ = [[FBControl alloc]init];
    [fbControl_ postToFacebook:@"98765443"];

How to post message to Twitter from iPhone App

Step 1: Create a new application of Twitter from app registration and you will see the information as below.
It contains two key values we will need.

Consumer key

uc5Ag3Oog5Ot3lWtt4w91Q

Consumer secret

ZFcLlWfQEq5poFDpFuTRHAVdHWPVctQYu6wXM2qpTQ
Step 2: Download Twitter SDK
Step 3: Add Twitter SDK into your project.
Step 4: Add libxml2.dylib into Framework
Step 5: Add $(SDKROOT)/usr/include/libxml2 in "Header Search Paths"
Step 6: Replace key values with previous what we got in TwitterControl.m
#define kOAuthConsumerKey @"uc5Ag3Oog5Ot3lWtt4w91Q"
#define kOAuthConsumerSecret @"ZFcLlWfQEq5poFDpFuTRHAVdHWPVctQYu6wXM2qpTQ"

Step 7: Integrate Twitter SDK with your project
  • Add #import "TwitterControl.h" and TwitterControlProtocol in xxxx.h
  • Implement TwitterControlProtocol
-(void)didPostComplete:(TwitterControl *)control success:(BOOL)result
{
    [control autorelease];
}
  • Post message to twitter
[TwitterControl postMessage:self msg:@"your message"];


Reference from here

Memory Management - 1

  Objective-C 支援 Garbage Collection, 但 iPhone 並不支援 Garbage Collection, 所以 iPhone 開發者在對於沒有使用到的 Object 必須要自己釋放.

iPhone 使用到 Reference Counting 的機制來幫助開發者管理記憶體, 何謂 Reference Counting 呢 ? 簡單的說就是對於每個 Object 都有個 counter, 只要此 Object 被 alloc 內部的 counter 就會累計加 1, 而 Object 被 release 時, 內部 counter 就會減 1, 當 counter 被遞減成 0 時, 此 Object 就會被 OS 釋放.

iPhone 基本操作中會遞增 counter 的有 alloc, new, retain, 而會遞減 counter 則是 release.


NSObject *obj = [[NSObject alloc]init];
...
NSObject *obj = [NSObject new];
...
[obj retain];
...
[obj release];
...


一般來說, 當開發者配置 memory 之後, 就一定要釋放, 不然就會產生 memory leakage. 在 iPhone Programming 中, 針對 memory 的寫法, 大概有幾個簡單的原則, 只要遵循這些原則, 基本上就可以大幅降低 memory leakage 的問題.
1. 誰遞增 reference counter, 就由誰負責遞減 reference counter
    針對這點, 這邊舉了兩個例子來分析這彼此的差異
case 1:


Modude A:
- (NSString *)generateString
{
    myString = [[NSString alloc]initWithString:@"test"];
    return myString;
}

Module B:
-(void)function
{
    NSString *string = [ModuleA generateString];
    ...
    [string release];
}



case 2:


Modude A:
- (NSString *)generateString
{
    myString = [[NSString alloc]initWithString:@"test"];
    return myString;
}

- (void)releaseString:(NSString *)string
{
    [string release];
}

Module B:
-(void)function
{
    NSString *string = [ModuleA generateString];
    ...
    [ModuleA releaseString:string];
}


單看上述這兩個例子, 或許你可能會覺得兩個都沒錯, 記憶體的確也沒有遺漏, 實際上也是如此,兩個方式語法上都沒有錯誤, 但是一般不管是在寫遊戲還是應用程式, 期複雜度一定遠遠超過上面的例子, 當專案越大時, 在 case 1 的情況下, 要去檢查 [ModuleA generateString在使用上是否有遺漏 release 的情況, 這將會是一個麻煩的事情. 相反的, 若是使用 case 2 的情況下, 要做相同檢查的動作, 只要在 Module A 中做簡單的修改即可得知是否有少 call releaseString. 在 Module A 中簡單的增加一個 allocCounter 變數, 最終只要 allocCounter > 0, 就代表有 memory 的洩露.

Modude A:
- (NSString *)generateString
{
    myString = [[NSString alloc]initWithString:@"test"];
    allocCounter++;
    return myString;
}

- (void)releaseString:(NSString *)string
{
    [string release];
    allocCounter--;
}


2. 善用 autorelease
每個 iPhone 應用程式都會有一個基本的  Autorelease Pool, 也就是在  main.m 中所建立的

int main(int argc, char *argv[]) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
int retVal = UIApplicationMain(argc, argv, nil, @"XXXDelegate");
[pool release];
return retVal;
}


我們可以透過這個 Autorelease Pool 的協助, 來幫助我們更容易的管理記憶體, 使用方式很簡單, 只要在 object 之後呼叫 autorelease 即可.

NSString *myString = [[[NSString alloc]initWithString:@"test"]autorelease];


上述的 NSString 配置方式和先前的最大不同在於, myString 經過呼叫 autorelease 之後, 就不需要在自己呼叫 release 去做釋放的動作, 釋放 myString 會由 iOS 自動幫開發者執行. 那究竟 iOS 何時會去做這件事情呢 ? 簡單的說, 當 iOS 完成了一個 event loop 時, 就會去檢查 Autorelease Pool 是否有需要"自動遞減" counter 的物件, 然後會將 counter 變為  0 的物件釋放. 我們可以做一個簡單的測試, 在 iPhone 中開啓一新專案, 並透過 UIButton 的點擊去呼叫下面測試函式. 使用 UIButton 主要目的是為了讓兩次的呼叫是在不同的 event loop 中執行.

- (void)testAutorelease

{
    static NSObject *obj = nil;
    if (!obj)
    {
        obj = [[[NSObject alloc]init]autorelease];
        [obj retain];
    }
    NSLog(@"obj counter : %d\n",[obj retainCount]);
}



在連續兩次叫用 testAutorelease 時所出現的結果, 第一次顯示的 counter 值是 2, 而第二次顯示的 counter 是 1. 這結果很明顯的告知我們, 當 event loop 結束時, iOS 會針對 autorelease pool 裡的物件自動去做 counter 遞減動作.

由這結果可以得知, 若能善用 autorelease 則可以大幅降低維護記憶體的複雜度. 但是有一個需要額外注意的是, 因為 autorelease 的是放時間點是在 event loop 執行完成之後, 在這過程中是不會有釋放的動作, 所以若太大量的物件都交給 Autorelease Pool 管理, 很有可能會在執行其間遇到記憶體不足的問題, 所以要如何利用 autorelease 是每一個 iPhone 開發者都要思考的.


Retrieve Scores from Leaderboard

GKLeaderboard* leaderboard = nil;
if ([players count] > 0)
{
    leaderboard = [[[GKLeaderboard alloc] initWithPlayerIDs:players] autorelease];
}
else
{
    leaderboard = [[[GKLeaderboard alloc] init] autorelease];
    leaderboard.playerScope = playerScope;
}

if (leaderboard != nil)
{
    leaderboard.timeScope = timeScope;
    leaderboard.category = category;
    leaderboard.range = range;
    [leaderboard loadScoresWithCompletionHandler:^(NSArray* scores, NSError* error)
    {
        NSMutableArray *retrievePlayerIDs = [[[NSMutableArray alloc]init]autorelease];
    
        for (GKScore *s in scores)
        {
            [retrievePlayerIDs addObject:s.playerID];        
        }
    
        [GKPlayer loadPlayersForIdentifiers:retrievePlayerIDs withCompletionHandler:^(NSArray *playerArray, NSError *error)
         {
             NSMutableArray *retrievePlayerAlias = [[[NSMutableArray alloc]init]autorelease];
             for (GKPlayer* player in playerArray)
             {
                 NSLog(@"Player : %@",player.alias);

             }
         }]; 
    }];

}


2011年5月19日 星期四

cocos2d & box2d tutorial - 1

目前越來越多遊戲應用到基本的物理特性, 包含下墜, 撞擊等, 經典的例子就是目前最為人知道的 "Angry Bird", 這邊簡單介紹如何透過 cocos2d & box2d 來達成這些物理現象.

step 1:
Create "cocos2d Box2d Application" Project:

step 2:
add variable in HelloWorldScene.h
b2World* world_;

step 3:
add the code in -(id) init() function

CGSize winSize = [CCDirector sharedDirector].winSize;
        
//create a ccprite
CCSprite *ball = [CCSprite spriteWithFile:@"ball.png"];
ball.position = ccp(100,300);
ball.tag = 1;
[self addChild:ball];
        
//create world
b2Vec2 gravity = b2Vec2(0.0f, -10.0f);
bool doSleep = true;
world_ = new b2World(gravity,doSleep);
        
//create a rectangle of a world
b2Body *groundBody;
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0,0);
groundBody = world_->CreateBody(&groundBodyDef);
        
//set edges
b2PolygonShape groundBox;
b2FixtureDef boxShapeDef;
boxShapeDef.shape = &groundBox;
groundBox.SetAsEdge(b2Vec2(0,0),
                    b2Vec2(winSize.width/PTM_RATIO,0));
groundBody->CreateFixture(&groundBox,0);
groundBox.SetAsEdge(b2Vec2(0,0),
                    b2Vec2(0,winSize.height/PTM_RATIO));
groundBody->CreateFixture(&groundBox,0);
groundBox.SetAsEdge(b2Vec2(winSize.width/PTM_RATIO,0),                      
                    b2Vec2(winSize.width/PTM_RATIO,
                           winSize.height/PTM_RATIO));
groundBody->CreateFixture(&groundBox,0);        
groundBox.SetAsEdge(b2Vec2(0,winSize.height/PTM_RATIO),
                    b2Vec2(winSize.width/PTM_RATIO,
                           winSize.height/PTM_RATIO));
groundBody->CreateFixture(&groundBox,0);
        
b2BodyDef ballBodyDef;
ballBodyDef.type = b2_dynamicBody;
ballBodyDef.position.Set(100/PTM_RATIO, 300/PTM_RATIO);
ballBodyDef.userData = ball;
b2Body *body = world_->CreateBody(&ballBodyDef);
        
b2CircleShape circle;
circle.m_radius = 26.0/PTM_RATIO;
        
b2FixtureDef ballShapeDef;
ballShapeDef.shape = &circle;
ballShapeDef.density = 0.5;
ballShapeDef.friction = 0.2f;
ballShapeDef.restitution = 0.7;
body->CreateFixture(&ballShapeDef);
        
[self schedule: @selector(tick:)];

step 4:
add a tick function


-(void) tick: (ccTime) dt
{
    int32 velocityIterations = 8;
    int32 positionIterations = 1;

    world_->Step(dt, velocityIterations, positionIterations);

    for (b2Body* b = world_->GetBodyList(); b; b = b->GetNext())
    {
        if (b->GetUserData() != NULL
        {
            CCSprite *myActor = (CCSprite*)b->GetUserData();
            myActor.position = CGPointMake(b->GetPosition().x * PTM_RATIO
                                           b->GetPosition().y * PTM_RATIO);
            myActor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
        }
    }
}

上述程式部份完成, 我們再來一步一步看看究竟寫了些什麼?


b2World* world_;

b2Vec2 gravity = b2Vec2(0.0f, -10.0f);
bool doSleep = true;
world_ = new b2World(gravity,doSleep);


b2World class 是一個比較特殊的物件, 一個遊戲中只會有一個 b2World, 有寫過 Java JSR184 的人相信對於這類型的 class 就比較不陌生, b2World 很相似於 JSR184 裡的 World class. 此 class 控制了這整個遊戲世界裡的狀態, 包含畫面的更新, 遊戲物件的控制, 以及遊戲世界中的重力系統..等.
所以再開始利用 box2d 實作之前, 一定都要先 new b2World, 而 b2World 被創建的時候會需要兩個參數
  • const b2Vec2& gravity - 遊戲世界的重力向量
  • bool doSleep - 當物體靜止(sleep)時, 忽略此物體的計算
b2Body *groundBody;
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0,0);
groundBody = world_->CreateBody(&groundBodyDef);


設定遊戲世界的原點, 由於 cocos2d 和 box2d 的坐標系統不同, 所以我們必須要將兩者的坐標系統做一個 mapping, 設定坐標系統會需要兩個步驟, 第一個是如上面程式去設定原點, cocos2d 的原點在左下角, 所以便將 box2d 的原點設定在 (0,0) 的位置.




b2PolygonShape groundBox;
b2FixtureDef boxShapeDef;
boxShapeDef.shape = &groundBox;
//bottom
groundBox.SetAsEdge(b2Vec2(0,0),
                    b2Vec2(winSize.width/PTM_RATIO,0));
groundBody->CreateFixture(&groundBox,0);

//left
groundBox.SetAsEdge(b2Vec2(0,0),
                    b2Vec2(0,winSize.height/PTM_RATIO));
groundBody->CreateFixture(&groundBox,0);

//right
groundBox.SetAsEdge(b2Vec2(winSize.width/PTM_RATIO,0),                      
                    b2Vec2(winSize.width/PTM_RATIO,
                           winSize.height/PTM_RATIO));
groundBody->CreateFixture(&groundBox,0);        

//top
groundBox.SetAsEdge(b2Vec2(0,winSize.height/PTM_RATIO),
                    b2Vec2(winSize.width/PTM_RATIO,
                           winSize.height/PTM_RATIO));
groundBody->CreateFixture(&groundBox,0);


#define PTM_RATIO 32


Box2d 的坐標系統範圍為 0.1 ~ 10, 因此定義一個 PTM_RATION 為 32 來作為 480x320 的映射比例. 在上面這段 code 裡分別設定四個邊的位置, 設定完的坐標對應會如下圖所示



CCSprite *ball = [CCSprite spriteWithFile:@"ball.png"];
ball.position = ccp(100,300);
ball.tag = 1;
[self addChild:ball];

b2BodyDef ballBodyDef;
ballBodyDef.type = b2_dynamicBody;
ballBodyDef.position.Set(100/PTM_RATIO300/PTM_RATIO);
ballBodyDef.userData = ball;
b2Body *body = world_->CreateBody(&ballBodyDef);



利用 cocos2d 去產生一個 CCSprite, 透過 b2BodyDef 將此 CCSprite 和 b2World 做關聯
  • type : 設定此物體是可以自由移動的
  • position : 設定此物體在 b2World 裡的位置
  • userData : 將 CCSprite pointer 紀錄在 userData 中, 之後可以利用這 pointer 來對 CCSprite 做變化



b2CircleShape circle;
circle.m_radius = 26.0/PTM_RATIO;
        
b2FixtureDef ballShapeDef;
ballShapeDef.shape = &circle;
ballShapeDef.density = 0.5;
ballShapeDef.friction = 0.2f;
ballShapeDef.restitution = 0.7;
body->CreateFixture(&ballShapeDef);


因為此範例所載入的圖行為一圓球, 所以必須相對應的使用 b2CircleShape 來告知 b2World, 針對此 body 要怎樣去做計算, 並且指定此圓球的密度(density), 摩擦係數(friction),彈力係數(restitution)

[self schedule@selector(tick:)];



-(void) tick: (ccTime) dt
{
    int32 velocityIterations = 8;
    int32 positionIterations = 1;
    world_->Step(dt, velocityIterations, positionIterations);
    for (b2Body* b = world_->GetBodyList(); b; b = b->GetNext())
    {
        if (b->GetUserData() != NULL
        {
            CCSprite *myActor = (CCSprite*)b->GetUserData();
            myActor.position = CGPointMake(b->GetPosition().x * PTM_RATIO
                                           b->GetPosition().y * PTM_RATIO);
            myActor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
        }
    }
}


最後透過 cocos2d 的 schedule 來更新所有畫面的動作, (ccTime) dt 就是 cocos2d 的更新頻率, 預設的值會是 1/60 second, 當每次 tick 被調用的時候, 上述的 code 會透過 world_->GetBodyList() 去將裡面所有的 body 取出, 且當 b->GetUserData() 有值的時候(也就是剛剛設定的 CCSprite), 就根據經過 box2d 所計算出來的位置和角度分別設定 position & rotation

範例程式 : Tutorial - FallingBall