@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

上述代码就是手动创建变量的gettersetter的实现,gettersetter本质就是符合一定命名规范的实例方法。

具体使用

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允许使用点语法来访问gettersetter

合成使用,使用@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引出的那些问题

posted @ 2018-05-06 16:36  国孩  阅读(5552)  评论(0编辑  收藏  举报