考察一个面试者基础咋样,基本上问一个 @property 就够了:
-
- @property 后面可以有哪些修饰符?
答 :
线程安全的: atomic, nonatomic
访问权限的: readonly, readwrite
内存管理(ARC) assign,strong(强引用,指针拷贝),weak(弱引用),copy(内容拷贝)
内存管理(MRC)assign, retain(强引用,指针拷贝),copy(内容拷贝)
- @property 后面可以有哪些修饰符?
-
- 什么情况使用 weak 关键字,相比 assign 有什么不同?
UI控件使用weak的原因:
- 什么情况使用 weak 关键字,相比 assign 有什么不同?
UI控件之所以可以使用弱指针,是因为控制器有强指针指向UIView
UIView 有强指针指向Subviews数组,数组中由强指针指向控件
代理必须使用weak的原因:
代理必须是weak,因为代理一般都是指向控制器,会造成循环引用,无法释放,造成内存泄露
weak 和 assign的区别:
相同点:
assign和weak不会牵扯到内存管理,不会增加引用计数
不同点:
assign 可修饰基本数据类型 也可修饰OC对象, 但如果修饰对象类型指向的是一个强指针, 当他指向的这个指针释放后, 他仍指向这块内存,必须手动给其置为nil, 否则就会产生野指针,如果还通过此指针操作那块内存,便会导致EXC_BAD_ACCESS错误,调用了已经释放的内存空间; 而weak 只能修饰OC对象, 且相对assign比较安全, 如果指向的对象消失了,那么他会自动置为nil,不会产生野指针.
在ARC,出现循环引用的时候,必须有一端使用weak
weak修饰的对象销毁的时候,指针会自动设置为nil
而assign不会,assign可以用于非OC对象,而weak必须用于OC对象
-
- 关键字copy和strong的区别,如何使用它们?
NSString、NSArray、NSDictionary常用copy,为什么不用strong?
- 关键字copy和strong的区别,如何使用它们?
strong是强引用,指向的是同一个内存地址,copy是内容拷贝,会另外开辟内存空间,指针指向一个不同的内存地址,copy返回的是一个不可变对象,如果使用strong修饰可变对象,那么对象就会有可能被不经意间修改,有时不是我们想要的,而copy不会发生这种意外。
copy、strong 和 可变、不可变类型, 先看下面这张图:
1) copy 和 strong 都可修饰不可变类型,但一般用copy
一般用copy修饰不可变的, 因为安全, 可以保证其封闭性.
因为用copy修饰,setter方法中会自动判断如果来源,如果是不可变的,那和Strong一样,进行浅拷贝,会增加其引用计数,如果是可变的那么就深拷贝,不会增加其引用计数. 所以如果如果项目中这样的不可变对象(比如NSString)多的话,当一定数量if判断消耗的时间累加起来就会影响性能.
所以,只需要记住一点,当你给你的不可变对象 赋值时, 如果来源是可变的,那么就用copy, 如果来源是不可变类型的,就用strong.
注:如果当strong修饰不可变的, 如果来源是不可变得,那么同上,没有问题. 如果来源是可变的时, 那么当源对象变化时,我们的不可变属性也会跟着变化,那么就破坏了其封闭性, 就不安全了.
2) 如果用 copy 修饰 可变类型 会出现什么问题?
Copy 修饰 可变的对象的话, 会生成一个不可变的NSCFConstantSting对象,赋值给可变属性,编译没问题, 调方法修改其内容时崩溃. unrecognized selector sent to instance
总结
1、copy 修饰 不可变的 要看赋值来源
(1)来源是可变的话, 会自动进行深拷贝, 来源对象的变化不会影响我们的不可变属性
(2)来源是不可变的话,那么就和strong一样大胆的指针拷贝,反正都不会改变.
2、copy 修饰可变的.
那么会生成一个不可变对象,赋值给可变属性,编译没问题,调方法修改其内容时会崩溃unrecognized selector sent to instance
3、 Strong修饰不可变的 也要看来源
(1)如果来源是不可变的, 那就没什么问题
(2)如果来源是可变的, 那么当源对象的内容发生改变时,我们的不可变属性的值也会发生改变,那么就破坏的其封闭性, 不安全.
4、strong修饰可变的 也要看来源
(1)如果来源是不可变的, 那么会直接报警告运行出错 unrecognized selector sent to instance
(2) 如果来源是可变的,那么没问题.
-
- 这个写法会出什么问题: @property (copy) NSMutableArray *array;
那么会生成一个不可变对象,赋值给可变属性,编译没问题,调方法修改其内容时会崩溃unrecognized
- 这个写法会出什么问题: @property (copy) NSMutableArray *array;
-
- 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。
这一套问题区分度比较大,如果上面的问题都能回答正确,可以延伸问更深入点的:
**具体步骤:
1.需声明该类遵从 NSCopying 协议
实现 NSCopying 协议。该协议只有一个方法:
- (id)copyWithZone:(NSZone *)zone;
注意:一提到让自己的类用 copy 修饰符,我们总是想覆写copy方法,其实真正需要实现的却是 “copyWithZone” 方法。
至于如何重写带 copy 关键字的 setter这个问题,
如果抛开本例来回答的话,如下:
- (void)setName:(NSString *)name {
//[_name release];
_name = [name copy];
}
参考答案-孙源博客
-
- @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的
1.@property的本质是什么
- @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的
@property的本质 = ivar (实例变量) + getter (取方法) + setter (存方法)
“属性”(property)有两大概念:实例变量(ivar)、存取方法(getter + setter)
2、ivar、 getter 、setter 是如何生成并添加到这个类中的
这是编译器自动合成的,通过@synthesize 关键字指定,若不指定,默认为@synthesize propertyName = _propertyName;若手动实现了getter/setter 方法,则不会自动合成。
现在编译器已经默认为我们添加了@synthesize propertyName = _propertyName;因此不再手动添加了,除非你真的要改变成员变量名字。
生成getter方法时,会判断当前属性名是否有“_”,比如声明属性为@property(nonatomic,copy)NSString *_name;那么所生成的成员变量名就会变成“_name”,如果我们要手动生成getter 方法,就要判断是否以“_”开头了。
-
- @protocol 和 category 中如何使用 @property
1) 在protocol中视同property只会生成setter getter方法声明,我们使用属性的目的是希望遵守我协议的对象能实现该属性
2) 在category使用@property也是只会生成setter,getter方法的声明,如果我们挣得需要给category增加属性的实现,需要借助于运行时的两个函数
objc_setAssociatedObject
objc_getAssociatedObject
-
- runtime 如何实现 weak 属性
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为
0 的时候会 dealloc,假如 weak 指向的对象内存地址是 a ,那么就会以 a 为键, 在这个 weak 表中搜索,找到所有以 a 为键的 weak
对象,从而设置为 nil 。
-
- weak属性需要在dealloc中置nil么?
不需要,在 ARC 环境下,无论是强指针还是弱指针都不需要在 dealloc 中置为 nil ,ARC 会自动处理的。
- 10.@synthesize和@dynamic分别有什么作用?
1. @property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;
2.@synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
3. @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
- 11.ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?
atomic,readwrite,strong(对象),assgin(基本数据类型)。
*12. 用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
这个问题无非就是考察你对copy、strong这个两个修饰符的理解。简单来讲,strong是强引用,仍旧指向同一个内存地址;copy是内容拷贝,会另外开辟一个内存空间来存储被拷贝的内容,指针指向了一个不同的内存地址。注意,copy返回的是一个不可变对象。如果用strong修饰可变对象,那么这个对象就会有可能在不经意间被修改,有时候这并不是我们的想要看到的,而用copy便不会有这种意外发生了。
*13. @synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?
@synthesize表示由编译器来自动实现属性的getter/setter方法,不需要你自己再手动去实现。默认情况下,不需要指定实例变量的名称,编译器会自动生成一个属性名前加“_”的实例变量。当然也可以在实现代码里通过@synthesize语法来指定实例变量的名字。比如@synthesize foo = oof(oof为你想要定义的名称)。
如果property名为foo并且存在一个名为_foo的实例变量,编译器便不会为我们自动合成新变量了。下面有一段代码大家可以试一下帮助更好的理解。
@interface ViewController : UIViewController
@property (copy, nonatomic) NSString *testA;
@property (copy, nonatomic) NSString *testB;
@end
@interface ViewController ()
{
NSString *_testA;
NSString *_testB;
}
@end
@implementation ViewController
@synthesize testB = testBBBBB;
- (void)viewDidLoad {
[super viewDidLoad];
self.testA = @"1111";
self.testB = @"1111";
//输出结果为:self.testA = 1111,_testA = 1111,self.testB = 1111,testBBBBB = 1111,_testB = (null)
NSLog(@"self.testA = %@,_testA = %@,self.testB = %@,testBBBBB = %@,_testB = %@",self.testA,_testA,self.testB,testBBBBB,_testB);
_testA = @"2222222";
_testB = @"2222222";
//输出结果为:self.testA = 2222222,_testA = 2222222,self.testB = 1111,_testB = 2222222,testBBBBB = 1111
NSLog(@"self.testA = %@,_testA = %@,self.testB = %@,_testB = %@,testBBBBB = %@",self.testA,_testA,self.testB,_testB,testBBBBB);
testBBBBB = @"333333";
//输出结果:self.testB = 333333,testBBBBB = 333333,_testB =2222222
NSLog(@"self.testB = %@,testBBBBB = %@,_testB =%@",self.testB,testBBBBB,_testB);
*14. objc中向一个nil对象发送消息将会发生什么?
在 Objective-C 中向 nil 发送消息是完全有效的——只是在运行时不会有任何作用:
(1). 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:
Person * motherInlaw = [[aPerson spouse] mother];
(2). 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回0。
(3). 如果方法返回值为结构体,发送给 nil 的消息将返回0。结构体中各个字段的值将都是0。
(4). 如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。
具体原因如下:
objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
那么,为了方便理解这个内容,还是贴一个objc的源代码:
// runtime.h(类在runtime中的定义)
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指针指向Meta Class,因为Objc的类的本身也是一个Object,为了处理这个关系,runtime就创造了Meta Class,当给类发送[NSObject alloc]这样消息时,实际上是把这个消息发给了Class Object
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存,对象接到一个消息会根据isa指针查找消息对象,这时会在method Lists中遍历,如果cache了,常用的方法调用时就能够提高调用的效率。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
总结: objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。那么,回到本题,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。
*15. KVO实现的原理:
Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。
下面做下详细解释:
键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就会记录旧的值。而当改变发生后, observeValueForKey:ofObject:change:context: 会被调用,继而 didChangeValueForKey: 也会被调用。可以手动实现这些调用,但很少有人这么做。一般我们只在希望能控制回调的调用时机时才会这么做。大部分情况下,改变通知会自动调用。
比如调用 setNow: 时,系统还会以某种方式在中间插入 wilChangeValueForKey: 、 didChangeValueForKey: 和 observeValueForKeyPath:ofObject:change:context: 的调用。大家可能以为这是因为 setNow: 是合成方法,有时候我们也能看到有人这么写代码:
- (void)setNow:(NSDate *)aDate {
[self willChangeValueForKey:@"now"]; // 没有必要
_now = aDate;
[self didChangeValueForKey:@"now"];// 没有必要
}
键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: