高見龍

iOS app/Ruby/Rails Developer & Instructor, 喜愛非主流的新玩具 :)

retainCount of NSNumber

在Objective-C裡有一種記憶體管理機制叫做Reference Counting,是說當某個物件生成並初始化之後,它的retain count會設定成1。執行該物件的”retain”方法會讓該物件的retain count加1,而release方法會讓retain count減1。當該物件的retain count變成0的時候,這個物件自動會呼叫dealloc方法,然後把記憶體還回來,Objective-C的書上或是官方手冊裡差不多都是這樣教的。

所以,下面的程式碼,2個物件的retain count預期應該會是1:

1
2
3
4
5
NSNumber *n1 = [[NSNumber alloc] initWithInt: 10];
NSNumber *n2 = [[NSNumber alloc] initWithInt: 100];

NSLog(@"retain count of n1 is %i", [n1 retainCount]);
NSLog(@"retain count of n2 is %i", [n2 retainCount]);

輸出結果是:

retain count of n1 is 2
retain count of n2 is 1

n2的retain count是1沒問題,但n1的卻是2,這結果跟我想像的不太一樣,這令我這個初學者很困惑..我知道retainCount不應該被拿來做為流程裡的邏輯判斷的依據,不過我很好奇為什麼會有這樣的差異?

翻了一下網路上的討論,原來是說因為某些數字太常被用到,為了做一些最佳化,所以在系統裡直接就預先產生了一份,再試了一下以下的程式碼:

1
2
3
4
5
6
7
8
9
10
11
NSNumber *n1 = [[NSNumber alloc] initWithInt: 10];
NSLog(@"retain count of n1 is %i", [n1 retainCount]);

NSNumber *n2 = [[NSNumber alloc] initWithInt: 100];
NSLog(@"retain count of n2 is %i", [n2 retainCount]);

NSNumber *n3 = [[NSNumber alloc] initWithInt: 100];
NSLog(@"retain count of n3 is %i", [n3 retainCount]);

NSNumber *n4 = [[NSNumber alloc] initWithInt: 10];
NSLog(@"retain count of n4 is %i", [n4 retainCount]);

結果是:

retain count of n1 is 2
retain count of n2 is 1
retain count of n3 is 1
retain count of n4 is 3

又試著把他們的位址印出來:

1
2
3
4
5
// address of nums
NSLog(@"address of n1 is %p", n1);
NSLog(@"address of n2 is %p", n2);
NSLog(@"address of n3 is %p", n3);
NSLog(@"address of n4 is %p", n4);

結果是:

address of n1 is 0x100108e20
address of n2 is 0x10010cb30
address of n3 is 0x10010cb50
address of n4 is 0x100108e20

這樣似乎就能解釋retain count跟預期不太一樣的原因了,可以看到n1跟n4是指向同一塊記憶體位置,而n2跟n3是不同的位置,retain count都是1,表示n2跟n3在生成的時候是建立一個新的數字,而n1跟n4則是指向一個像是”預先產生而且共享”的數字,所以n1一開始的 retain count是2,n4因為指向跟n1同一個地方,所以retain count變3。而這個最佳化(預先產生)的範圍,似乎是從-1 ~ 12之間。

果然還有很多要學的 :)

新手上路,若有錯誤還請不吝指教!

Comments