什么是属性?
属性=成员变量+set方法+get方法
我们在OC中用@property来声明一个属性,编译器会自动为你的实例变量生成setter和getter方法。添加实例变量是有一个前提的,就是对象还没有同名的成员变量,就是如果已经有_str了,就不再添加了。
实际上,一个类经过编译后,会生成变量列表ivar_list,方法列表method_list,每添加一个属性,在变量列表ivar_list会添加对应的变量,如_name,方法列表method_list中会添加对应的setter方法和getter方法。
也就是说我们每次在增加一个属性,系统都会在ivar_list中添加一个成员变量的描述,在method_list中增加setter与getter方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出setter与getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.
我们可以用运行时验证一下。
#import "ViewController.h"
#include <objc/runtime.h>
@interface ViewController ()
@property (nonatomic, strong) NSString *str;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
unsigned int count = 0;
Ivar *varList = class_copyIvarList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *varName = ivar_getName(varList[i]);
printf("成员变量-------%s\n",varName);
}
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
SEL methodName = method_getName(methodList[i]);
NSLog(@"方法-------%@",NSStringFromSelector(methodName));
}
}
@end
打印的日志:
成员变量-------_str
2018-07-13 10:32:09.115049+0800 TestAppIOS[445:214283] 方法-------str
2018-07-13 10:32:09.115161+0800 TestAppIOS[445:214283] 方法-------setStr:
2018-07-13 10:32:09.115233+0800 TestAppIOS[445:214283] 方法-------.cxx_destruct
2018-07-13 10:32:09.115264+0800 TestAppIOS[445:214283] 方法-------viewDidLoad
同时我们可以自定义属性的存取方法,如果我们定义了一个,编译器还是会为我们生成另一个。例如,我们自定义get方法, 编译器就会为我们生成set方法。如果我们同时自定义了属性的存取方法,编译器就不会为对象声明实例变量了。
@synthesize:
1.在MRC下,@synthesize str这样,编译器才会自动合成str的存取方法。不过在ARC下就不必了,无论你是否@synthesize str,编译器都会自动合成str的存取方法。
2.如果你声明的属性是str,系统自动给你添加的成员变量是_str,如果你对这个变量名字不满,可以这样@synthesize str = mystr;,自己给个名字。这样系统给添加的成员变量就是myStr,而不是_str,但是变量的存取方法没有变化。不过我建议最好不要这么办,因为都按照约定成俗的方式来命名变量,代码的可读性较高,大家都理解,所以我建议大家最好不要用这个关键字。
@dynamic:
@dynamic 关键字主要是告诉编译器不用为我们自动合成变量的存起方法, 我们会自己实现。即使我们没有实现,编译器也不会警告,因为它相信在运行阶段会实现。如果我们没有实现还调用了,就会报这个错误'-[ViewController setStr:]: unrecognized selector sent to instance 0x10040af10'。
如果我们在子类中重写父类的属性,就会报下面的警告
Autoproperty synthesis will not synthesize property'str';it will be implemented by its superclass,use @dynamicto acknowledge intention
因为我们同时在父类和子类中同时声明了str的属性,系统就不知道该在哪里(父类Father还是子类Son?)自动合成str的存取方法,系统默认是在父类中声明,因为子类可以调用。不过,系统希望我们显式的声明这一点,这样有利于提高代码的可读性。
属性关键字:
读写权限:readonly,readwrite
readwrite:读写。
readonly:只读。
属性默认都是readwrite的,表示可读可写,set/get方法编译器都会自动合成。如果用readonly修成属性,表示该属性是只读的,编译器只会自动合成get方法, 不会合成set方法。另外,我们可以在.h文件中用readonly中修饰属性,在.m文件类扩展中用readwrite修饰同一个属性,这样来防止外界篡改该属性。
原子性:nonatomic,atomic
nonatomic:非原子性。
atomic:原子性。
atomic 和 nonatomic 的区别在于,系统自动生成的 getter/setter 方法不一样。对于atomic的属性,系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能得到一个完好无损的对象。系统采用自旋锁,对资源进行保护。
然而,即使用atomic也不能保证绝对的线程安全。举例来说,假设有一个线程A在不断的读取属性name的值,同时有一个线程B修改了属性name的值,那么即使属性name是atomic,线程A读到的仍旧是修改后的值。
简单点说,只保证setter和getter的操作完整性,不保证属性的线程安全。
除了不能保证线程安全,atomic还会消耗系统资源,因此,开发iOS程序时一般都会使用 nonatomic 属性。
内存管理语义:strong,weak,assign,copy, retain,unsafe_unretained
有六个关键字,对象默认strong,基本数据类型默认assign。
assign
基本数据类型用 assign 修饰,如果assign修饰的对象,当对象废弃之后,对象会变为野指针,不知道指向哪,再向该对象发消息,非常容易崩溃。栈上空间的分配和回收都是系统来处理的,因此开发者无需关注,也就不会产生野指针的问题。对象是在堆上的,所以对象不能用。
strong
strong的对象会持有对象, 表示一种“拥有关系”。为属性设置新值的时候,设置方法会先保留新值(新值的引用计数加一),并释放旧值(旧值的引用计数减一),然后将新值赋值上去。
weak
weak不会持有对象,表示一种“非拥有关系”。用weak修饰属性的时候,为属性设置新值的时候,设置方法既不会保留新值(新值的引用计数加一),也不会释放旧值(旧值的引用计数减一)。当属性所指的对象释放的时候,属性也会被置为nil。用于修饰UI控件,代理(delegate)。
@property (nonatomic, weak) UIView *weakViewA;
@property (nonatomic, weak) UIView *weakViewB;
@property (nonatomic, strong) UIView *strongView;
- (void)viewDidLoad {
[super viewDidLoad];
self.weakViewA = [[UIView alloc] init];
NSLog(@"weakViewA = %@", self.weakViewA);
UIView *tempView = [[UIView alloc] init];
self.weakViewB = tempView;
NSLog(@"weakViewB = %@", self.weakViewB);
self.strongView = [[UIView alloc] init];
NSLog(@"strongView = %@", self.strongView);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touchesBegan-----weakViewA = %@", self.weakViewA);
NSLog(@"touchesBegan-----weakViewB = %@", self.weakViewB);
NSLog(@"touchesBegan-----strongView = %@", self.strongView);
}
打印结果:
copy
通常,可变对象属性修饰符使用strong,不可变对象属性修饰符使用copy。
copy修饰的属性设置新值的时候,当新值是不可变的,和strong是一模一样的。当新值是可变的(开头是NSMutable),设置方法不会保留新值(新值的引用计数加一),而是对新值copy一份,不会影响新值的引用计数。copy常用来修饰NSString,因为当新值是可变的,防止属性在不知不觉中被修改。
使用strong和copy时都会使引用对象引用计数+1。但是使用copy修饰的属性在某些情况下赋值的时候会创建对象的副本,也就是深拷贝。
使用场景:
一般情况下,copy可以用于对不可变容易的属性修饰中,主要是NSArray /NSDictionary/NSString, 也可以用来修饰block。
在MRC和ARC下都可以使用。
其setter方法,与retain处理流程一样,先旧值release,再copy出新的
retain
使用场景:
一般情况下,retain用在MRC情况下,被retain修饰的对象,引用计数retainCount要加1的。
retain只能修饰oc对象,不能修饰非oc对象,比如说CoreFoundation对象就是C语言框架,它没有引用计数,也不能用retain进行修饰。
retain一般用来修饰非NSString 的NSObject类和其子类。
unsafe_unretained
在iOS5.0后,基本被weak替代。但是weak会额外消耗资源(后面说到weak原理会提到),所以并不全优于unsafe_unretained。
unsafe_unretained,不安全的所有权修饰符,和weak一样不会持有对象。其修饰的指针又名悬垂指针,当指针指向的对象被废弃时,指针成为了野指针,处理不好会导致程序崩溃。这点和weak不一样,weak指针在对象废弃时,会置为nil。
和assing修饰对象的时候是一模一样的。为属性设置新值的时候,设置方法既不会保留新值(新值的引用计数加一),也不会释放旧值(旧值的引用计数减一)。唯一的区别就是当属性所指的对象释放的时候,属性不会被置为nil,这就会产生野指针,所以是不安全的
面试题:
1.属性关键字有哪些?
原子性--- atomic nonatomic特质
读/写权限--- readwrite(读写),readonly (只读)
内存管理语义--- assign,strong,weak,unsafe_unretained,copy
2. 如何让自定义类可以用 copy 修饰符?如何重写带 copy 关键字的 setter?
如果想让自己的类具备copy方法,并返回不可边类型,必须遵循nscopying协议,并且实现
- (id)copyWithZone:(NSZone *)zone
如果让自己的类具备mutableCopy方法,并且放回可变类型,必须遵守NSMutableCopying,并实现- (id)mutableCopyWithZone:(nullable NSZone *)zone
注意:再此说的copy对应不可边类型和mutableCopy对应不可边类型方法,都是遵从系统规则而已。如果你想实现自己的规则,也是可以的。
3.用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
众所周知,我们知道,可变类型(NSMutableArray,NSMutableString等)是不可边类型(NSString,NSArray等)的子类,因为多态的原因,我们可以使用不可边类型去接受可变类型。
1.当我们使用strong修饰A不可边类型的时候,并且使用B可变类型给A赋值,再去修改可变类型B值的时候,A所指向的值也会发生改变。引文strong只是让创建的对象引用计数器+1,并返回当前对象的内容地址,当我们修改B指向的内容的时候,A指向的内容也同样发生了改变,因为他们指向的内存地址是相同的,是一份内容。
2.当我们使用copy修饰A不可边类型的时候,并且使用B可变类型给A赋值,再去修改可变类型B值的时候,A所指向的值不会发生改变。因为当时用copy的修饰的时候,会拷贝一份内容出来,并且返回指针给A,当我们修改B指向的内容的时候,A指向的内容是没有发生改变的。因为A指向的内存地址和B指向的内存地址是不相同的,是两份内容
3.copy修饰不可边类型(NSString,NSArray等)的时候,且使用不可边类型进行赋值,表示浅拷贝,只拷贝一份指针,和strong修饰一样,当修饰的是可变类型(NSMutableArray,NSMutableString等)的时候,且使用可边类型进行赋值,表示深拷贝,直接拷贝新一份内容,到内存中。表示两份内
4.@synthesize 和 @dynamic 分别有什么作用?
如果@synthesize和@dynamic都没写,那么替代的就是@syntheszie var = _var;
@dynamic :修饰的属性,其getter和setter方法编译器是不会自动帮你生成。必须自己是实现的。
@synthesize:修饰的属性,其getter和setter方法编译器是会自动帮你生成,不必自己实现。且指定与属性相对应的成员变量。
5.为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?
代理是使用weak来修饰的。1.使用weak是为了避免循环引用。2.当使用weak修饰的属性,当对象释放的时候,系统会对属性赋值nil,object-c有个特性就是对nil对象发送消息也就是调用方法,不会cash。
delegate:传递的是事件(even),代理可以让A对象通知B对象,我(A)发生的变化,前提B遵循了A的代理,并且实现了A的代理方法。
dataSource: 传递的是数据。如果A对象声明了数据源,当我们创建A对象的时候,我们就该实现数据源,来告诉A,他所需要的一些数据。例如:tableView数据源方法,需要告诉它,我要实现几组cell,每组cell多少行cell,实现的cell什么样式,什么内容
相同点:代理和Block大多是我们都可以用来做倒序传值的。我们都得注意避免循环引用。不然我们去使用代理还是Block的时候,都需要判断它们是否实现
不同点:代理使用weak修饰,代理必须先声明方法。当我们调用代理的时候要判断是否已经实现。
block:使用的是copy来修饰,block保存的是一段代码,其实也就是一个函数。并且可以自动捕捉自动变量,如果想修改此自动变量,还必须使用__block修饰。
6.weak置为nil是如何实现的?
runtime对注册的类,会进行布局,对于弱对象会加入一个哈希表中。用弱指向的对象内存地址作为键,当此对象的引用计数为0的时候会dealloc,如果弱指向对象的内存地址是a,那么就会以a为键,在这个弱表中搜索,找到所有以a为键的弱对象,从而设置为nil。
runtime 如何实现 weak 属性具体流程大致分为 3 步:
1、初始化时:runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。
2、添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数,objc_storeWeak() 的作用是更新指针指向(指针可能原来指向着其他对象,这时候需要将该 weak 指针与旧对象解除绑定,会调用到 weak_unregister_no_lock),如果指针指向的新对象非空,则创建对应的弱引用表,将 weak 指针与新对象进行绑定,会调用到 weak_register_no_lock。在这个过程中,为了防止多线程中竞争冲突,会有一些锁的操作。
3、释放时:调用 clearDeallocating 函数,clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数组,然后遍历这个数组把其中的数据设为 nil,最后把这个 entry 从 weak 表中删除,最后清理对象的记录
7.@protocol 和 category 中如何使用 @property?
在协议中使用属性只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性
类别使用@property也是也会生成setter和getter方法的声明,如果我们真的需要给类别增加属性的实现,需要生成运行时的两个函数:
objc_setAssociatedObject
objc_getAssociatedObject
8.assign可以修饰OC对象吗?
不可以
9.属性修饰符atomic的内部实现是怎么样的?能保证线程安全吗?
10.SideTable的结构是什么样的?最好可以画出来?
11.@peoperty的本质是什么?ivar getter setter是如何生成并添加到类中?
@property = ivar + getter + setter;
大致生成了五个东西
OBJC_IVAR_$类名$属性名称 :该属性的“偏移量”(偏移量),这个替换量是“硬编码”(硬编码),表示该变量距离放置对象的内存区域的起始地址有多远。
setter与getter方法对应的实现函数
ivar_list :成员变量列表
method_list :方法列表
prop_list :属性列表
也就是说我们每次在增加一个属性,系统都会在ivar_list中添加一个成员变量的描述,在method_list中增加setter与getter方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的接近量,然后将setter与getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,以便能够读取正确字节数,系统对象转变量的指针类型进行了类型强转
12.数组copy后里面的元素会复制一份新的吗?
不会,数组里面存的是之前对象的地址,不会改变,可以自己测试一下
13.如何访问私有成员变量?
通过runtime来获取对象的成员变量Ivar,然后再通过object_getIvar来获取某个对象的成员变量的值
Ivar userNameIvar = class_getInstanceVariable([model class], "_userName");
NSString *userName = object_getIvar(model, userNameIvar);
14.什么情况使用 weak 关键字,相比 assign 有什么不同?
在ARC中,在有可能出现循环引用的时候,经常要通过让其中一端使用
自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用弱,自定义IBOutlet中控件属性一般也使用弱;
assign: 一般用来修饰基本的数据类型,包括基础数据类型和C数据类型。assign声明的属性是不会增加引用计数的,声明的属性释放后,属性就不存在了。但是,指针没有置空过程,成为了野指针,如果新的对象被分配到了这个内存地址上,又会crash,所以一般只用来声明基本的数据类型。
weak:弱引用,不增加引用计数。防止循环引用时使用,并且在所指向的对象被释放之后,weak指针会被设置的为nil。weak引用通常是用于处理循环引用的问题,如代理及block的使用中,相对会较多的使用到weak。
只可以修饰对象。如果修饰基本数据类型,编译器会报错-“Property with ‘weak’ attribute must be of object type”。
当属性用weak修饰时,会将该指针存入一个weak表,该表是一个hash表,obj为键值,存储了一组obj_weak的地址。当属性使用weak修饰时,性能开销较大。
15.怎么用 copy 关键字?
NSString,NSArray,NSDictionary等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString,NSMutableArray,NSMutableDictionary;
块也经常使用copy关键字,
block使用copy是从MRC遗留下来的“传统”,在MRC中,方法内部的block是在栈区的,使用copy可以把它放到堆区。
在ARC中写不写都行:
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上
16. 这个写法会出什么问题: @property (copy) NSMutableArray *array?
两个问题:1,添加,删除,修改复制内部的元素的时候,程序会因为找不到对应的方法而崩溃。因为copy就是复制一个不可变NSArray的对象; 2,使用了atomic属性会严重影响性能
18.@property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?默认的是哪些
属性是描述类的特征,也就是具备什么特性。三个部分,带下划线的成员变量,get、setter方法。
默认关键字:readwrite,assign, atomic; -- 是针对基本类型(NSInteger, BOOL, NSUInteger, int, 等)
但是针对引用类型, 默认:strong, readwrite, atomic (例如:NSString, NSArray, NSDictory等)
19. @synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?
实例变量=成员变量= ivar
在类的实现代码里通过@synthesize语法来指定实例变量的名字:
@实现CYLPerson @synthesize firstName = _myFirstName; @synthesize lastName = _myLastName; @结束
语法上述会将生成的实例变量命名为_myFirstName与_myLastName,而不再使用默认的名字
总结下@synthesize合成实例变量的规则,有以下几点:
1. 如果指定了成员变量的名称,会生成一个指定的名称的成员变量,
2. 如果这个成员已经存在了就不再生成了。
3. 如果是@synthesize foo;即将生成一个名称为foo的成员变量,则:
如果没有指定成员变量的名称会自动生成一个属性同名的成员变量,
4. 如果是@synthesize foo = _foo;就不会生成成员变量了。
20. 在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?
我们要搞清楚一个问题,什么情况下不会autosynthesis(自动合成)?
1. 同时重组了setter和getter时
2. 重建了预期属性的getter时
3. 使用了@dynamic时
4. 在@protocol中定义的所有属性
5. 在类别中定义的所有属性
6. 重写(overridden)的属性
21. 讲一下atomic的实现机制;为什么不能保证绝对的线程安全(最好可以结合场景来说)?
atomic会对属性的setter/getter方法进行加锁,这仅仅只能保证在操作setter/getter方法是安全的。不能保证其他线程的安全
例如:线程1调用了某一属性的setter方法并进行到了一半,线程2调用其getter方法,那么会执行完setter操作后,再执行getter操作,线程2会获取到线程1setter后的完整的值;当几个线程同时调用同一属性的setter、getter方法时,会获取到一个完整的值,但获取到的值不可控
22.被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?
23.关联对象有什么应用,系统如何管理关联对象?其被释放的时候需要手动将所有的关联对象的指针置空么?
24. assign可以修饰OC对象吗?
25.具体说一下循环强引用,可以吗?
26. 属性和成员变量的区别?
属性对成员变量扩充了存取方法 .
属性默认会生成带下划线的成员变量 .
但只声明了变量,是不会有属性的,
27.深copy和浅copy的区别?
浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
copy方法:如果是非可扩展类对象,则是浅拷贝。如果是可扩展类对象,则是深拷贝。
mutableCopy方法:无论是可扩展类对象还是不可扩展类对象,都是深拷贝。
[immutableObject copy] //浅复制
[immutableObject mutableCopy] //深复制
[mutableObject copy] //深复制
[mutableObject mutableCopy] //深复制
28.如何访问并修改一个类的私有属性?
有两种方法可以访问私有属性,一种是通过KVC获取,一种是通过runtime访问并修改私有属性
29.MRC下如何重写retain修饰变量的setter方法?
@property(nonatiomic,retain)id obj;
-(void)setObj:(id)obj{
if(_obj != obj){
[_obj release];
_obj = [obj retain];}}
30.