2011年11月27日 星期日

關於 Objective-C memory

兩個問題:
  1. 一個app在mobile device上使用了多少記憶體可能導致crash?
  2. 我們要如何看到我們的app使用記憶體的狀況?
在回答這兩個問題之前,再特別強調一個觀念:
在Objective-C裡面指的memory leak,只是產生了一個程式再也無法用任何方式回收的記憶體空間,其實它沒有那麼恐怖!(指的是它並非是造成crash的主因)」
為何我特別標註第一句話,因為在通用語言的定義中,指的是「沒有任何程式碼來回收的記憶體空間,都叫做memory leak」,事實上也是如此,真正恐怖的不是Objective-C指的memory leak,而是你沒有好好控管你的程式,導致一堆記憶體空間雖然可以用程式回收,但你卻沒有在適當的時機回收,這種memory leak,是你用xcode(Objective-C IDE)的Leaks工具也抓不出來的!
更詳細的memory leak的例子,可以參考這個連結http://www.shopify.com/technology/4321572-most-memory-leaks-are-good
這又回到了上面第二個問題,我們即便知道了記憶體在mobile device上是很珍貴的,但我們如果不知道自己寫的app是如何使用記憶體的,那我們又怎麼規劃最適當的記憶體使用方式?(xcode 4.2預設已經是ARC(Automatic Reference Counting),理論上並不會再出現Objective-C指的memory leak,但仍會出現通用語言定義的memory leak)
先回答第二個問題,其實xcode(instruments)內建的allocations工具就可以觀察記憶體使用狀況了。
再回到第一個問題,「一個app在mobile device上使用了多少記憶體可能導致crash
記憶體使用本來就是作業系統動態管理的,沒有「明確的指標」是正常的,但一定有所謂的理論危險值,而就我查到的資料,雖然沒有人給100%的肯定答案,不過仍有一些人有推論值(這部份可以隨著我們觀察的經驗,越來越精準):
  • iPhone3G:14MB進入危險邊緣、20MB肯定crashl
  • iPhone3GS:iOS改為更動態的分配記憶體,ram也較大(256mb),較不容易發生,有人推論是iPhone3G的四倍左右,也就是56-80MB
  • ipad:ram規格同上
  • iPhone4:沒查到相關資料,但總之iphone4的ram是512mb,然後作業系統iOS4~5大概佔1xx,我猜超過200mb就進入危險邊緣。
  • ipad2:ram規格同上

參考資源








2011年11月7日 星期一

解決 iOS5 / xcode 4.2 導致第三方函式庫無法使用的問題

由於 iOS5 SDK 將 retain, release, autorelease, dealloc…等記憶體管理的指令都拿掉了,對後進開發者雖然是一大福音,但此舉卻也導致許多第三方的函式庫無法正常使用,最好的解決方案就是告訴 IDE 不要對這些第三方的函式庫進行 Automatic Reference Counting(ARC)。
target > build phases > compile sources,將有用到這個記憶體管理的檔案,全部加上
-fno-objc-arc

img

如此一來,便可正常引用第三方的函式庫了。

Note : 相反則是 -fobjc-arc

2011年11月5日 星期六

Objective-C Protocol 觀念

http://www.otierney.net/objective-c.html.zh-tw.big5 裡,其實就已經很清楚的提到 Objective-C 的 Protocol 與 Java 的 Interface 相同,底下以一個例子說明。
我們有個 Hero,職業是巫師,而這個職業有火球技能。火球技能的傷害算法是
火球等級 * 火球基礎傷害 * 職業加成倍率

那麼身為 Hero 的火球技能,必須知道 Hero 現在的職業加成倍率為何才對。如果 Hero 轉職了,職業加成倍率也就不同了。所以每當火球技能引發傷害時,都得動態問問看 Hero 現在的職業加成倍率。
在 Objective-C 裡面會這樣實作
Wizard.h
#import <Foundation/Foundation.h>
#import "FireBolt.h"
 
