1. 程式人生 > >小碼哥-(知其所以然二)從底層分析OC中ARC和非ARC下深複製和淺...

小碼哥-(知其所以然二)從底層分析OC中ARC和非ARC下深複製和淺...

今天,在坊間聽到有人在爭論OC中關於NSString的深淺複製,聽了下,感覺很有必要來一個分析總結,讓我們從底層去了解OC中深淺複製的運作機制.


所謂copy就是在原有物件的基礎上產生一個副本物件,遵循最關鍵的兩點原則:


改變原物件的屬性和行為不會對副本物件產生任何影響


改變副本物件的屬性和行為不會對原物件產生任何影響


在理解了這一層之後,我們一起來研究下deep copy 和 shallow copy,因為蘋果是一個非常注重效能的公司,所以拷貝在底層實現沒那麼簡單:


以NSString為案例,以下四種情況徹底說明了什麼是深複製和淺複製:


(一) 如果是不可變物件呼叫copy方法產出不可變副本,那麼不會產生新的物件, 因此這是shallow copy,不會產生新的物件,指標地址相等.


int main(int argc, const char * argv[])
{
    @autoreleasepool {
        NSString *srcStr = @"copy";
        NSString *copyStr = [srcStr copy];


        NSLog(@"%p %p", srcStr, copyStr);
    }
    return 0;
}
2014-05-25 22:26:13.805 03-複製[47093:303] 0x100001080 0x100001080
(二) 如果是不可變物件呼叫mutableCopy方法產出可變副本,那麼一定會產生新的物件, 因此這是deep copy,會產生新的物件,指標地址不相等.


int main(int argc, const char * argv[])
{
    @autoreleasepool {
        NSString *srcStr = @"copy";
        NSMutableString *copyStr =  [srcStr mutableCopy];
        NSLog(@"srcStr=%p, copyStr=%p", srcStr, copyStr);
    }
    return 0;
}
2014-05-25 22:32:22.598 03-複製[47519:303] srcStr=0x100001080, copyStr=0x100203f70
(三) 如果是可變物件呼叫copy方法產出不可變副本,那麼一定會產生新的物件, 因此這是deep copy,會產生新的物件,指標地址不相等.


int main(int argc, const char * argv[])
{
    @autoreleasepool {
        NSMutableString *srcStr = [NSMutableString stringWithFormat:@"copy"];
        NSString *copyStr = [srcStr copy];
        NSLog(@"srcStr=%p, copyStr=%p", srcStr, copyStr);
    }
    return 0;
}
2014-05-25 22:35:37.218 03-複製[47754:303] srcStr=0x10010afd0, copyStr=0x1001031e0
(四) 如果是可變物件呼叫copy方法產出可變副本,那麼一定會產生新的物件, 因此這是deep copy,會產生新的物件,指標地址不相等.


int main(int argc, const char * argv[])
{
    @autoreleasepool {


        NSMutableString *srcStr = [NSMutableString stringWithFormat:@"copy"];
        NSMutableString *copyStr = [srcStr mutableCopy];
        NSLog(@"srcStr=%p, copyStr=%p", srcStr, copyStr);
    }
    return 0;
}
2014-05-25 22:37:40.300 03-複製[47897:303] srcStr=0x100203f70, copyStr=0x100204080
只有在第一種情況中才是淺複製,可以理解下複製的本意:雖然兩物件內容相同,但絕對互不影響.為什麼第一種是淺複製呢?因為是一個不可變的拷貝生產另外一個不可變的,既然都不可變,那乾脆指向同一個記憶體空間.因為"蘋果"很注重效能.


由於現在都在ARC下開發,非ARC下開發很少,但是還是來分析下其運作流程,以便鞏固下知識:


兩種情況,分別來分析:


(一) 在進行淺複製時,雖然沒有產出新物件, 但是源物件和副本物件的計數器都會+1:


int main(int argc, const char * argv[])
{
    @autoreleasepool {
        NSString *str = [NSString stringWithFormat:@"Copy"];
        NSString *str2 = [str copy];
       // [str2 release];
        NSLog(@"%zd, %zd", [str retainCount], [str2 retainCount]);
    }
    return 0;
}
2014-05-25 22:48:28.151 04-複製和計數器[48639:303] 2, 2
為什麼呢?這裡牽扯到OC的記憶體管理原則了,凡事new,retain,copy..時,計數器都會+1,因為產生了一個新的物件,但是,如果是:


NSString *str = @"1"; // 一個常量 結果就是


2014-05-25 22:48:28.151 04-複製和計數器[48639:303] -1, -1
所有,在iOS中NSString是嚴格遵守NSCopying和NSMutableCopying的.


(二) 在進行深複製時,產出新物件, 但是源物件的計數器不變,副本物件的計數器+1:


int main(int argc, const char * argv[])
{
    @autoreleasepool {
        NSString *str = @"Copy";
        NSMutableString *str2 = [str mutableCopy];
        NSLog(@"str=%zd, str2=%zd", [str retainCount], [str2 retainCount]);
        // [str2 release];
    }
    return 0;
}
2014-05-25 22:59:08.688 04-複製[49397:303] str=-1, str2=1
運用copy的終極目的是: 當我賦值後,改變舊值不會影響新值. 如果要改變,就用retain..... 還有一點copy的底層實現,不就徹底弄清了:


- (void) setTitle : (NSString *)title{
     if (_title != title){
        [_title release];
        _title = [title copy];
    }
}
- (void)dealloc{
   self.title = nil;
   [super dealloc];
}