【iOS开发】——属性关键字

在定义属性的时候,都需要为属性加特性,属性的特性决定了属性在原子性、读写权限以及内存管理的特性。

第一类:原子性(atomic,nonatomic)

(1)atomic(默认)

atomic意为操作是原子的,即是只能通过单个线程去访问实例变量。
如何去限制只有一个线程去访问实例变量?
这种特性下,由系统自动生成的setter/getter方法会加锁,但是即使这样也不能一定保证线程安全,而且比较耗时,所以我们一般不选用atomic。

(2)nonatomic

nonatomic正好与atomic相反,表示是多线程的,多个线程可以同时去访问实例变量。在这种特性下,系统自动生成的setter/getter方法不会上锁,所以我们一般选用这个特性。

第二类:读写权限(readwrite/readonly)

(1)用readwrite修饰的属性,拥有getter和setter方法
(2)用readonly修饰的属性,只拥有getter方法,没有setter方法。

第三类:内存管理

再介绍下面的特性之前,先了解一下“引用计数”

(1) assign

assign主要应用于代表简单数据类型的property,比如int,float等。
如果这个用assign属性修饰的property代表一个指向对象的指针,那么当这个指针指向某个对象时,这个对象的引用计数不应该被改变。也就是说,用assign属性修饰的property,不应该持有一个对象。

(2)strong

strong强引用,当引用时,引用计数会加1,分配内存地址。

(3)weak

不会改变引用计数,weak比较常用的地方就是delegate属性的设置。

weak用于解决循环引用的例子
  1. delegate

例如:有两个类,分别是teacher和student,给teacher类添加个delegate的属性,并且让student作为teacher的代理。

//Teacher.h
#import <Foundation/Foundation.h>

@protocol TeacherDelegate <NSObject>

@end

@interface Teacher : NSObject
@property(nonatomic, weak) id <TeacherDelegate> delegate;
@end



//Teacher.m
#import "Teacher.h"

@implementation Teacher
- (void)dealloc {
    NSLog(@"Teacher is dealloc");
}
@end


//Student.m
#import "Student.h"
#import "Teacher.h"

@interface Student ()<TeacherDelegate>

@property (nonatomic, strong) Teacher *teacher;
@end

@implementation Student

- (instancetype)init {
    if (self = [super init]) {
        self.teacher = [[Teacher alloc] init];
        self.teacher.delegate = self;
    }
    return self;
}

- (void)dealloc {
    NSLog(@"Student is dealloc");
}
@end

#import "ViewController.h"
#import "Student.h"
#import "Teacher.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    Student *student = [[Student alloc] init];
}
@end

打印结果:
请添加图片描述

如果将delegate的修饰符换为strong,则什么都打印不出来。原因是:
在这里插入图片描述
在这里插入图片描述

以上是strong和weak对于delegate修饰的区别,那weak和assign呢?
大多数情况下,修饰delegate,既可以用weak,又可以用assign。因为在几乎所有场景下,delegate所指向的对象B的生存期都是覆盖了delegate成员变量本身所在的对象A的生存期的,所以,在A的生存期内,B所使用的A的指针都是有效的,所以这个时候使用assign是没有关系的。
但是使用weak更加安全,因为如果在delegate所指向的对象B被释放掉,在使用delegate发送消息时,就会因assign修饰而没有将delegate及时置为nil导致delegate指向了一个已销毁的对象而crash;若使用weak则会及时将delegate置为nil,防止程序崩溃。例如下面例子:

//Teacher.h
#import <Foundation/Foundation.h>

@protocol TeacherDelegate <NSObject>
-(void)testDelegate:(NSString *)str;
@end

@interface Teacher : NSObject
@property(nonatomic, assign) id <TeacherDelegate> delegate;
- (void)test;
@end

//Teacher.m
#import "Teacher.h"

@implementation Teacher
-(void)test{
    if(self.delegate && [self.delegate respondsToSelector:@selector(testDelegate:)]){
        [self.delegate testDelegate:@"我是一条测试回调"];
    }
}
@end

//ViewController.m
#import "ViewController.h"
#import "Student.h"
#import "Teacher.h"
@interface ViewController ()<TeacherDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    Teacher* teacher = [[Teacher alloc] init];
    {
        Student* student = [[Student alloc] init];
        teacher.delegate = student;
    }
    [teacher test];
}

@end

请添加图片描述
所以修饰delegate时,我们既不选择strong也不选择assign;如果非要选择assign,在delegate所指的对象销毁的同时给delegate手动置nil。

  1. Block
    当一个对象强引用一个block并且该block又强引用该对象,则会造成循环引用。例如:
//Teacher.h
#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface Teacher : NSObject
@property(nonatomic, strong) MyBlock block;
- (void)test;
@end

Teacher.m
#import "Teacher.h"

@implementation Teacher
-(void)test{
    self.block = ^{
        [self doSomething];
    };
    self.block();
}
- (void)doSomething {
    NSLog(@"12312");
}
-(void)dealloc {
    NSLog(@"已释放");
}
@end

