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()

@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{}中

可以去github上查看Demo,喜欢的话star一下哦
github
CSDN

你可能感兴趣的:(iOS 基础02--单例、属性修饰符、深浅拷贝)