iOS面试题解析

  1. 分类和扩展有什么区别?可以分别用来做什么?分类有哪些局限性?分类的结构体里面有哪些成员?
    扩展主要是为类增加一些私有的属性和方法,只能供类内部调用。
    分类可以在不改变当前类的代码前提下,实现为类添加新的方法和属性
    局限性:分类为类添加的属性不会自动生成对应的成员变量来保存属性值,因此需要自己去实现方法保存和读取属性值。
    分类会在程序编译的时候生成对应的结构体,结构体中保存有分类新增的方法,属性等。
// 定义在objc-runtime-new.h文件中
struct category_t {
    const char *name; // 比如给Student添加分类,name就是Student的类名
    classref_t cls;
    struct method_list_t *instanceMethods; // 分类的实例方法列表
    struct method_list_t *classMethods; // 分类的类方法列表
    struct protocol_list_t *protocols; // 分类的协议列表
    struct property_list_t *instanceProperties; // 分类的实例属性列表
    struct property_list_t *_classProperties; // 分类的类属性列表
};

我们每创建一个分类,在编译时都会生成这样一个结构体并将分类的方法列表等信息存入这个结构体。在编译阶段分类的相关信息和本类的相关信息是分开的。等到运行阶段,会通过runtime加载某个类的所有Category数据,把所有Category的方法、属性、协议数据分别合并到一个数组中,然后再将分类合并后的数据插入到本类的数据的前面.
2.讲一下atomic的实现机制;为什么不能保证绝对的线程安全.
atomic对应nonatomic,都是声明属性时的修饰符。前置可以保证属性在getter和setter的时候是线程安全的,但并不能保证整个属性在多线程中是安全的。
其它线程依旧可以在读写之间进行别的操作来改变属性值。
3.KVO的底层实现?如何取消系统默认的KVO并手动触发(给KVO的触发设定条件:改变的值符合某个条件时再触发KVO)?
通过Objective-C强大的运行时(runtime)实现的。当你第一次观察某个object时,runtime会创建一个新的继承原先class的subclass。在这个新的class中,它重写了所有被观察的key,然后将object的isa指针指向新创建的class(这个指针告诉Objective-C运行时某个object到底是哪种类型的object)。所以object神奇地变成了新的子类的实例。
要取消自动触发,需要重写下面的方法

+ (BOOL)automaticallyNotifiesObserversForName
{ 
    return NO;
}

然后重写需要手动触发的属性的setter方法

- (void)setName:(NSString *)name
{
     if (_name == name) 
    { 
        return; 
    } 
    // 自动触发
    [self willChangeValueForKey:@"name"];
    _name == name; 
    // 自动触发
    [self didChangeValueForKey:@"name"];}

4.为什么在block外部使用__weak修饰的同时需要在内部使用__strong修饰?
1). __weak防止循环引用
2).在block内部用__strong,是为了防止在block执行结束前,block所引用的对象被释放,导致异常结果

  1. RunLoop的作用是什么?它的内部工作机制了解么?
    RunLoop可以理解为一种事件循环机制,不同的是不会导致死循环,会负责在有事件需要处理的时候唤醒程序,没事件处理的时候处于休眠。
    每个线程都有属于自己的Runloop,主线程的Runloop默认开启,子线程的Runloop需要手动开启。

6.AppDelegate如何瘦身?
一个比较好的办法就是用分类(category)

7.内存的5大区域
1).栈区(stack) 由编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的。优点是快速高效,缺点时有限制,数据不灵活。

2).堆区(heap) 由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收 ,比如在ios 中 alloc 都是存放在堆中。
优点是灵活方便,数据适应面广泛,但是效率有一定降低。

3).全局区(静态区) (static) 全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后由系统释放。

4).文字常量区 存放常量字符串,程序结束后由系统释放;

5).代码区 存放函数的二进制代码

8.静态库和动态库的区别
什么是库?
库就是程序代码的集合,将N个文件组织起来,是共享程序代码的一种方式。库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行。

静态库:这类库的名字一般是libxxx.a;利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。

动态库:这类库的名字一般是libxxx.so;相对于静态函数库,动态函数库在编译的时候 并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便

9.线程同步策略
互斥锁和读写锁: 提供对临界资源的保护,当多线程试图访问临界资源时,都必须通过获取锁的方式来访问临界资源。(临界资源:是被多线程共享的资源)当读写线程获取锁的频率 差别不大时,一般采用互斥锁,如果读线程访问临界资源的频率大于写线程,这个时候采用读写锁较为合适,读写锁允许多个读线程同时访问临界资源,读写线程必 须互斥访问临界资源。读写锁的实现采用了互斥锁,所以在读写次数差不多的情况下采用读写锁性能没有直接采用互斥锁来的高。
条件变量:提供线程之间的一种通知机制,当某一条件满足时,线程A可以通知阻塞在条件变量上的线程B,B所期望的条件已经满足,可以解除在条件变量上的阻塞操作,继续做其他事情。
信号量:提供对临界资源的安全分配。如果存在多份临界资源,在多个线程争抢临界资源的情况下,向线程提供安全分配临界资源的方法。如果临界资源的数量为1,将退化为锁

10.面向对象设计5大基本原则。
1)单一职责原则 :就一个类而言,应该仅有一个引起它变化的原因
2)开放-封闭原则:对扩展开放,对修改封闭
3)依赖倒转原则:抽象不应该依赖细节,细节应该依赖抽象。针对接口编程,不要对实现编程
4)里氏替换原则:子类型必须能完全替换父类型
5)接口隔离原则:使用多个专门的接口,而不是使用一个总的接口

你可能感兴趣的:(iOS面试题解析)