iOS常见的几个修饰词深入讲解

前言:

最近公司在扩招,做为公司仅有的唯一一个首席iOS开发工程师(手动滑稽),我不得不硬着头皮上阵。

然后却发现很多人的水平和年限严重不符,公司招的人都是3年+以上经验的人,然而这些人中有一半连修饰词的作用也说的模棱两可,加上自己水平也不高,对以后的职业生涯产生了严重的危机感,遂决定以后每周希望能写一篇有价值的文章,与君共勉,今天就说说iOS常见的几个修饰词。

一、readOnly,readWrite

readOnly:

根据字面意思,大家都很容易知道是“只读”的意思,意味着只生成了getter方法,而没有生成setter方法,如果这时候调用setter方法,会报一个Assignment to readonly property错误

PS:这里顺便说一下self.和_的区别

self.就是调用property自动生成的getter和setter方法,而_则是直接去调用实例变量(property会自动生成一个实例变量,如果你重写了getter与setter方法,property自动生成的实例变量就无效了,需要手动去申明一个实例变量或者用@@synthesize).

回到正题,那么这意味着我们就完全没办法去修改readOnly的属性了吗?不然,如果你尝试一下setValue:forKey:,你就会发现竟然改变成功了,amazing,让我们来看看代码:

@interface MyFirstClass : NSObject

@property (nonatomic, copy, readonly) NSString * string;

@end

#import "MyFirstClass.h"

@implementation MyFirstClass

- (instancetype) init{

 self = [super init];
 if (self) {
 _string = @"来改变我啊";
 }

 return self;
}

@end

- (void)viewDidLoad {

 [super viewDidLoad];

 MyFirstClass * class = [MyFirstClass new];
 NSLog(@"string === %@", class.string);
 [class setValue:@"改变了" forKey:NSStringFromSelector(@selector(string))];
 NSLog(@"string === %@", class.string);
}
Log如下:
2018-03-16 11:08:58.932303+0800 PropertyDesc[5681:445705] string === 来改变我啊
2018-03-16 11:08:58.932454+0800 PropertyDesc[5681:445705] string === 改变了

而如果这个时候在MyFirstClass里加入

@implementation MyFirstClass

- (instancetype) init{
 self = [super init];
 if (self) {
 _string = @"来改变我啊";
 }

 return self;
}

+ (BOOL) accessInstanceVariablesDirectly{
 return NO;
}

@end

在运行,boom,系统会报以下错误

2018-03-16 11:19:21.619747+0800 PropertyDesc[6016:478446] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<MyFirstClass 0x6040000088f0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key string.'

没有找到当前要赋值的属性,那么accessInstanceVariablesDirectly究竟是什么呢,我们打开苹果的官方文档找到Key-Value Coding Programming Guide

在这里可以看到,如果这个方法设为YES,访问器就会去寻找名称为_<key>, _is<Key>, <key>, or is<Key>的成员变量,如果为NO,就会跳到第6步,第6步就会报[valueForUndefinedKey:]错误。

总结:

readOnly并不能完全保证只读,我们可以通过KVC尝试去修改其值。

PS:有兴趣的小伙伴可以尝试去修改别人的SDK,包括苹果爸爸的

readWrite:

这个实在没什么可说的,默认的修饰词就是readWrite,代表可读可写

二、nonatomic、atomic

我们先看一下官方文档

苹果官网对两者的说法

atomic:

默认的属性修饰词,按官方文档上说即使从不同的线程通过getter或setter方法去访问属性也能完全的获取到或设置值,从这里也可以看出,atomic并不是线程安全的,因为atomic只能保证通过setter和getter方法能获取到一个完整的value,如果A线程在getter,B、C线程在setter,可能A获取到的值是BC执行之后的值,也可能是BC线程执行完之前的值,也可能是B C线程任何一个线程执行完的值。

因此atomic的伪代码大概如下:

- (void)setString:(NSString *)string{
 @synchronized(self) {
 if (_string != string) {
  [_string release];//MRC
  _string = [string copy];
 }
 }
}

- (NSString *) string{
 @synchronized(self) {
 return _ string;
 }
}

nonatomic:

相对而言,通过nonatomic修饰的属性,并没有做锁的操作,多线程同时进行setter/getter操作,并不能保证得到一个完整的value,所以相对atomic来说nonatomic修饰的属性访问速度更快,而且平时对线程安全我们更倾向于使用信号量、NSLock和synchronized去控制线程安全,他们都能保证代码块的原子性,所以几乎所有的属性都用nonatomic去修饰。

