2019-03-27 面试题整理
1. 属性修饰符
常用的属性修饰符有
atomic,nonatomic,strong,retain,weak,assign,unsafe_unretained,copy,readonly,readwrite
2. ARC下,不指定属性修饰符时,默认的是
- 基本数据类型:atomic readwrite assign
- 普通OC对象:atomic readwrite strong
3. 关于copy
和strong
可变对象 copy是深拷贝
不可变对象 copy是浅拷贝
mutableCopy 始终是深拷贝
3.1 为什么要用copy
修饰NSString/NSArray/NSDictory
因为使用copy来修饰不可变对象,可以保证安全
扩展:
copy
浅拷贝 不拷贝对象本身,仅仅是拷贝指向对象的指针(复制的对象和原对象都指向同一个地址)
mutableCopy
深拷贝 直接拷贝整个对象内存到另一块内存中
3.2 使用copy
去修饰NSMutableArray
会怎么样?
使用copy
修饰可变数组之后,数组初始化的时候,会执行copy
方法,生成的是一个不可变的数组,当执行[arr addObject:]
时会crash
4. atomic是否是绝对线性安全的
atomic
原子性,不是绝对线性安全的
@property (atomic, assign) NSInteger intA; //有一个atomic的属性,表示是原子的
- (void)viewDidLoad {
[super viewDidLoad];
//开启一个线程对intA的值+1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0;i < 1000;i ++){
self.intA = self.intA + 1;
}
NSLog(@"intA : %ld",(long)self.intA);
});
//开启一个线程对intA的值+1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0;i < 1000;i ++){
self.intA = self.intA + 1;
}
NSLog(@"intA : %ld",(long)self.intA);
});
}
错误分析:
因为intA
是atomic
修饰的,所以是线程安全的,在+1
的时候,只会有一个线程去操作,所以最终的打印结果必定有一个是2000
输出如下:
intA : 1186
intA : 896
分析:
其实atomic
是原子的是没问题的,这个只是表示set
方法是原子的,效果类似与下面的效果
//atomic表示的是对set方法加锁,表示在设置值的时候,只会有一个线程执行set方法
- (void)setIntA:(NSInteger)intA{
[self.lock lock];
_intA = intA;
[self.lock unlock];
}
只是对set
方法加锁,而我们代码里面的self.intA = self.intA + 1;
,这一部分不是线程安全的,正确的处理方法是:
[self.lock lock];
self.intA = self.intA + 1;
[self.lock unlock];
5. 进程与线程,堆和栈
一个程序至少有一个进程,一个进程至少有一个线程。同一个进程内的线程共享进程里的资源。
堆 由程序员分配释放,一般用来存放对象(ARC下会自动释放)
栈 由编译器自动分配释放,存放函数的参数值、局部变量的值等
6. UIButton
继承自什么?为什么?
UIButton
是一个可以响应事件的控件,因此它的直接父类是UIControl
,UIControl
的直接父类是UIView
。
UIButton
从父类UIControl
那继承了控制相关的方法,比如添加事件、移除事件等
7. 响应链与事件传递
UIResponder
响应者对象,只要继承自UIResponder
的类,才能处理事件。
UIApplication
、UIView
、UIViewController
都是继承自UIResponder
类,可以响应和处理事件。CALayer
不是UIResponder
的子类,无法处理事件。
事件的分发与传递:
- 当iOS程序中发生触摸事件后,系统会将事件加入到UIApplication管理的一个任务队列中
- UIApplication将处于任务队列最前端的事件向下分发。即UIWindow。
- UIWindow将事件向下分发,即UIView。
- UIView首先看自己是否能处理事件,触摸点是否在自己身上。如果能,那么继续寻找子视图。
- 遍历子控件,重复以上两步。
- 如果没有找到,那么自己就是事件处理者。如果
- 如果自己不能处理,那么不做任何处理。
其中UIView不接受事件处理的情况主要有以下三种
1. alpha <0.01
2. userInteractionEnabled = NO
3. hidden = YES.
响应者链:
响应链是从最合适的view开始传递,处理事件传递给下一个响应者,响应者链的传递方法是事件传递的反方法,如果所有响应者都不处理事件,则事件被丢弃。我们通常用响应者链来获取上几级响应者,方法是UIResponder
的nextResponder
方法。
8. 请写出下面这段代码的输出
NSString *str1 = [NSString stringWithFormat:@"hello"];
NSString *str2 = @"hello";
NSString *str3 = @"hello";
if (str1 == str2) {
NSLog(@"str1 = str2");
}
if (str2 == str3) {
NSLog(@"str2 = str3");
}
if ([str1 isEqualToString:str2]) {
NSLog(@"str1 isEqualToString:str2");
}
输出如下:
str2 = str3
str1 isEqualToString:str2
这里考察的是常量池相关的知识点
isEqualToString
比较的是两个字符串的内容;
而==
比较的是地址的引用;
这里str2 == str3
返回true
,主要是与常量池有关;在给str2赋值的时候,将hello一起放入了常量池中,当再次将hello赋值给str3的时候,先从常量池中查看是否存在hello的值,如果有,则直接取出。所以str2和str3指的是同一个引用,因此返回的结果自然是true
扩展:
iOS程序中的内存分为:堆区、栈区、全局区(静态区)、常量区、方法区