@property详解,@property修饰符以及各个修饰符区别(上)
本人已迁移博客至掘进,以后会在掘进平台更新最新的文章也会有更多的干货,欢迎大家关注!!!https://juejin.im/user/588993965333309
相信很多参加过面试的人员很多都会被问到:weak与assign的区别,copy与strong的区别。如果你仅仅说一点点copy一般对NSString,weak对于控件的修饰,assign对于基本类型,那么面试官可以会对你深入问,block用过吗?修饰block用什么,又为什么用copy,这样一层层问下去,可能场面就很尴尬了,即使你进去,可能薪资也不能达到你所期望的。这篇我准备花几天完成,希望对大家有所帮助,阅读这篇问题大约需要20-30分钟……
一.@property
1.讲解
Objective-C的属性(property)是通过用@property定义的公有或者私有的方法。属性(property)提供了一种安全、便捷的方式来与这些属性(attribute)交互,而不需要手动编写一系列的访问方法,如果需要的话可以自定义getter和setter方法来覆盖编译器自动生成的相关方法。
尽量多的使用属性(property)而不是实例变量(attribute)因为属性(property)相比实例变量有很多的好处:
(1)自动合成getter和setter方法。当声明一个属性(property)的时候编译器默认情况下会自动生成相关的getter和setter方法更好的声明一组方法。
(2)因为访问方法的命名约定,可以很清晰的看出getter和setter的用处。
2.用法
@interface Person : NSObject { NSString *_name; NSUInteger _age; } - (void)setName:(NSString*)name; - (NSString*)name; - (void)setAge:(NSUInteger)age; - (NSUInteger)age; @end @implementation Person - (void)setName:(NSString*)name { _name = [name copy]; } - (NSString*)name { return _name; } - (void)setAge:(NSUInteger)age { _age = age; } - (NSUInteger)age { return _age; } @end
上述代码就是手动创建变量的getter
和setter
的实现,getter
和setter
本质就是符合一定命名规范的实例方法。
具体使用
int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = [[Person alloc] init]; //函数调用name的setter [p setName:@"Jiaming Chen"]; //函数调用age的setter [p setAge:22]; //函数调用name和age的getter,输出 Jiaming Chen 22 NSLog(@"%@ %ld", [p name], [p age]); } return 0; } //另一种写法 int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = [[Person alloc] init]; //使用点语法访问name的setter p.name = @"Jiaming Chen"; //使用点语法访问age的setter p.age = 22; //使用点语法访问name和age的getter,输出 Jiaming Chen 22 NSLog(@"%@ %ld", p.name, p.age); } return 0; }
Objective-C允许使用点语法来访问getter
和setter
。
合成使用,使用@property方法
@interface Person : NSObject @property (nonatomic, copy) NSString* name; @property (nonatomic, assign) NSUInteger age; @end @implementation Person //编译器会帮我们自动生成_name和_age这两个实例变量,下面代码就可以正常使用这两个变量了 @synthesize name = _name; @synthesize age = _age; - (void)setName:(NSString*)name { //必须使用_name来赋值,使用self.name来设置值时编译器会自动转为调用该函数,会导致无限递归 //使用_name则是直接访问底层的存储属性,不会调用该方法来赋值 //这里使用copy是为了防止NSMutableString多态 _name = [name copy]; } - (NSString*)name { //必须使用_name来访问属性值,使用self.name来访问值时编译器会自动转为调用该函数,会造成无限递归 return _name; } @end
二.@property后面有哪些修饰符
1.线程安全的-------atomic、nonatomic
atomic:原子性:Object-C使用的是一种线程保护技术,从基本上讲,是防止在未完成的时候被另一个线程更改,造成数据错误。而这种机制是耗费系统资源,所以在iPhone这种小型设备上,如果没有使用多线程之间的通讯编程,那么nonatomic是一个非常好的选择。
nonatomic:非原子性: 禁止多线程,变量保护,提高性能。
原子性:默认
这个属性是为了保证程序在多线程下,编译器会自动生成自旋锁代码,避免该变量的读写不同步问题,提供多线程线程,多线程只有一个线程对它访问。
attention
(1)atomic原子性指的是一个操作不可以被CPU中途叫停,然后再调度。即不能被中断,要么执行完,要么不执行。
(2)atomic是自旋锁,当上一个线程没有执行完毕的时候(被锁住),下一个线程会一直等待,不会进入睡眠,当上一个线程执行完毕后,下一个线程会立即执行。它区别于互斥锁,互斥锁在等待的时候,会进入睡眠状态,当上一个线程执行完毕之后,会被唤醒,然后再执行。
(3)atomic需要消耗大量的资源,执行效率低
但是atomic并不保证线程安全,因为只会保持set内部安全,在外面并不能保证安全!例如在数组array set方法中atomic原子操作,但是外面使用array addobject并不会保证线程安全!
二、访问权限
readwrite 默认 拥有getter/setter方法,可读可写
readonly 只读属性,只会生成getter方法,不会生成setter方法
三、内存管理
1.assign 默认
适用于基本数据类型:NSInteger,CGFloat和C数据类型int,float等,另外还有id类型
2.strong对应MRC中的retain
强引用,只有OC对象才能使用该属性,它使对象的引用计数加1
3.weak
弱引用,只是单纯引用某个对象,但是并未拥有该对象
即一个对象被持有无数个弱引用,只要没有强引用指向它,那么它就会被清楚释放。
下面通过一些拓展一下:面试重点!!!
(1)strong与retain
相同点:strong和retain都是针对对象类型进行内存管理。如果去修饰基本数据类型,Xcode会直接报错,当给对象类型使用此修饰符时,setter方法先将旧的对象属性release掉,再将新的对象赋值给属性并对该对象进行一次retain操作,两者都会增加对象的引用计数。
不同点:strong一般用于ARC,retain一般用于MRC环境。
(2)assgin与weak
相同点:assgin和weak不会牵扯到内存管理,不会增加引用计数
不同点:assign可修饰基本数据类型,也可修饰OC对象,但如果修饰对象类型指向的是一个强指针,当它指向的这个指针释放后,他仍指向这块内存,必须手动给置为nil,否则就会产生野指针,如果还通过此指针操作那块内存,便会导致EXC_BAD_ACCESS错误,调用了已经释放的内存空间;而weak只能修饰OC对象,且相比assign比较安全,如果指向的对象消失了,那么他会自动置为nil,不会产生野指针。
(3)strong与copy(重点重点)--这个可能比较难懂,多看两遍,可能有点乏味,不过很重要!!!
1.copy(拓展)----深拷贝和浅拷贝的区别
浅拷贝:指针拷贝,不产生新的对象,源对象的引用计数器加1;只是多了一个指向这块内存的指针,共用一块内存。
深拷贝:对象拷贝,会产生新的对象,源对象的引用计数器不变;两块内存是完全不同的,也就是两个对象指针分别指向不同的内存,互不干涉。
判断是浅拷贝和深拷贝就看一下两个变量的内存地址是否一样,一样就是浅拷贝,不一样就是深拷贝,也可以改变一个变量的其中一个属性值看两者的值都会发生变化;
系统原生的对象深浅拷贝区别:
NSObject类提供了copy和mutableCopy方法,通过这两个方法即可拷贝已有对象的副本,主要的系统原生对象有:NSString和NSMutableString、NSArray和NSMutableArray、NSDictionary和NSMutableDictionary、NSSet和NSMutableSet。 NSValue和NSNumber 只遵守的NSCopying协议。
NSString-------copy/mutableCopy
NSString *string = @"copyTest"; NSString *copyString = [string copy]; NSString *mutableCopyString = [string mutableCopy]; NSMutableString *copyMutableString = [string copy]; NSMutableString *mutableCopyMutableString = [string mutableCopy]; NSLog(@"\n string = %p \n copystring = %p \n mutablecopystring = %p " "\n copyMutableString = %p \n mutableCopyMutableString = %p \n", string, copyString, mutableCopyString, copyMutableString, mutableCopyMutableString);
打印结果:
2018-05-06 10:31:51.209346+0800 copy[829:67521] string = 0x100001040 copystring = 0x100001040 mutablecopystring = 0x10058c8e0 copyMutableString = 0x100001040 mutableCopyMutableString = 0x10058cde0 Program ended with exit code: 0
小结论:在字符串是直接赋值的,是否生成新对象是和=右边相关的,如果=右边的是mutableCopy才会产生新的对象
NSMutableString----copy/mutableCopy
NSMutableString *string = [NSMutableString stringWithString:@"学习研究"]; NSString *copyString = [string copy]; NSString *mutableCopyString = [string mutableCopy]; NSMutableString *copyMutableString = [string copy]; NSMutableString *mutableCopyMutableString = [string mutableCopy]; NSLog(@"\n string = %p \n copystring = %p \n mutablecopystring = %p " "\n copyMutableString = %p \n mutableCopyMutableString = %p \n", string, copyString, mutableCopyString, copyMutableString, mutableCopyMutableString);
打印结果:
2018-05-06 10:48:44.755398+0800 copy[929:77373] string = 0x100504600 copystring = 0x100554e10 mutablecopystring = 0x100555880 copyMutableString = 0x100506e40 mutableCopyMutableString = 0x1005558f0 Program ended with exit code: 0
小结论:只要=右边从创建到赋值,至少包含一个MSMutable便会重新创建生成一个对象。
其他对象NSArray、NSMutableArray 、NSDictionary、NSMutableDictionary、NSSet、NSMutableSet一样适用。
刚刚疏解完copy,下面看一下面试最喜欢问的strong和copy修饰的区别:
下面看一组例子:
以NSString为例说明下,首先定义以下属性。
1 @property (nonatomic, strong) NSString *strongString; 2 @property (nonatomic, copy) NSString *copyedString; 3 @property (nonatomic, strong) NSMutableString *strongMutableString; 4 @property (nonatomic, copy) NSMutableString *copyedMutableString;
2.1 当外部赋给对应属性一个不可变(非mutable)的字符串 NSString
1 - (void)testPropertyCopyOrStrong 2 { 3 NSString *string = [NSString stringWithFormat:@"abc"]; 4 self.strongString = string; 5 self.strongMutableString = string; 6 self.copyedString = string; 7 self.copyedMutableString = string; 8 string = [string stringByReplacingOccurrencesOfString:@"c" withString:@"233"]; 9 10 NSLog(@"\n origin string: %p, %p %@ %@", string, &string, string, NSStringFromClass([string class])); 11 NSLog(@"\n strong string: %p, %p %@ %@", _strongString, &_strongString, _strongS tring, NSStringFromClass([_strongString class])); 12 NSLog(@"\n strongMutable string: %p, %p %@ %@", _strongMutableString, &_strongMutableSt ring, _strongMutableString, NSStringFromClass([_strongMutableString class])); 13 NSLog(@"\n copy string: %p, %p %@ %@", _copyedString, &_copyedString, _copyedS tring, NSStringFromClass([_copyedString class])); 14 NSLog(@"\n copyMutable string: %p, %p %@ %@", _copyedMutableString, &_copyedMutableSt ring, _copyedMutableString, NSStringFromClass([_copyedMutableString class])); 15 16 }
打印结果:
1 origin string: 0x103a74098, 0x7fff5c18ca88 ab233 __NSCFString 2 strong string: 0xa000000006362613, 0x7f84c9f056d8 abc NSTaggedPointerString 3 strongMutable string: 0xa000000006362613, 0x7f84c9f056e8 abc NSTaggedPointerString 4 copy string: 0xa000000006362613, 0x7f84c9f056e0 abc NSTaggedPointerString 5 copyMutable string: 0xa000000006362613, 0x7f84c9f056f0 abc NSTaggedPointerString
可能大家不是很看懂这个例子:我们换一个简单的操作:
首先在类延展中声明两个属性变量:
1 @property (nonatomic, strong)NSString * stringStrong; //strong修饰的字符串对象 2 @property (nonatomic, copy)NSString * stringCopy; //copy修饰的字符串对象
接着创建两个不可变字符串(NSString)
1 //新创建两个NSString对象 2 NSString * strong1 = @"I am Strong!"; 3 NSString * copy1 = @"I am Copy!";
将这两个属性进行赋值
1 //初始化两个字符串 2 self.stringStrong = strong1; 3 self.stringCopy = copy1;
分别打印四个变量的地址
1 StrongOrCopy[5046:421886] strong1 = 0x10a0b3078 2 StrongOrCopy[5046:421886] stringStrong = 0x10a0b3078
//这是两个字符串
3 StrongOrCopy[5046:421886] copy1 = 0x10a0b3098
4 StrongOrCopy[5046:421886] stringCopy = 0x10a0b3098
结果发现:可以看出,无论是strong修饰的字符串还是copy修饰的字符串,都进行了浅拷贝(仅仅是多了个指向该内存的指针,地址不会发挥变化)
如果创建两个不可变字符串对象(NSMutableString)呢
1 //新创建两个NSMutableString对象 2 NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"]; 3 NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"];
分别对属性再次赋值
self.stringStrong = mutableStrong;
self.stringCopy = mutableCopy;
打印结果:
1 1 StrongOrCopy[5046:421886] mutableStrong = 0x7fccba425d60 2 2 StrongOrCopy[5046:421886] stringStrong = 0x7fccba425d60 3 3 StrongOrCopy[5046:421886] mutableCopy = 0x7fccba40d7c0 4 4 StrongOrCopy[5046:421886] stringCopy = 0x7fccba4149e0
结果发现:这时就发现了,用strong修饰的字符串依旧进行了浅Copy,而由copy修饰的字符串进行了深Copy,所以mutableStrong与stringStrong指向了同一块内存,而mutableCopy和stringCopy指向的是完全两块不同的内存。
看了一些实例,有什么用呢,看如下:
1 //新创建两个NSString对象 2 NSString * strong1 = @"I am Strong!"; 3 NSString * copy1 = @"I am Copy!"; 4 5 //初始化两个字符串 6 self.stringStrong = strong1; 7 self.stringCopy = copy1; 8 9 //两个NSString进行操作 10 [strong1 stringByAppendingString:@"11111"]; 11 [copy1 stringByAppendingString:@"22222"];
结果如下:
1 StrongOrCopy[5146:439360] strong1 = I am Strong! 2 StrongOrCopy[5146:439360] stringStrong = I am Strong! 3 StrongOrCopy[5146:439360] copy1 = I am Copy! 4 StrongOrCopy[5146:439360] stringCopy = I am Copy!
分别对在字符串后面进行拼接,当然这个拼接对原字符串没有任何的影响,因为不可变自字符串调用的方法都是有返回值的,原来的值是不会发生变化的.打印如下,对结果没有任何的影响:(不可变的字符串)
然后是对可变字符串进行操作:
1 //新创建两个NSMutableString对象 2 NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"]; 3 NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"]; 4 5 //初始化两个字符串 6 self.stringStrong = mutableStrong; 7 self.stringCopy = mutableCopy; 8 9 //两个MutableString进行操作 10 [mutableStrong appendString:@"Strong!"]; 11 [mutableCopy appendString:@"Copy!"];
打印结果如下:
1 StrongOrCopy[5245:446189] stringStrong = StrongMutableStrong! 2 StrongOrCopy[5245:446189] mutableStrong = StrongMutableStrong! 3 StrongOrCopy[5245:446189] stringCopy = CopyMutable 4 StrongOrCopy[5245:446189] mutableCopy = CopyMutableCopy!
对mutableStrong进行的操作,由于用strong修饰的stringStrong没有进行深Copy,导致共用了一块内存,当mutableStrong对内存进行了操作的时候,实际上对stringStrong也进行了操作; 相反,用copy修饰的stringCopy进行了深Copy,也就是说stringCopy与mutableCopy用了两块完全不同的内存,所以不管mutableCopy进行了怎么样的变化,原来的stringCopy都不会发生变化.这就在日常中避免了出现一些不可预计的错误。
总结:在不可变对象之间进行转换,strong与copy作用是一样的,但是如果在不可变与可变之间进行操作,那么楼主比较推荐copy,这也就是为什么很多地方用copy,而不是strong修饰NSString,NSArray等存在可变不可变之分的类对象了,避免出现意外的数据操作.
>>>>>>>拓展
修饰block为什么要用copy修饰?
关于block的用法,前几篇博客有,请关注:下面直接说原因:
(1)block内部没有调用外部局部变量时存放在全局区(ARC和MRC下均是)
(2)block使用了外部局部变量,这种情况也正是我们平时所常用的方式。MRC:Block的内存地址显示在栈区,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.所以在使用Block属性时使用copy修饰。但是ARC中的Block都会在堆上的,系统会默认对Block进行copy操作
(3)用copy,strong修饰block在ARC和MRC都是可以的,都是在堆区
下面继续讲解
4.指定方法名称: setter= getter=
今天到此结束(如果太多,可能大家一时接受不了),下一步,我可能继续讲解block造成的循环引用,@property与ivar的区别等,看@property引出的那些问题