三、assign、weak与strong

assign:

一般来说,我们都用assign去修饰OC的基本数据类型,but why?

因为assign并不会使对象的引用计数加1,也就是说如果用assign去修饰一个对象,这个对象会立即被释放,重要的是assgin在被释放的时候是不会自动置为nil,还是保留对象的指针地址,会形成野指针,这个时候向其发送消息就会崩溃,简单实验的代码如下:

@interface MySecondClass : NSObject

@property (nonatomic, assign) NSMutableArray * array;

@end

- (void) testMethodTwo{
 MySecondClass * class = [[MySecondClass alloc] init];
 self.secondClass = class;
 self.secondClass.array = [NSMutableArray array];
 [self.secondClass.array addObject:@(0)];
}

在运行到最后一步的时候程序会崩溃报EXC_BAD_ACCESS的错误,如果打断点的话会发现在执行到这步的时候array还是有地址的。

weak:

如果把上面的代码
@property (nonatomic, assign) NSMutableArray * array;换成
@property (nonatomic, weak) NSMutableArray * array;

.这个时候程序并不会崩溃,如果你打个断点的话会发现array被自动置为nil,而OC的特性使得像nil发送消息并不会崩溃,这就是weak和assgin最大的区别,此外weak必须用于修饰对象,这和他自动置为nil相关,如果强行使用weak修饰基本数据类型,编译器会报一个大大的红色错误!

strong:

strong的作用和assign和weak恰恰相反,strong也是属性默认的修饰词,代表着被修饰的对象引用计数+1

如果把上面的代码
@property (nonatomic, assign) NSMutableArray * array;换成
@property (nonatomic, strong) NSMutableArray * array;

self.secondClass.array = [NSMutableArray array];

最后一句代码可以解释为[NSMutableArray array]创造了一个对象A,此时A的引用计数为1,self.secondClass.array做为对象B,把A赋值给B的时候,A的引用计数加1,此时A的引用计数为2,B指向了A,然后编译器会自动对A进行释放操作(因为是局部变量),A的引用计数-1。在拥有B的对象不释放的时候,A的引用计数永远不可能为0,除非你手动释放或者把B指向一个新的对象,这样A永远不会被释放,这就是所谓的强引用。

weak和strong的区别:weak和strong不同的是 当一个对象不再有strong类型的指针指向它的时候 它会被释放 ,即使还有weak型指针指向它。一旦最后一个strong型指针离去 ,这个对象将被释放,所有剩余的weak型指针都将被清除。

copy与retain:

  • copy其实是建立了一个相同的对象,而retain不是.
  • copy是内容拷贝,retain是指针拷贝.
  • copy是内容的拷贝 ,对于像NSString,的确是这样,如果拷贝的是NSArray这时只是copy了指向array中相对应元素的指针.这便是所谓的"浅复制".

atomic是Objc使用的一种线程保护技术,基本上来讲,是防止在写未完成的时候被另外一个线程读取,造成数据错误。而这种机制是耗费系统资源的,所以在iPhone这种小型设备上,如果没有使用多线程间的通讯编程,那么nonatomic是一个非常好的选择。

四、copy与mutableCopy

苹果官网对对象拷贝的说法

在说copy与mutableCopy之前我们先看看官方文档对深拷贝与浅拷贝的阐释,如下

深拷贝:

对象拷贝 - 重新申请一片内存保留这个对象,与原对象之间没有半点关系。

浅拷贝:

指针拷贝 - 实际上相当于引用计数+1,被拷贝的和拷贝的引用同一个对象。

接下来我们分两个方面做测试:

1.对非集合类对象的copy操作,以NSString为例

对immutableObject做copy操作

 NSString * string = [NSString stringWithFormat:@"1"];
 NSString * copyString = [string copy];
 NSString * mutableCopyString = [string mutableCopy];
 NSLog(@"string:%p", string);
 NSLog(@"copyString:%p", copyString);
 NSLog(@"mutableCopyString:%p", mutableCopyString);

Log如下:

2018-03-19 15:51:38.785253+0800 PropertyDesc[10283:759804] string:0xa000000000000311
2018-03-19 15:51:38.785435+0800 PropertyDesc[10283:759804] copyString:0xa000000000000311
2018-03-19 15:51:38.785518+0800 PropertyDesc[10283:759804] mutableCopyString:0x608000055150

可以看出对string和copyString的地址是一样的,而mutableCopyString则不同。

