@property详解,@property修饰符以及各个修饰符区别(上)

相信很多参加过面试的人员很多都会被问到: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:];
//函数调用name和age的getter,输出 Jiaming Chen 22
NSLog(@"%@ %ld", [p name], [p age]);
}
return ;
} //另一种写法
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
//使用点语法访问name的setter
p.name = @"Jiaming Chen";
//使用点语法访问age的setter
p.age = ;
//使用点语法访问name和age的getter,输出 Jiaming Chen 22
NSLog(@"%@ %ld", p.name, p.age);
}
return ;
}

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);

打印结果:

-- ::51.209346+ copy[:]
string = 0x100001040
copystring = 0x100001040
mutablecopystring = 0x10058c8e0
copyMutableString = 0x100001040
mutableCopyMutableString = 0x10058cde0
Program ended with exit code:

小结论:在字符串是直接赋值的,是否生成新对象是和=右边相关的,如果=右边的是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);

打印结果:

-- ::44.755398+ copy[:]
string = 0x100504600
copystring = 0x100554e10
mutablecopystring = 0x100555880
copyMutableString = 0x100506e40
mutableCopyMutableString = 0x1005558f0
Program ended with exit code:

小结论:只要=右边从创建到赋值,至少包含一个MSMutable便会重新创建生成一个对象。

其他对象NSArray、NSMutableArray 、NSDictionary、NSMutableDictionary、NSSet、NSMutableSet一样适用。

@property详解,@property修饰符以及各个修饰符区别(上)

@property详解,@property修饰符以及各个修饰符区别(上)

刚刚疏解完copy,下面看一下面试最喜欢问的strong和copy修饰的区别:

下面看一组例子:

以NSString为例说明下,首先定义以下属性。

 @property (nonatomic, strong) NSString *strongString;
@property (nonatomic, copy) NSString *copyedString;
@property (nonatomic, strong) NSMutableString *strongMutableString;
@property (nonatomic, copy) NSMutableString *copyedMutableString;

2.1 当外部赋给对应属性一个不可变(非mutable)的字符串 NSString

 - (void)testPropertyCopyOrStrong
{
NSString *string = [NSString stringWithFormat:@"abc"];
self.strongString = string;
self.strongMutableString = string;
self.copyedString = string;
self.copyedMutableString = string;
string = [string stringByReplacingOccurrencesOfString:@"c" withString:@""]; NSLog(@"\n origin string: %p, %p %@ %@", string, &string, string, NSStringFromClass([string class]));
NSLog(@"\n strong string: %p, %p %@ %@", _strongString, &_strongString, _strongS  tring, NSStringFromClass([_strongString class]));
NSLog(@"\n strongMutable string: %p, %p %@ %@", _strongMutableString, &_strongMutableSt  ring, _strongMutableString, NSStringFromClass([_strongMutableString class]));
NSLog(@"\n copy string: %p, %p %@ %@", _copyedString, &_copyedString, _copyedS  tring, NSStringFromClass([_copyedString class]));
NSLog(@"\n copyMutable string: %p, %p %@ %@", _copyedMutableString, &_copyedMutableSt  ring, _copyedMutableString, NSStringFromClass([_copyedMutableString class])); }

打印结果:

 origin        string: 0x103a74098, 0x7fff5c18ca88  ab233  __NSCFString
strong string: 0xa000000006362613, 0x7f84c9f056d8 abc NSTaggedPointerString
strongMutable string: 0xa000000006362613, 0x7f84c9f056e8 abc NSTaggedPointerString
copy string: 0xa000000006362613, 0x7f84c9f056e0 abc NSTaggedPointerString
copyMutable string: 0xa000000006362613, 0x7f84c9f056f0 abc NSTaggedPointerString

可能大家不是很看懂这个例子:我们换一个简单的操作:

首先在类延展中声明两个属性变量:

 @property (nonatomic, strong)NSString * stringStrong;   //strong修饰的字符串对象
@property (nonatomic, copy)NSString * stringCopy; //copy修饰的字符串对象

接着创建两个不可变字符串(NSString)

 //新创建两个NSString对象
NSString * strong1 = @"I am Strong!";
NSString * copy1 = @"I am Copy!";

将这两个属性进行赋值

 //初始化两个字符串
self.stringStrong = strong1;
self.stringCopy = copy1;

分别打印四个变量的地址

   StrongOrCopy[:] strong1 = 0x10a0b3078
StrongOrCopy[:] stringStrong = 0x10a0b3078
//这是两个字符串

 StrongOrCopy[:] copy1 = 0x10a0b3098 
 StrongOrCopy[:] stringCopy = 0x10a0b3098 

结果发现:可以看出,无论是strong修饰的字符串还是copy修饰的字符串,都进行了浅拷贝(仅仅是多了个指向该内存的指针,地址不会发挥变化)

如果创建两个不可变字符串对象(NSMutableString)呢

 //新创建两个NSMutableString对象
NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"];
NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"];

分别对属性再次赋值

self.stringStrong = mutableStrong;
self.stringCopy = mutableCopy;

打印结果:

  StrongOrCopy[:] mutableStrong = 0x7fccba425d60
StrongOrCopy[:] stringStrong = 0x7fccba425d60
StrongOrCopy[:] mutableCopy = 0x7fccba40d7c0
StrongOrCopy[:] stringCopy = 0x7fccba4149e0

结果发现:这时就发现了,用strong修饰的字符串依旧进行了浅Copy,而由copy修饰的字符串进行了深Copy,所以mutableStrong与stringStrong指向了同一块内存,而mutableCopy和stringCopy指向的是完全两块不同的内存。

看了一些实例,有什么用呢,看如下:

 //新创建两个NSString对象
NSString * strong1 = @"I am Strong!";
NSString * copy1 = @"I am Copy!"; //初始化两个字符串
self.stringStrong = strong1;
self.stringCopy = copy1; //两个NSString进行操作
[strong1 stringByAppendingString:@""];
[copy1 stringByAppendingString:@""];

结果如下:

  StrongOrCopy[:] strong1 = I am Strong!
StrongOrCopy[:] stringStrong = I am Strong!
StrongOrCopy[:] copy1 = I am Copy!
StrongOrCopy[:] stringCopy = I am Copy!

分别对在字符串后面进行拼接,当然这个拼接对原字符串没有任何的影响,因为不可变自字符串调用的方法都是有返回值的,原来的值是不会发生变化的.打印如下,对结果没有任何的影响:(不可变的字符串)

然后是对可变字符串进行操作:

 //新创建两个NSMutableString对象
NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"];
NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"]; //初始化两个字符串
self.stringStrong = mutableStrong;
self.stringCopy = mutableCopy; //两个MutableString进行操作
[mutableStrong appendString:@"Strong!"];
[mutableCopy appendString:@"Copy!"];

打印结果如下:

  StrongOrCopy[:] stringStrong = StrongMutableStrong!
StrongOrCopy[:] mutableStrong = StrongMutableStrong!
StrongOrCopy[:] stringCopy = CopyMutable
StrongOrCopy[:] 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引出的那些问题

上一篇:Codevs 1299 切水果


下一篇:如何用MarsEdit快速插入源代码