iOS的property相关修饰符

你如果参加过面试,80%的可能性会被问到过,@property的修饰符有哪些,区别又是什么;

既然想深入的了解我们就要知道它的来龙去脉,首先,我们的得知道什么是property。

Property

OC中称为属性,采用此属性,编译器会自动帮我们合成一个变量以及setter、getter方法,比如:

@property  NSString * name;

则系统会默认帮我们合成一个 NSString * _name;的成员,同时帮我们为此成员变量写好了setter、getter方法,因此,property的本质就是:

property = ivar + setter + getter

我们再看一下runtime.c的源码里关于类的结构定义:

struct objc_class {
        Class isa; // 指向metaclass

        Class super_class ; // 指向其父类
        const char *name ; // 类名
        long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
        long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
        long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
        struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
        struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
        struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;
        struct objc_protocol_list *protocols; // 存储该类遵守的协议
    }

我们重点看一下ivars 和 methodLists,他们就是保存成员和方法的结构体,property其实就是往对应的list里添加了一个变量或一对方法罢了。

上面我们提到是默认添加,但是我们总是面对不同的软件需求或则特立独行的风格,比如,我不想让它的变量为_name,可以么?我不想让它的设置方法为 - (void)setName:(NSString *)name;可以么?或则我想让它只读?

这时候就引出了一部分property相关的修饰符了,通过这些修饰符,我们告诉编译器,怎么帮我们合成成员和方法,主要的有:

内存相关
  • strong
  • copy
  • weak
  • retain
  • assign
  • unsafe_unretained
线程安全相关
  • atomic
  • nonatomic
读写权限
  • readonly
  • readwrite
存取方法样式
  • setter=
  • getter=

    下面我们分别讲解一下:
    strong、weak、retain、assign
    strong的英文含义为强悍,强壮,这里是强引用的意思,它和weak一样,是ARC里引进的,它主要的作用是让属性的引用计数加1,可以认为retain≈strong;而weak却只会引用,并不会让引用计数加1,如果变对象释放,再次调用该变量对象,就会出现崩溃;当对象dealloc时候,weak修饰的变量对象指针自动赋值为nil;assign与weak类似,只是assign不会在对象dealloc时候自动将对应的属性设置为nil;
    以上都是理论:但是具体是怎么实现的呢?

我们用NSString * name 来解释(ARC下):

retain、strong:

  • (void)setName:(NSString *) name{
    _name = name;
    }
    上面的_name 对象其实修饰符默认为__strong,假设原来_name有值,那么原来的值指向的对象引用计数减一,新赋值的name对象计数加一;
@interface Test : NSObject
@property (nonatomic,strong) NSString *name;
@end
@implementation Test
@end

等效于

@interface Test : NSObject
{
    __strong NSString * _name;
}
@end
@implementation Test
-(void)setName:(NSString *)name
{
    _name = name;
}
//getter忽略
@end

我们开始说过property会合成一个ivar,这个ivar前面的修饰符,就是编译器通过@property括号里的内存修饰符而来。

再延生至MAC下,strong应该就变成:

-(void)setName:(NSString *)name
{
    if(_name != name){
       [_name  release];
       _name = [name retain];
    }
}
或
-(void)setName:(NSString *)name
{
       [name retain];
       [_name  release];
       _name = name;
}

assign实现便是:

-(void)setName:(NSString *)name
{
    _name = name;
}

那weak的实现是怎么样的呢?因为我们知道,他会在持有改属性的对象dealloc的时候自动将属性指针设置为nil,其实这个很好理解,因为每个实例的Class,Ivar list里的ivar,包含了成员变量的类型、名称,这个类型通过类型编码,转换成了一个char *的字符串表示,如上面的name会被编码为:

T@"NSString",&,N,V_name

那么,当遍历属性列时候,遇到weak修饰的对象,就设置为nil即可。