对mutableObject做copy操作

 NSMutableString * string = [NSMutableString stringWithFormat:@"1"];
 NSString * copyString = [string copy];
 NSString * mutableCopyString = [string mutableCopy];
 NSLog(@"string:%p - %@", string, string);
 NSLog(@"copyString:%p - %@", copyString, copyString);
 NSLog(@"mutableCopString:%p - %@", mutableCopyString, mutableCopyString);
 [string appendString:@",2"];
 NSLog(@"copyString:%p - %@", copyString, copyString);
 NSLog(@"mutableCopString:%p - %@", mutableCopyString, mutableCopyString);

Log如下:

2018-03-19 15:51:38.785670+0800 PropertyDesc[10283:759804] string:0x60400005a940 - 1
2018-03-19 15:51:38.785784+0800 PropertyDesc[10283:759804] copyString:0xa000000000000311 - 1
2018-03-19 15:51:38.785834+0800 PropertyDesc[10283:759804] copyString:0xa000000000000311 - 1
2018-03-19 15:51:38.785891+0800 PropertyDesc[10283:759804] mutableCopyString:0x60400005a910 - 1
2018-03-19 15:51:38.786037+0800 PropertyDesc[10283:759804] mutableCopyString:0x60400005a910 - 1

可以看出对string与copyString、mutableCopyString三者的地址都是不同的。

即使改变了原string的value,copyString与mutableCopystring也没有改变,这与下文对集合类对象得出的结论正好相反。

结论:

对 immutableObject进行 copy 操作是指针拷贝,mutableCopy 操作时对象拷贝。

对 mutable Object进行 copy 和 mutableCopy 都是对象拷贝。简单的表格图如下:

Object Handle Result
immutableObject copy 指针拷贝
immutableObject mutableCopy 深拷贝
mutableObject copy 深拷贝
mutableObject mutableCopy 深拷贝

2.对集合类对象的copy操作

对immutableObject做copy操作

 NSArray * array = [NSArray arrayWithObject:@"1"];
 NSArray * copyArry = [array copy];
 NSMutableArray * mutableCopyArray = [array mutableCopy];

 NSLog(@"array:%p", array);
 NSLog(@"copyArry:%p", copyArry);
 NSLog(@"mutableCopyArray:%p", mutableCopyArray);

Log如下

2018-03-19 15:51:38.786167+0800 PropertyDesc[10283:759804] array:0x6000000094c0
2018-03-19 15:51:38.786278+0800 PropertyDesc[10283:759804] copyArray:0x6000000094c0
2018-03-19 15:51:38.786385+0800 PropertyDesc[10283:759804] mutableCopyArray:0x600000240030

可以看出array与copyArray的地址是一样的,而mutableCopyArray则不同。

对mutableObject做copy操作

 NSMutableString * string = [NSMutableString stringWithFormat:@"1"];
 NSMutableArray * array = [NSMutableArray arrayWithObject:string];
 NSArray * copyArry = [array copy];
 NSMutableArray * mutableCopyArray = [array mutableCopy];

 NSLog(@"array:%p", array);
 NSLog(@"copyArry:%p", copyArry);
 NSLog(@"mutableCopyArray:%p", mutableCopyArray);
 [array addObject:@"2"];
 [string appendString:@"1"];

 NSLog(@"array:%p - %@", array, array);
 NSLog(@"copyArry:%p - %@", copyArry, copyArry);
 NSLog(@"mutableCopArray:%p - %@", mutableCopyArray, mutableCopyArray);

Log如下

2018-03-26 13:36:38.786499+0800 PropertyDesc[10283:759804] array:0x600000240150
2018-03-26 13:36:38.786600+0800 PropertyDesc[10283:759804] copyArry:0x6000000095f0
2018-03-26 13:36:38.786698+0800 PropertyDesc[10283:759804] mutableCopyArray:0x6000002400f0
2018-03-26 13:36:38.786865+0800 PropertyDesc[10283:759804] array:0x600000240150 - (
    11,
    2
)
2018-03-26 13:36:38.787018+0800 PropertyDesc[10283:759804] copyArry:0x6000000095f0 - (
    11
)
2018-03-26 13:36:38.787142+0800 PropertyDesc[10283:759804] mutableCopyArray:0x6000002400f0 - (
    11
)

What??不管是copy还是mutableCopy我们可以看到当我们修改了string的值后,数组中的值都变了,但是在 [array addObject:@"2"];的时候两个复制出来的数组的对象并没有变化?

