iOS 基礎02--單例、屬性修飾符、深淺拷貝
iOS 基礎02—單例、屬性修飾符、深淺拷貝
單例
講單例就必須得先講講單例這種設計模式的作用和最可能出現的應用場景,以便更好地理解這種設計模式:
比如在整個程序當中,我們經常會用到使用者資訊,這就要求我們能夠把使用者資訊存在一個統一的物件當中,以便於對資訊進行操作。
有對情況下,某個類也可能只能允許只有一個例項。比如音訊播放器。
這樣,我們就大概瞭解單例的了,它就是整個程序只存在一個例項物件的類。所以它的生命週期也就是程序的生命週期。只要app不被幹掉,例項物件就不會被釋放。
建立單例是最常考的面試題
- GCD 建立方法:
GCD once 內部是會加鎖,但是比普通的互斥鎖 效率高 100多倍,所以蘋果推薦使用once
#import "ShareManager.h" static ShareManager * _shareInstance; @implementation ShareManager + (instancetype)shareinstance { static dispatch_once_t oneToken; dispatch_once(&oneToken, ^{ _shareInstance = [[self alloc] init]; }); return _shareInstance; } + (instancetype)allocWithZone:(struct _NSZone *)zone{ static dispatch_once_t oneToken; dispatch_once(&oneToken, ^{ _shareInstance = [super allocWithZone:zone]; }); return _shareInstance; } -(id)copyWithZone:(NSZone *)zone{ return _shareInstance; } -(id)mutableCopyWithZone:(NSZone *)zone{ return _shareInstance; } @end
- 互斥鎖@synchronized方法:
static ShareManager * _shareInstance; @implementation ShareManager + (instancetype)shareinstance { @synchronized (self) { if (_shareInstance == nil) { _shareInstance = [[self alloc] init]; } } return _shareInstance; } + (instancetype)allocWithZone:(struct _NSZone *)zone{ @synchronized (self) { if (_shareInstance == nil) { _shareInstance = [super allocWithZone:zone]; } } return _shareInstance; } -(id)copyWithZone:(NSZone *)zone{ return _shareInstance; } -(id)mutableCopyWithZone:(NSZone *)zone{ return _shareInstance; } @end
這裡要著重說明一下:如果沒有實現allocWithZone:(struct _NSZone *)zone這個類方法,那麼呼叫[[ alloc]init]創建出來的物件就不是shareinstance的例項了,這樣一個專案就有可能出現多個例項;如果不寫copy或multablecopy,則單例就無法使用[ copy]或[ multablecopy]這兩個函數了。
單例的刪除:
static TestSingle * _instance;
static dispatch_once_t oneToken; //注意這個令牌要作為全域性變數來處理,在刪除操作的時候將其置為0。
@implementation TestSingle
+ (instancetype)shareinstance {
dispatch_once(&oneToken, ^{
_instance = [TestSingle new];
});
return _instance;
}
+ (void)deleteSingle {
oneToken = 0; //在刪除操作的時候將其置為0。
_instance = nil;
}
- (void)dealloc
{
printf("TestSingle has been destroyed");
}
弱單例,其生命不是存在於整個專案的生命週期,而是在所有引用它的類都釋放後,會隨著釋放的單例:
@implementation TestWeakSingle
+ (instancetype)shareInstance {
static __weak TestWeakSingle * _instance;
TestWeakSingle * strongInstance = _instance;
@synchronized(self){
strongInstance = [TestWeakSingle new];
_instance = strongInstance;
}
return strongInstance;
}
- (void)dealloc
{
printf("TestSingle has been destroyed");
}
@property:
@property 就相當於幫我們建立了getter和setter方法,這樣我們就不需要自己另外寫程式碼。
@property = ivar(例項變數)+ getter + setter;
- atomic&nonatomic:
atomic:原子性——要麼完整被執行,要麼不執行,這樣在有無數個執行緒同時訪問它的時候,它會保證有且只有一個執行緒在訪問它,而且一定會返回一個完整的值(其實就是不會返回垃圾地址),然後才會允許其他執行緒對它進行訪問。但是其實它並不能保證執行緒安全,比如有多個執行緒在排隊對A進行getter或setter方法,這個沒有問題,但是如果此時還有一個執行緒對A進行了release操作還是會crash的。概括來說atomic只保證了getter和setter的完整性,也就是說,這個屬性的讀\寫是執行緒安全的,但是release並不受getter和setter的限制,這就意味著它不不是執行緒安全的。
nonatomic:也就是非原子性的,它雖然無法保證getter和setter的完整性,但是它保證了無論對屬性進行什麼操作都是可以進行的,雖然結果無法預料。 接著用程式碼來實現一下:
@interface ViewController ()
@property (atomic,copy)NSString * str;
@property (nonatomic,copy)NSString * str2;
@end
@implementation ViewController
//OC中定義屬性,通常會宣告一個_成員變數(這個功能是Xcode的功能),如果你同時重寫了屬性的getter&setter方法,_成員變數就不會自動生成。
@synthesize str = _str;
@synthesize str2 = _str2; //如果要同時實現getter和setter方法,必須實現@synthesize
-(void)setStr:(NSString *)str{
@synchronized (self) {
_str = str;
}
}
-(NSString *)str{
NSString * tempStr = nil;
@synchronized (self) {
tempStr = _str;
}
return tempStr;
}
-(void)setStr2:(NSString *)str2{
_str2 = str2;
}
- (NSString *)str2{
return _str2;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.str = @"123";
NSLog(@"%@",self.str);
self.str2 = @"456";
NSLog(@"%@",self.str2);
}
如上程式碼,atomic的屬性,如果我們自己重寫getter和setter方法是需要加互斥鎖的,而加鎖對於iOS來說是很消耗效能的,所以我們在不考慮多執行緒的大多數情況下都是使用nonatomic的,它能夠提高更高的效能和效率。(這裡稍微說一下,atomic屬性的內部就有一把鎖:自旋鎖。自旋鎖和互斥鎖的共同點就是都能保證同一時間點就只有一條執行緒訪問,不同點就是互斥鎖鎖上時,在外等待的執行緒會進入睡眠狀態,當鎖開啟時,休眠的執行緒又會被重新喚醒。而自旋鎖鎖上時,就緒執行緒就會使用死迴圈的方式一直等待鎖定的程式碼執行完畢,自旋鎖更適合執行非常短的程式碼,但是無論什麼鎖,都是以CPU效能作為代價的)。然後atomic內部實現只有在setter方法中才會加鎖,在getter方法中不會加鎖,也就是說寫操作的時候可以多執行緒,讀操作如果多執行緒就會產生髒資料。
assign & weak & unsafe_unretained & strong & copy
- assign:對屬性進行簡單的賦值操作,不影響新值的引用計數,也不改變舊值的引用計數。主要應用於NSInteger這些標量型別,此外,它同樣可以作用於物件型別,如NSString。但是作用於物件型別時,如果物件被釋放了,指標仍會指向之前被銷燬的記憶體,這時訪問該屬性就會產生野指標並crash。
@property (nonatomic, assign) NSString * nick;
FCFPerson * p = [[FCFPerson alloc]init];
NSMutableString * s = [[NSMutableString alloc] initWithString:@"fcf"]; //這裡使用NSMutableString而不使用NSString,是因為NSString會快取字串,當執行s=nil時,實際上還是沒有被銷燬。。
p.nick = s;
NSLog(@"nick:%p",p.nick);
NSLog(@"s:%p",s);
s = nil;
NSLog(@"nick:%p",p.nick); //這裡crash
NSLog(@"%@",p.nick);
- weak:同樣對屬性進行簡單的賦值操作,不影響新值的引用計數,也不改變舊值的引用計數。但是它只能用於修飾物件型別。其次,當物件被釋放了,指標會自動賦值為nil,這樣就可以避免野指標問題了。
- unsafe_unretained:這個基本就和assign相同,不影響新值的引用計數,也不改變舊值的引用計數,而且物件被釋放的時候,也會產生野指標。它與assign唯一的區別在於它和weak一樣只能修飾物件。
- strong:對屬性所賦的值持有強引用,會先增加新值的引用計數,然後再釋放物件減少舊值的引用計數。但是,它只能修飾物件型別
- copy:它也只能修飾物件型別。它會在記憶體裡拷貝一份物件,兩個指標指向不同的記憶體地址。一般來修飾有對應可變型別子類的物件。比如NSString(子類NSMutableString),如果使用strong修飾NSString,那麼修改了所賦物件的值,str也會隨之改變:
@interface FCFPerson : NSObject
@property (nonatomic, strong) NSString * phone;
@end
FCFPerson * p = [[FCFPerson alloc]init];
NSMutableString * s = [[NSMutableString alloc] initWithString:@"123"];
p.phone = s;
NSLog(@"phone:%p, phone:%@",p.phone,p.phone);
NSLog(@"s:%p, s:%@",s,s);
[s appendString:@"456"];
NSLog(@"phone:%p, phone:%@",p.phone,p.phone);
NSLog(@"s:%p, s:%@",s,s);
//結果:
phone:0x17426c280, phone:123
s:0x17426c280, s:123
phone:0x17426c280, phone:123456
s:0x17426c280, s:123456
其次,當使用copy為關鍵字之後,呼叫setter方法後,拷貝的物件就變成不可變的了,如果用copy去修飾NSMutableArray、NSMutableString、NSMutableDictionary,那麼當它呼叫addObject類似可變物件的方法時就會奔潰。
property的深入理解
使用命令:
clang -rewrite-objc main.m
將專案中main.m轉成main.cpp檔案
//main.m
@interface Person : NSObject
@property (nonatomic, copy) NSString * fname;
@property (nonatomic, assign) NSUInteger fage;
@property (nonatomic, strong) NSString * fphone;
@property (nonatomic, unsafe_unretained) NSString * fcompany;
@end
@implementation Person
@synthesize fname = _fname;
@synthesize fage = _fage;
@synthesize fphone = _fphone;
@synthesize fcompany = _fcompany;
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
Person * p = [[Person alloc]init];
p.fname = @"fcf";
p.fage = 18;
p.fphone = @"123456";
p.fcompany = @"google";
}
}
轉成的.cpp的部分程式碼
#ifndef _REWRITER_typedef_Person
#define _REWRITER_typedef_Person
typedef struct objc_object Person; //objc_object重新命名
typedef struct {} _objc_exc_Person;
#endif
下面是每個屬性的偏移量,應該是相對於Person結構體指標的偏移量,這樣就可以更快定位變數的位置
extern "C" unsigned long OBJC_IVAR_$_Person$_fname;
extern "C" unsigned long OBJC_IVAR_$_Person$_fage;
extern "C" unsigned long OBJC_IVAR_$_Person$_fphone;
extern "C" unsigned long OBJC_IVAR_$_Person$_fcompany;
//這個就是Person結構體,。
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS; //這是一個結構體指標變數,它指向類物件,用於獲取Person類的方法列表、屬性列表等資訊
NSString *_fname;
NSUInteger _fage;
NSString *_fphone;
NSString *_fcompany;
};
// @property (nonatomic, copy) NSString * fname;
// @property (nonatomic, assign) NSUInteger fage;
// @property (nonatomic, strong) NSString * fphone;
// @property (nonatomic, unsafe_unretained) NSString * fcompany;
/* @end */
下面相當於fname的getter方法,通過self指標和上面的fname的指標偏移量來計算出記憶體地址,最後取出內容。
fname使用的是copy,這句相當於fname的setter方法,這裡的地址則是通過OFFSETOFIVAR函式來計算出來的,然後通過objc_setProperty函式進行復制
// @implementation Person
// @synthesize fname = _fname;
static NSString * _I_Person_fname(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_fname)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_Person_setFname_(Person * self, SEL _cmd, NSString *fname) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _fname), (id)fname, 0, 1); }
fage使用assign。它直接就是通過self指標和fage的指標偏移量計算出記憶體地址,然後把值賦值進去
// @synthesize fage = _fage;
static NSUInteger _I_Person_fage(Person * self, SEL _cmd) { return (*(NSUInteger *)((char *)self + OBJC_IVAR_$_Person$_fage)); }
static void _I_Person_setFage_(Person * self, SEL _cmd, NSUInteger fage) { (*(NSUInteger *)((char *)self + OBJC_IVAR_$_Person$_fage)) = fage; }
fphone使用strong。它直接就是通過self指標和fphone的指標偏移量計算出記憶體地址,然後把值賦值進去
// @synthesize fphone = _fphone;
static NSString * _I_Person_fphone(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_fphone)); }
static void _I_Person_setFphone_(Person * self, SEL _cmd, NSString *fphone) { (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_fphone)) = fphone; }
fcompany使用unsafe_unretained。它直接就是通過self指標和fcompany的指標偏移量計算出記憶體地址,然後把值賦值進去
// @synthesize fcompany = _fcompany;
static NSString * _I_Person_fcompany(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_fcompany)); }
static void _I_Person_setFcompany_(Person * self, SEL _cmd, NSString *fcompany) { (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_fcompany)) = fcompany; }
綜上,main函式:
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Person * p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName("setFname:"), (NSString *)&__NSConstantStringImpl__var_folders_v5_3kjxy0k94yxc39xljw9dp4240000gn_T_main_c9b13e_mi_0);
((void (*)(id, SEL, NSUInteger))(void *)objc_msgSend)((id)p, sel_registerName("setFage:"), (NSUInteger)18);
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName("setFphone:"), (NSString *)&__NSConstantStringImpl__var_folders_v5_3kjxy0k94yxc39xljw9dp4240000gn_T_main_c9b13e_mi_1);
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName("setFcompany:"), (NSString *)&__NSConstantStringImpl__var_folders_v5_3kjxy0k94yxc39xljw9dp4240000gn_T_main_c9b13e_mi_2);
}
}
所以從上述程式碼也差不多可以看出copy和strong的主要區別了。
深拷貝&淺拷貝:copy&multableCopy:
通俗點講就是淺拷貝拷貝指標,深拷貝則會將指標和內容都拷貝;
- 淺拷貝:就是生成一個新的指標,但是指標指向的內容地址還是原來的地址;
- 深拷貝:就是生成一個新的指標,指標指向的內容地址也是一個新的內容地址,只是內容是從原來的拷貝過來的;
一般來說,使用copy都是淺拷貝,使用mutableCopy都是深拷貝。但是這個不是絕對的,像NSMutableString、NSMutableArray、NSMutableDictionary這種帶NSMutable的進行Copy操作就都是深拷貝:
NSString * str = @"111";
NSString * str1 = [str copy];
NSString * str2 = [str mutableCopy];
NSLog(@"str內容地址:%p",str);
NSLog(@"str指標地址:%x",&str);
NSLog(@"str1內容地址:%p",str1);
NSLog(@"str1指標地址:%x",&str1);
NSLog(@"str2內容地址:%p",str2);
NSLog(@"str2指標地址:%x",&str2);
NSLog(@"****************************************\n");
NSMutableString * mStr = [[NSMutableString alloc]initWithString:@"222"];
NSMutableString * mStr1 = [mStr copy];
NSMutableString * mStr2 = [mStr mutableCopy];
NSLog(@"mStr內容地址:%p",mStr);
NSLog(@"mStr指標地址:%x",&mStr);
NSLog(@"mStr1內容地址:%p",mStr1);
NSLog(@"mStr1指標地址:%x",&mStr1);
NSLog(@"mStr2內容地址:%p",mStr2);
NSLog(@"mStr2指標地址:%x",&mStr2);
這裡執行出來的結果就如下:
str內容地址:0x100038090
str指標地址:6fdcdf58
str1內容地址:0x100038090
str1指標地址:6fdcdf50
str2內容地址:0x170271600
str2指標地址:6fdcdf48
****************************************
mStr內容地址:0x170271a80
mStr指標地址:6fdcdf40
mStr1內容地址:0xa000000003232323
mStr1指標地址:6fdcdf38
mStr2內容地址:0x170271ac0
mStr2指標地址:6fdcdf30
這裡可以很直觀地看到,對NSMutableString 進行的操作都是深拷貝的;
對NSString進行的操作,copy是淺拷貝,MutableCopy是深拷貝。
對自定義的型別進行拷貝的話,就得先實現相關協議,例如:
//.h
@interface FCFPerson : NSObject
@property (nonatomic, copy)NSString * name;
@property (nonatomic, assign) int age;
@end
//.m
@interface FCFPerson()<NSCopying>
@end
@implementation FCFPerson
-(id)copyWithZone:(NSZone *)zone{
FCFPerson * p = [[FCFPerson allocWithZone:zone]init];
p.name = self.name;
p.age = self.age;
return p;
}
-(id)mutableCopyWithZone:(NSZone *)zone{
FCFPerson * p = [[FCFPerson allocWithZone:zone]init];
p.name = self.name;
p.age = self.age;
return p;
}
@end
使用
FCFPerson * p = [[FCFPerson alloc]init];
p.name = @"fcf";
p.age = 18;
FCFPerson * p1 = [p copy];
FCFPerson * p2 = [p mutableCopy];
NSLog(@"p指向的內容地址:%p",p);
NSLog(@"p的name:%@,age:%d",p.name,p.age);
NSLog(@"p1指向的內容地址:%p",p1);
NSLog(@"p1的name:%@,age:%d",p1.name,p1.age);
NSLog(@"p2指向的內容地址:%p",p2);
NSLog(@"p2的name:%@,age:%d",p2.name,p2.age);
結果
p指向的內容地址:0x174032840
p的name:fcf,age:18
p1指向的內容地址:0x1740327c0
p1的name:fcf,age:18
p2指向的內容地址:0x1740328c0
p2的name:fcf,age:18
其他
- block使用copy不使用strong的原因:
block本身是可以像物件一樣可以retain和release的。但是block在的建立的時候,它的記憶體是分配在棧上的,而不是在堆上。它本身的作用域是屬於建立時候的作用域,一旦在建立的時候的作用域外面呼叫block將導致程式崩潰。可以使用retain,但是block的retain行為預設是copy的行為實現的。所以為了能讓宣告在棧裡的block被域外使用,把它copy到堆裡,防止被釋放,這就是為什麼block使用copy的原因。
block:
block{}體內,是不可以對外面的變數進行更改的,使用block就能改了;
block & weak:
block不管ARC還是MRC都可以使用;不僅可以修飾物件型別,也可以使用標量型別; block物件的值可以在block{}中修改值
weak只能在ARC中使用,只能修飾物件型別,weak物件的值不可以在block{}中