屬性關鍵字
- 深拷貝與淺拷貝
- 類型
- 各類對象深淺拷貝判斷
- 完全深拷貝的實現
- 屬性關鍵字
- @property、@synthesize和@dynamic
- 原子操作
- 讀寫權限
- 內存管理
- strong 🆚 copy
- 總結
深拷貝與淺拷貝
先前學習OC時已經對深淺拷貝進行了一次學習,這里進行一個復習總結和補充,具體參照博客深拷貝與淺拷貝。
類型
OC對象的拷貝形式分為深拷貝、淺拷貝,其中深拷貝又細分為單層深拷貝和完全深拷貝。
- 淺拷貝:指針拷貝,復制新指針與原指針存到同一塊內存區域。
- 深拷貝:內容拷貝,將數據拷貝到一塊新的內存區域,新指針指向新區域。
- 單層深拷貝:副本對象本身是深拷貝,其里面所有對象是淺拷貝。(例如容器類數組對象本身是深拷貝,數組元素是淺拷貝)
- 完全深拷貝:無論副本對象本身還是里面對象元素都是深拷貝。
各類對象深淺拷貝判斷
- 可變對象(非容器類、容器類)的copy和mutableCopy都為深拷貝。
- 不可變對象(非容器類、容器類)的copy為淺拷貝,mutableCopy為深拷貝。
- 自定義對象的copy和mutableCopy都為深拷貝。
無論容器類對象還是非容器類對象,都屬于系統類,xCode會自動幫我們實現好copyWithZone:
和 mutableCopyWithZone:
方法。然而自定義類的這兩個方法需要我們自己手動實現。
@implementation Person- (id)copyWithZone:(NSZone *)zone {Person *copy = [[[self class] allocWithZone:zone] init];copy.name = [self.name copy];copy.age = self.age;return copy;
}- (id)mutableCopyWithZone:(NSZone *)zone {Person *copy = [[[self class] allocWithZone:zone] init];copy.name = [self.name mutableCopy];copy.age = self.age;return copy;
}@end
從上述的方法實現中我們可以看出,無論是copy還是mutableCopy,這里都init出一個新的對象,開辟了一個新內存,與原對象不在一個內存地址,這就是深拷貝。
完全深拷貝的實現
在之前的博客中對完全深拷貝的實現沒有過多講解,這里具體總結一下從單層深拷貝轉換為為完全深拷貝的兩種方法。
- 歸檔和解檔
這種方法的重點在于實現encodeWithCoder:
和 initWithCoder:
方法。對于系統自帶類來說,自動實現了這兩個方法,可以直接歸檔和解檔。相反,自定義類的歸檔和解檔的關鍵就是遵守NSSecureCoding
協議實現以下方法:
#import "Person.h"@implementation Person//支持安全編碼
+(BOOL)supportsSecureCoding {return YES;
}//將對象轉化為二進制
- (void)encodeWithCoder:(nonnull NSCoder *)coder {[coder encodeObject:self.name forKey:@"name"];
}//二進制還原對象
- (instancetype)initWithCoder:(nonnull NSCoder *)coder {if (self = [super init]) {self.name = [coder decodeObjectOfClass:[NSString class] forKey:@"name"];}return self;
}@end
#import <Foundation/Foundation.h>
#import "Person.h"int main(int argc, const char * argv[]) {@autoreleasepool {Person *person = [[Person alloc] init];person.name = @"Alice";NSLog(@"%p---%@", person, person.name);//歸檔,將對象封裝進二進制容器NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:nil];//解檔Person *copyPerson = [NSKeyedUnarchiver unarchivedObjectOfClass:[Person class] fromData:data error:nil];NSLog(@"%p---%@", copyPerson, copyPerson.name);person.name = @"Bob";NSLog(@"%p---%@", person, person.name);NSLog(@"%p---%@", copyPerson, copyPerson.name);}return 0;
}
運行結果:
從運行結果可以看出,通過解檔歸檔,得到了一個新對象,新建了一塊內存地址,內容相同但地址不同,因此是深拷貝。并且,修改原始對象不改變解檔對象,二者彼此獨立,所以copyPerson依然是原來的值,這也說明實現了深拷貝。
- 使用
initWithXxx: copyItems:YES
方法
該方法只能實現一層深拷貝,因此只有當容器類對象中的對象是自定義類對象或者可變對象時,可以實現完全深拷貝。很好理解,因為自定義類對象和可變對象的拷貝都是深拷貝,因此使用該方法再實現一層深拷貝就實現了完全深拷貝。
#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {@autoreleasepool {NSMutableString *str1 = [NSMutableString stringWithString:@"Alice"];NSMutableString *str2 = [NSMutableString stringWithString:@"Bob"];NSArray *arr = @[str1, str2];NSArray *copyArr = [[NSArray alloc] initWithArray:arr copyItems:YES];NSLog(@"%@---%p", arr, arr);NSLog(@"%@---%p", copyArr, copyArr);NSLog(@"%@---%p", str1, str1);NSLog(@"%@---%p", copyArr[0], copyArr[0]);[str1 appendString:@"Tom"];NSLog(@"%@---%p", str1, str1);NSLog(@"%@---%p", copyArr[0], copyArr[0]);}return 0;
}
運行結果:
從運行結果可以看出,通過這種方法只復制了內容,但地址不同,并且對原對象的修改不會影響copyArr對象的內容。
值得注意的是:如果不是上述所說的容器類對象中的對象是自定義類對象或者可變對象時,使用該方法實現的是單層深拷貝。
屬性關鍵字
- 屬性的本質是 ivar(實例變量)+getter(getter方法)+setter(setter方法)。
- 屬性的修飾符應該按照順序排列:原子操作、讀寫權限、內存管理。
@property、@synthesize和@dynamic
ivar+getter+setter的組合方式使得代碼臃腫,因此使用@property、@synthesize和@dynamic來實現屬性自動合成存取方法。
- @property:用于封裝對象中的數據,自動生成屬性的setter與getter方法的聲明。
- @synthesize:自動生成setter和getter方法。
- @dynamic:告訴編譯器不自動進行@synthesize,而是程序員手動實現setter和getter方法。
注意??:
- 如果重寫setter和getter方法,編譯器就不會自動為@property生成@synthesize,需要手動添加。
- 如果在協議里使用@property聲明一個屬性,那么在某個類中遵循這個協議時,@synthesize需要手動添加同時生成實例變量和setter和getter方法。
#import <Foundation/Foundation.h>@protocol Demand <NSObject>-(void)testDemand;
@property(nonatomic, strong) NSString *name;@end@interface Person : NSObject<NSSecureCoding>@end#import "Person.h"@implementation Person@synthesize name = _name;-(void)testDemand {NSLog(@"屬性關鍵字");
}
原子操作
屬性是否具有原子性可以理解為線程是否安全。
同步鎖:在多線程環境中,當多個線程同時訪問或修改同一對象時,會導致程序崩潰等結果。同步鎖則是一種用于確保在給定時間內只有一個線程可以執行某特定代碼而保證線程安全的機制。
- atomic:原子性,加同步鎖,但也不一定保證線程安全,且會損耗性能。
- nonatomic:非原子性,不加同步鎖,使用這個可以提高訪問性能,代碼中常用nonatomic修飾屬性。
讀寫權限
- readwrite:默認,生成setter和getter方法。
- readonly:只生成getter方法。
內存管理
- assign:
- 既可以修飾基本數據類型(NSInteger、BOOL、int、float 等),也可以修飾對象類型。
ps:如果使用assign修飾OC對象,對象銷毀時可能會產生懸垂指針,從而出現程序崩潰,因此最好assign只用來修飾基本數據類型。
- 一般對于基本數據類型,setter方法的實現可以是直接賦值。
- 修飾對象類型時,不增加引用計數。
- 修飾delegate 在MRC使用assign.
- 會產生懸垂指針,assign修飾的對象在被釋放后,指針仍然指向原對象地址,也就是說如果assign修飾的對象被釋放后,仍通過該指針訪問原對象的話,程序可能崩潰。
懸垂指針:指針所指的內存已經被釋放(對象已被銷毀),但指針本身仍然存在,并繼續指向那塊現在已經無效的內存地址。
- 既可以修飾基本數據類型(NSInteger、BOOL、int、float 等),也可以修飾對象類型。
- weak:
- 只能修飾對象類型(只能修飾OC對象,如UIButton、UIView等)。
- ARC下才能使用。
ARC: 是一種編譯器特性,它*編譯時自動為你插入適當的內存管理代碼(
retain
,release
,autorelease
),而不是像傳統手動引用計數(MRC)那樣需要開發者自己寫這些代碼。 - 修飾弱引用,不增加引用計數,用于避免循環利用。
- weak 修飾的對象在被釋放之后,會自動將指針置為 nil,不會產生懸垂指針。
- 對于有必要進行 remove 的視圖可以使用 weak,remove 之后會自動置為 nil。
- 修飾delegate 在ARC使用weak。
- unsafe_unretained:
- 既可以修飾基本數據類型,也可以修飾對象類型。
- MRC 下經常使用,ARC 下基本不用。
- 修飾弱引用,同 weak,但性能更好,同時使用要求高,因為會產生懸垂指針。
- retain:
- MRC下使用。
- 修飾強引用,將指針原來指向的舊對象釋放掉,然后指向新對象,同時新對象的引用計數加1。
- setter 方法的實現是 release 舊值,retain 新值,用于OC對象類型。
strong 🆚 copy
- strong:
- ARC 下才能使用。
- 原理同 retain,但在修飾 block 時,strong 相當于 copy,而 retain 相當于 assign。
- 淺拷貝
- copy:
- setter 方法的實現是 release 舊值,copy 新值。一般用于 block、NSString、NSArray、NSDictionary 等類型。
- copy設置的屬性會復制傳入的對象,而不是僅僅保持對傳入對象的引用,用于確保屬性擁有自己的獨立副本,使得避免不經意的更改。
- 區別:
- 屬性聲明使用copy修飾,則合成方法使用類的copy方法,生成一個對象的不可變副本。
- strong修飾的的對象的賦值是多個指針指向同一個地址,而copy對象的賦值是每次在內存中開辟一個新內存地址,指針指向不同的地址。
@interface Person : NSObject<NSSecureCoding>@property(nonatomic, strong) NSString *strStrong;
@property(nonatomic, copy) NSString *strCopy;
@property(nonatomic, strong) NSMutableString *mutablestrStrong;
@property(nonatomic, copy) NSMutableString *mutablestrCopy;-(void)testDemo;@end-(void)testDemo {NSMutableString *str = [NSMutableString stringWithString:@"Hello"];NSLog(@"%@---%p", str, str);self.strStrong = str;NSLog(@"%@---%p", self.strStrong, self.strStrong);self.strCopy = str;NSLog(@"%@---%p", self.strCopy, self.strCopy);self.mutablestrStrong = str;NSLog(@"%@---%p", self.mutablestrStrong, self.mutablestrStrong);self.mutablestrCopy = str;NSLog(@"%@---%p", self.mutablestrCopy, self.mutablestrCopy);[str appendString:@" iOS"];NSLog(@"%@---%p", self.strStrong, self.strStrong);NSLog(@"%@---%p", self.strCopy, self.strCopy);NSLog(@"%@---%p", self.mutablestrStrong, self.mutablestrStrong);NSLog(@"%@---%p", self.mutablestrCopy, self.mutablestrCopy);
}
運行結果:
從運行結果看的出,正如我們的結論所說,strong修飾的賦值指向同一個地址(淺拷貝),copy修飾的賦值指向新的地址(深拷貝)。因此,當原值改變時,strong修飾的對象的指針還指向原內存,因此值隨之改變,而copy的不會改變。
因此,對于copy修飾的對象,為確保對象中的字符串值不會無意改動,應該在賦值前拷貝一份。
總結
屬性關鍵字的知識還有很多,后續學習后會完善補充。