这里我们要提到一个新概念 不完全深拷贝(也有人说是单层深拷贝 )------ 即虽然新开辟了内存地址,但是存放在内存上的值(也就是数组里的元素仍然指向原数组元素值,并没有另外复制一份),所以说上文中的array和mutableCopArray类似于有一个或多个相交点的相交链表。

而且我们也可以看到不管是copy还是mutableCopy都是不完全深拷贝,三者的地址都是不一样的。

结论:

对immutableObject做copy是指针拷贝,做mutableCopy是不完全深拷贝。

对mutableObject做copy或mutableCopy都是不完全深拷贝。

有趣的是,这与上文的结论有类似之处。简单的表格图如下:

Object Handle Result
immutableObject copy 指针拷贝
immutableObject mutableCopy 不完全深拷贝
mutableObject copy 不完全深拷贝
mutableObject mutableCopy 不完全深拷贝

并且如果打个断点可以发现对任何对象做copy操作返回的是一个不可变的对象,对任何对象做mutableCopy返回的是一个可变的对象(有兴趣的请自行验证)。

五、是用copy还是strong?

通过上述对copy与strong的描述,copy和strong最终都会返回一个引用计数为1的对象,所以单单从内存管理上说copy和strong并无任何区别,但是copy始终会返回一个不可变对象,他的值是不会被修改的。

而strong不同,被strong修饰的对象,可能会被可变对象赋值,从而在外部导致不可预料的被更改的情况。

总而言之,是否使用copy或strong还是根据具体场景来定,这里还有个性能优化的小技巧,如果copy的是可变的对象,会对它做一次完全深拷贝/不完全深拷贝,性能上是肯定不如strong直接引用计数+1来的快。

六、结尾