copy
这里我们单独将copy拿出来,这是为什么呢?因为它比较特别,特别在它使用的对象不同,结果也不同;从它的英文含义里,我们知道,它的意思是拷贝的意思,那拷贝的是指针还是指针指向的内容呢,如果你了解C++,这里就类似别名、引用拷贝,

    @property (copy) NSString * name;
    @property (copy) NSArray * fans;
    @property (copy) NSMutableArray * books;

@implement

NSString *theName = @"iOS2班";
self.name = theName;
NSLog(@"%p %p",_name,theName);

NSArray * fans = @[@"老谢"];
self.fans=fans;
NSLog(@"%p %p",_fans,fans);

NSMutableArray  * books =[@[ @"iOS开发进阶"] mutableCopy];
self.books = books;
NSLog(@"%p %p",_books,books);

@end

那上面这段代码,如果放在运行中,有什么问题呢?

2015-11-18 22:36:51.061 Demo[11559:2656983] 0x105c381a8 0x105c381a8
2015-11-18 22:36:51.063 Demo[11559:2656983] 0x7f8d29c28860 0x7f8d29c28860
2015-11-18 22:36:53.748 Demo[11559:2656983] 0x7f8d29c284b0 0x7f8d29c20b20

很明显,前两个不可变的对象,copy后,地址并未改变,而对于可变的对象,产生了一个新的数组对象,而且这个数组里面包含的对象并未改变。

我们在最后再加上这行代码:

NSLog(@"%@ %@",[_books class],[books class]);

运行结果为:

Demo[11609:2659420] __NSArrayI __NSArrayM

这个说明其实_books是一个不可变数组。
但是,我们明明在property中声明的对象为NSMutableArray * books;
这里完全是因为iOS系统的动态运行时,它并没有在编译的时候就确定你的对象类型,所以如果,你在代码后面执行譬如增删_books的方法,那肯定是要crash的。

刚才我们测试的都是iOS系统的类,假设我们用自定义的对象使用copy会如何呢?


ViewModel *model= [ViewModel new];
 self.model=model;

这时候你一定会遇上你非常熟悉的朋友:
[ViewModel copyWithZone:]: unrecognized selector sent to instance…….
这是说我们没有实现copyWithZone方法啊,这是什么意思?
这个方法是NSCopying协议里的方法,也就是如果我们自定义的类想用copy修饰,那必须实现这个协议。

因此,我们总结下:
1、对于系统的不可变对象,拷贝的只是指针,引用计数加1;
2、对于系统的可变对象,生成一个新的指针,指针指向的内容如果是容器,那容器里的东西不变,如果是可变字符串,那就是一个字符相等的地址不同的字符串,而且新的指针都是指向不可变对象,内容的应用计数加1(字符串是生成一个新的字符串,新的计数为1,但是那种常量字符串不存在引用计数的概念,请区别)。
3、对于自定义对象,那你想怎么实现就怎么实现,怎么计数就怎么计数了,完全取决于你的实现。

线程安全相关

atomic、nonatomic:
原子性和非原子性,其实这是一个加锁和不加锁的区别,加锁消耗性能,不加锁的话如果在多线程中,就有可能导致存取数据有差错,比如:
当我在A线程中写该属性值时候,在还没写完全,线程切换到了B,B线程进行了读该属性,那么读取的属性肯定也是不完全的。

那么这时候如果加上锁的话,在A没写完的情况下,B线程就会等待,知道A写完解锁,B才能进入读。

从上面我们细想一下,如果上面的atomic所谓的线程安全其实并不安全,因为它只针对读写的方法,对于整体来说,它并不安全:
比如ABCD,多个线程都进行self.name= xxx;的赋值,然后显示到label中,这时候,就可能很随机了,因为线程执行时间是不确定的。另外,如果这个属性是容器的话,多个线程对这个容器进行读写,容器里的对象并不线程安全,安全的也只是属性的设置。

//太晚了,先睡觉吧。。。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值