你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 從NSArray看類簇

從NSArray看類簇

編輯:IOS開發基礎

Class Clusters

Class Clusters(類簇)是抽象工廠模式在iOS下的一種實現,眾多常用類,如NSString、NSArray、NSDictionary以及NSNumber都運作在這一模式下,它是接口簡單性和擴展性的權衡體現,在我們完全不知情的情況下,偷偷隱藏了很多具體的實現類,只暴露出簡單的接口。

NSArray的類簇

雖然官方文檔中拿NSNumber說事兒,但Foundation並沒有像圖中描述的那樣為每個number都弄一個子類,於是研究下NSArray類簇的實現方式。

__NSPlacehodlerArray

熟悉這個模式的同學很可能看過下面的測試代碼,將原有的alloc+init拆開寫:

id obj1 = [NSArray alloc]; // __NSPlacehodlerArray *
id obj2 = [NSMutableArray alloc];  // __NSPlacehodlerArray *
id obj3 = [obj1 init];  // __NSArrayI *
id obj4 = [obj2 init];  // __NSArrayM *

發現+ alloc後並非生成了我們期望的類實例,而是一個__NSPlacehodlerArray的中間對象,後面的- init或- initWithXXXXX消息都是發送給這個中間對象,再由它做工廠,生成真的對象。這裡的__NSArrayI和__NSArrayM分別對應Immutable和Mutable(後面的I和M的意思)

於是順著思路猜實現,__NSPlacehodlerArray必定用某種方式存儲了它是由誰alloc出來的這個信息,才能在init的時候知道要創建的是可變數組還是不可變數組

於是乎很開心的去看了下*obj1的內存布局:

03.jpg

下面是32位模擬器中的內存布局(64位太長不好看就臨時改32位了- -),第一個箭頭是*obj1,第二個是*obj2

04.jpg

我們知道,對象的前4字節(32位下)為isa指針,指向類對象地址,上圖所示的0x0051E768就是__NSPlacehodlerArray類對象地址,可以從lldb下po這個地址來驗證。04.jpg

那麼問題來了,這個中間對象並沒有儲存任何信息诶(除了isa外就都是0了),那它init的時候咋知道該創建什麼呢?

經過研究發現,Foundation用了一個很賤的比較靜態實例地址方式來實現,偽代碼如下:

static __NSPlacehodlerArray *GetPlaceholderForNSArray() {
    static __NSPlacehodlerArray *instanceForNSArray;
    if (!instanceForNSArray) {
        instanceForNSArray = [[__NSPlacehodlerArray alloc] init];
    }
    return instanceForNSArray;
}
static __NSPlacehodlerArray *GetPlaceholderForNSMutableArray() {
    static __NSPlacehodlerArray *instanceForNSMutableArray;
    if (!instanceForNSMutableArray) {
        instanceForNSMutableArray = [[__NSPlacehodlerArray alloc] init];
    }
    return instanceForNSMutableArray;
}
// NSArray實現
+ (id)alloc
{
    if (self == [NSArray class]) {
        return GetPlaceholderForNSArray()
    }
}
// NSMutableArray實現
+ (id)alloc
{
    if (self == [NSMutableArray class]) {
        return GetPlaceholderForNSMutableArray()
    }
}
// __NSPlacehodlerArray實現
- (id)init
{
    if (self == GetPlaceholderForNSArray()) {
        self = [[__NSArrayI alloc] init];
    }
    else if (self == GetPlaceholderForNSMutableArray()) {
        self = [[__NSArrayM alloc] init];
    }
    return self;
}

Foundation不是開源的,所以上面的代碼是猜測的,思路大概就是這樣,可以這樣驗證下:

id obj1 = [NSArray alloc]; 
id obj2 = [NSArray alloc];
id obj3 = [NSMutableArray alloc];
id obj4 = [NSMutableArray alloc];

// 1和2地址相同,3和4地址相同,無論多少次都相同,且地址相差16位

靜態不可變空對象

除此之外,Foundation對不可變版本的空數組也做了個小優化:

NSArray *arr1 = [[NSArray alloc] init];
NSArray *arr2 = [[NSArray alloc] init];
NSArray *arr3 = @[];
NSArray *arr4 = @[];
NSArray *arr5 = @[@1];

上邊1-4號都指向了同一個對象,而arr5指向了另一個對象。

若干個不可變的空數組間沒有任何特異性,返回一個靜態對象也理所應當。

不僅是NSArray,Foundation中如NSString, NSDictionary, NSSet等區分可變和不可變版本的類,空實例都是靜態對象(NSString的空實例對象是常量區的@"")

所以也給用這些方法來測試對象內存管理的同學提個醒,很容易意料之外的。
參考:

https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/ClassClusters/ClassClusters.html

http://iphonedevwiki.net/index.php/Foundation.framework/Inheritance_hierarchy

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved