2019-03-27 面试题整理

1. 属性修饰符

常用的属性修饰符有

atomic,nonatomic,strong,retain,weak,assign,unsafe_unretained,copy,readonly,readwrite

2. ARC下,不指定属性修饰符时,默认的是

  1. 基本数据类型:atomic readwrite assign
  2. 普通OC对象:atomic readwrite strong

3. 关于copystrong

可变对象 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);
   });   
}

错误分析:
因为intAatomic修饰的,所以是线程安全的,在+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是一个可以响应事件的控件,因此它的直接父类是UIControlUIControl的直接父类是UIView
UIButton从父类UIControl那继承了控制相关的方法,比如添加事件、移除事件等

7. 响应链与事件传递

https://raw.githubusercontent.com/WuOtto/imgSrc/master/iOS_DefaultResponderChain.png

UIResponder响应者对象,只要继承自UIResponder的类,才能处理事件。

UIApplicationUIViewUIViewController都是继承自UIResponder类,可以响应和处理事件。CALayer不是UIResponder的子类,无法处理事件。

事件的分发与传递:

  1. 当iOS程序中发生触摸事件后,系统会将事件加入到UIApplication管理的一个任务队列中
  2. UIApplication将处于任务队列最前端的事件向下分发。即UIWindow。
  3. UIWindow将事件向下分发,即UIView。
  4. UIView首先看自己是否能处理事件,触摸点是否在自己身上。如果能,那么继续寻找子视图。
  5. 遍历子控件,重复以上两步。
  6. 如果没有找到,那么自己就是事件处理者。如果
  7. 如果自己不能处理,那么不做任何处理。
    其中UIView不接受事件处理的情况主要有以下三种
1. alpha <0.01
2. userInteractionEnabled = NO
3. hidden = YES.

响应者链:

响应链是从最合适的view开始传递,处理事件传递给下一个响应者,响应者链的传递方法是事件传递的反方法,如果所有响应者都不处理事件,则事件被丢弃。我们通常用响应者链来获取上几级响应者,方法是UIRespondernextResponder方法。

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程序中的内存分为:堆区、栈区、全局区(静态区)、常量区、方法区

原文地址:https://www.cnblogs.com/wuotto/p/10616630.html