@interface Wizard : NSObject <MagicDamage> {
    FireBolt* fireBolt;
    int intelligence;
    int level;
}
 
-(void) castFireBolt:(id) target;
 
@end


Wizard.m
#import "Wizard.h"
 
@implementation Wizard
 
- (Wizard*) init
{
    self = [super init];
    
    if (self) {
        intelligence = 15;
        level = 5;
        
        FireBolt *tmpFireBolt = [[FireBolt alloc] init];
        tmpFireBolt.delegate = self;
        fireBolt = tmpFireBolt;
    }
    
    return self;
 
}
 
-(void) castFireBolt:(id) target
{
    [fireBolt cast:target];
}
 
-(double) getRate
{
    return intelligence * level;
}
@end


FireBolt.h
#import <Foundation/Foundation.h>
 
@protocol MagicDamage
 
-(double) getRate;
 
@end
 
@interface FireBolt : NSObject {
    double baseDamage;
    int level;
}
 
@property (nonatomic, strong) id <MagicDamage> delegate;
 
-(void) cast:(id) target;
 
@end
FireBolt.m
#import "FireBolt.h"
#import "Enemy.h"
 
@implementation FireBolt
 
@synthesize delegate;
 
- (FireBolt*) init
{
    self = [super init];
    
    if (self) {
        level = 1;
        baseDamage = 10;
    }
    
    return self;
    
}
 
-(void) cast:(Enemy*) target
{
    double rate = [self.delegate getRate];
    double damage = rate * baseDamage * level;
    NSLog(@"%@ 被攻擊了!受到 %f 的傷害", target.Name, damage);
    target.Life -= damage;
    NSLog(@"%@ 的生命剩下 %d", target.Name, target.Life);
}
 
@end


Enemy.h
#import <Foundation/Foundation.h>
 
@interface Enemy : NSObject
 
@property (nonatomic) int Life;
@property (nonatomic, retain) NSString* Name;
 
- (Enemy*) initWithName:(NSString*) name;
 
@end


Enemy.m
#import "Enemy.h"
 
@implementation Enemy
 
@synthesize Life, Name;
 
- (Enemy*) initWithName:(NSString*) name
{
    self = [super init];
    
    if (self) {
        self.Name = name;
        self.Life = 5000;
    }
    
    return self;
    
}
 
@end


main.m
#import "Wizard.h"
#import "Enemy.h"
 
int main( int argc, const char *argv[] ) {
    Enemy *dan = [[Enemy alloc] initWithName:@"Dan"]; 
    Wizard *zephyr = [[Wizard alloc] init];
    [zephyr castFireBolt:dan];
    [zephyr castFireBolt:dan];
    [zephyr castFireBolt:dan];
    [zephyr castFireBolt:dan];
    [zephyr castFireBolt:dan];
    
    return 0;
}


Output
2011-11-05 17:31:53.595 test[886:f803] Dan 被攻擊了!受到 750.000000 的傷害
2011-11-05 17:31:53.596 test[886:f803] Dan 的生命剩下 4250
2011-11-05 17:31:53.597 test[886:f803] Dan 被攻擊了!受到 750.000000 的傷害
2011-11-05 17:31:53.597 test[886:f803] Dan 的生命剩下 3500
2011-11-05 17:31:53.598 test[886:f803] Dan 被攻擊了!受到 750.000000 的傷害
2011-11-05 17:31:53.598 test[886:f803] Dan 的生命剩下 2750
2011-11-05 17:31:53.599 test[886:f803] Dan 被攻擊了!受到 750.000000 的傷害
2011-11-05 17:31:53.599 test[886:f803] Dan 的生命剩下 2000
2011-11-05 17:31:53.600 test[886:f803] Dan 被攻擊了!受到 750.000000 的傷害
2011-11-05 17:31:53.600 test[886:f803] Dan 的生命剩下 1250