这次的修饰词讲解到此就告一段落了,然后想一下这周写什么样的文章。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • iOS开发之详谈属性设置readwrite、readonly、retain、copy、assign、nonatomic

    具体详细介绍请看下文文字说明吧,本文介绍的非常详细. 1 . 可读性: readonly.readwrite @property(readwrite,....) valueType value; 这个属性是变量的默认属性,就是如果你 (readwrite and readonly 都没有使用,那么你的变量就是 readwrite属性 ) ,通过加入 readwrite 属性你的变量就会有 get 和 set 方法. property(readonly,...) valueType value;

  • 浅谈iOS中几个常用协议 NSCopying/NSMutableCopying

    1.几点说明 说到NSCopying和NSMutableCopying协议,不得不说的就是copy和mutableCopy. 如果类想要支持copy操作,则必须实现NSCopying协议,也就是说实现copyWithZone方法; 如果类想要支持mutableCopy操作,则必须实现NSMutableCopying协议,也就是说实现mutableCopyWithZone方法; iOS系统中的一些类已经实现了NSCopying或者NSMutableCopying协议的方法,如果向未实现相应方法的系

  • IOS中(assign,retain,copy,weak,strong)的区别以及nonatomic的含义

    IOS中(assign,retain,copy,weak,strong)的区别以及nonatomic的含义 我们在声明@property 属性时,总是要在括号中写上assign.retain.copy.weak.strong中的一个,很多时候,我们仅仅只是按照习惯去写经常写的那一个,但有时候看代码时又会发现别人用的不尽相同,那这些之间的区别是什么呢? 首先,上面五个并不是在一个层面上的,可以分为两部分,第一部分是assign.retain.copy,第二部分是weak.strong. 我们先说第

  • 浅析iOS中的浅拷贝和深拷贝(copy和mutableCopy)

    ios提供了copy和mutablecopy方法,顾名思义,copy就是复制了一个imutable的对象,而mutablecopy就是复制了一个mutable的对象. copy与retain的区别: copy是创建一个新对象,retain是创建一个指针,引用对象计数加1.Copy属性表示两个对象内容相同,新的对象retain为1 ,与旧有对象的引用计数无关,旧有对象没有变化.copy减少对象对上下文的依赖. retain属性表示两个对象地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的

  • iOS常见的几个修饰词深入讲解

    前言: 最近公司在扩招,做为公司仅有的唯一一个首席iOS开发工程师(手动滑稽),我不得不硬着头皮上阵. 然后却发现很多人的水平和年限严重不符,公司招的人都是3年+以上经验的人,然而这些人中有一半连修饰词的作用也说的模棱两可,加上自己水平也不高,对以后的职业生涯产生了严重的危机感,遂决定以后每周希望能写一篇有价值的文章,与君共勉,今天就说说iOS常见的几个修饰词. 一.readOnly,readWrite readOnly: 根据字面意思,大家都很容易知道是"只读"的意思,意味着只生成了

  • IOS 常见内存泄漏以及解决方案

    IOS 常见内存泄漏以及解决方案 整理了几个内存泄漏的例子,由于转载地址已经找不到了,在这里就不一一列出来了. 1 OC和CF转化出现的内存警告 CFStringRef cfString = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,(CFStringRef)picDataString,NULL,CFSTR(":/?#[]@!$&'()*+,;="),kCFStringEncodingUTF8); N

  • IOS 常见的循环引用总结

    IOS 常见的循环引用总结 介绍: 循环引用,指的是多个对象相互引用时,使得引用形成一个环形,导致外部无法真正是否掉这块环形内存.其实有点类似死锁. 举个例子:A->B->C->....->X->B   ->表示强引用,这样的B的引用计数就是2,假如A被系统释放了,理论上A会自动减小A所引用的资源,就是B,那么这时候B的引用计数就变成了1,所有B无法被释放,然而A已经被释放了,所有B的内存部分就肯定无法再释放再重新利用这部分内存空间了,导致内存泄漏. 情况一:deleg

  • iOS常见算法以及应用知识点总结

    算法比较 关键词 二分 递归 分治 回溯 冒泡排序 思想:两次循环,外层进行循环次数的控制,内层循环,进行数据之间的比较,大的数据上浮(下沉) #pragma mark - Objective-C //冒泡排序 - (void)bubbleSort:(id)array{ if (!([array isKindOfClass:[NSArray class]] || [array isKindOfClass:[NSMutableArray class]])) { NSLog(@"传入的参数不是数组类

  • ios常见加密解密方法(RSA、DES 、AES、MD5)

    最近做了一个移动项目,是有服务器和客户端类型的项目,客户端是要登录才行的,服务器也会返回数据,服务器是用Java开发的,客户端要同时支持多平台(Android.iOS),在处理iOS的数据加密的时候遇到了一些问题.起初采取的方案是DES加密,老大说DES加密是对称的,网络抓包加上反编译可能会被破解,故采取RSA方式加密.RSA加密时需要公钥和私钥,客户端保存公钥加密数据,服务器保存私钥解密数据.(iOS端公钥加密私钥解密.java端公钥加密私钥解密,java端私钥加密公钥解密都容易做到,iOS不

  • 浅析被遗忘的SQLServer比较运算符修饰词

    SQLServer中有三个关键字可以修改比较运算符:All.Any和Some,其中Some和Any等价.官方的参考文档http://technet.microsoft.com/zh-cn/library/ms187074%28SQL.90%29.aspx他们作用于比较运算符和子查询之间,作用类似Exists.not exists.in.not in以及其他逻辑意义,这些语法同样被SQLServer2000支持但是很少看到有人用它们. 复制代码 代码如下: set nocount on use t

  • iOS常见宏理解及使用方法

    FOUNDATION_EXPORT, UIKIT_EXTERN 该宏的作用类似于extern,使用方法也与extern类似,在.m文件中,定义如下 NSString *const kFoundationExportString = @"Hello World"; NSString *const kExternString = @"Hello World"; 然后在.h文件中加上以下声明, 就可以在导入该.h文件的类中访问该常量. FOUNDATION_EXPORT

  • iOS MRC 下 block 循环引用问题实例讲解

    下面一段代码给大家介绍iOS MRC 下 block 循环引用问题 //注意此__block会复制一份指针出来 一次原始的指针如果置为nil的话,此处复制出来的指针还是野指针 __block __typeof(self)weakSelf = self; //__weak __typeof(self)weakSelf = self; //__weak Person *weakSelf = self; void (^block)(void) = ^(void){ //NSLog(@"name --&

  • C++中volatile关键字及常见的误解总结

    前言 近期看到C++标准中对volatile关键字的定义,发现和java的volatile关键字完全不一样,C++的volatile对并发编程基本没有帮助.网上也看到很多关于volatile的误解,于是决定写这篇文章详细解释一下volatile的作用到底是什么. 为什么用volatile? C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量,通常用于建立语言级别的 memory barrier.这是 BS 在 "The C++ Programming Language&

  • C++中volatile关键字的使用详解以及常见的误解

    为什么使用volatile ? C/C++中的 volatile 关键字 和const对应,用来修饰变量,通常用于建立语言级别的memory barrier.这是BS在"The C++ Programming Language"对volatile修饰词的解释: A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the lang

随机推荐