#import "ViewController.h"
#import "Teacher.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    Teacher* teacher = [[Teacher alloc] init];
    
    [teacher test];
}
@end

并不会调用dealloc方法,因为Teacher对象就没有释放。并且编译器会弹出警告信息:存在保留环,也就是循环引用。
请添加图片描述
想要避免这个问题可以有以下两种处理方式:

1. 将属性修饰符改为weak
2. 将block内的self改为弱引用weakSelf,`__weak typeof(self) weakSelf = self;`。
  1. NSTimer
    在使用NSTimer时,VC会持有NSTimer,在创建NSTimer时会设置NSTimer的target,而这个target为self,就会形成VC->NSTimer、NSTimer->VC的循环引用。为了避免内存泄漏我们需要去解决循环引用。
    1. 解决方案一:使用weak修饰符修饰NSTimer属性。(不可行)
@property(nonatomic, weak) NSTimer* timer;

根据打印信息可以看出,ViewController并没有释放。

  1. 解决方案二:将NSTimer的target 改为weakSelf。(不可行)
#import "ViewController.h"
#import "WeakObject.h"
@interface ViewController ()
@property(nonatomic, weak) NSTimer* timer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:weakSelf selector:@selector(print) userInfo:nil repeats:NO];
}

- (void)print {
    NSLog(@"%@", self.timer);
}

- (void)dealloc {
    [_timer invalidate];
    _timer = nil;
    NSLog(@"%s", __func__);
}

根据打印结果,可以看出,这个方案也没有用。

  1. 解决方案三:重写一个继承于NSObject类的中间对象
    在这里插入图片描述

VC强引用NSTimer,NSTimer强引用中间对象,中间对象弱引用VC。

#import "ViewController.h"
#import "WeakObject.h"
@interface ViewController ()
@property(nonatomic, strong) NSTimer* timer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));
    
        // 当前VC强引用了 timer
        // timer 对 weakObject 强引用
        // 但是 weakObject 对 self 是弱引用的关系
        // 因此不会产生循环引用
        WeakObject *weakObject = [WeakObject weakObjectWithTarget:self];
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakObject selector:@selector(print) userInfo:nil repeats:NO];
    NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));
    
}

- (void)print {
    NSLog(@"%@", self.timer);
}
@end
  1. 解决方案四: 使用NSProxy类实现消息转发
    在这里插入图片描述
.h
@interface Proxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@end

.m
@interface Proxy()
@property (weak, nonatomic) id target;
@end

@implementation Proxy
+ (instancetype)proxyWithTarget:(id)target {
    Proxy *proxy = [Proxy alloc];
    proxy.target = target;
    return  proxy;
}

// 以下还是运用消息转发机制进行
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

-(void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}
@end

这个方法和上一个方法的不同是在于:寻找方法的顺序不同,上面是本类->父类->动态方法解析->消息转发;而这个是:本类->消息转发。可见这个方法可以提升性能。

  1. 解决方案五:在block内部使用弱引用
    如果使用了iOS10之后的方法,就不用担心target引起的强引用了,但是要注意在block中避免循环引用。
__weak typeof(self) weakSelf = self;
    self.timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf doSomething];
    }];

(4)copy

修饰NSString、NSArray、NSDictionary等有对应可变类型的对象,建立一个索引计数为1的对象,然后释放旧对象。所以被copy的对象引用计数不会加1。

(5)retain

retain不能修饰用来代表简单数据类型的property,如果一个property被retain修饰,这代表着这个property应该持有它所指向的对象。

retain和strong

  • strong是对象的默认内存管理关键字,表明该属性定义了一种“持有关系”
  • 声明属性时用strong或者retain效果是同样的(貌似更多开发者更倾向于用strong)。不过在声明Block时,使用strong和retain会有大相径庭的效果。strong会等于copy,而retain居然等于assign。

assign、weak和unsafe_unretain

  • 相同之处:都不是强引用
  • 不同之处:
    • weak引用的OC对象被销毁时,指针会自动清空,不再只想销毁的对象,不会产生野指针错误;
    • unsafe_unretain和assign修饰对象时在引用的OC对象被销毁时,指针不会清空。
    • assign修饰基本数据类型,内存在栈上由系统自动回收,不会造成野指针的问题。

关于深拷贝和浅拷贝:

表一:

本对象类型copy/mutableCopy拷贝方式生成对象类型
不可变对象copy浅拷贝不可变对象
不可变对象mutablCcopy深拷贝可变对象
可变对象copy深拷贝不可变对象
可变对象mutableCopy深拷贝可变对象

表二:不可变对象在不同传入变量类型和不同修饰符下的拷贝方式:

传入变量类型修饰符拷贝方式
不可变对象copy浅拷贝
不可变对象strong浅拷贝
可变对象copy深拷贝
可变对象strong浅拷贝

因为可变对象可以赋值给不可变对象,所以如果是strong修饰不可变对象时,内容会发生变化,而copy修饰符修饰的不可变对象会生成一个副本,原来的内容不会被修改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值