《出一套iOS高级面试题》基础题 解答

Author Avatar
killua167 7月 25, 2018

J_Knight_大神的《出一套 iOS 高级面试题》基础题 解答

原文

1.分类和扩展有什么区别?可以分别用来做什么?分类有哪些局限性?分类的结构体里面有哪些成员?

类扩展(extension)能为某个类附加额外的私有的属性,成员变量,方法声明,一般写在.m文件中。
分类(category)作为是在不修改原来类的基础上,为一个类扩展方法。通常为系统类的添加额外的方法。
分类局限性:

  1. 不能扩展属性和成员变量,如果添加属性,则只会生成setter和getter的声明,不会生成实现和成员变量;
  2. 分类中的方法和原有类中方法同名,会覆盖原来的方法。
  3. 如果多个category中存在同名的方法,运行时到底调用哪个方法由编译器决定,最后一个参与编译的方法会被调用。

分类的结构体:

typedef struct category_t {
    const char *name;  //类的名字
    classref_t cls;  //类
    struct method_list_t *instanceMethods;  //category中所有给类添加的实例方法的列表
    struct method_list_t *classMethods;  //category中所有添加的类方法的列表
    struct protocol_list_t *protocols;  //category实现的所有协议的列表
    struct property_list_t *instanceProperties;  //category中添加的所有属性
} category_t;

2.讲一下atomic的实现机制;为什么不能保证绝对的线程安全(最好可以结合场景来说)?

系统自动生成的getter/setter方法会进行加锁操作。
atomic的加锁只保证getter和setter存取方法的线程安全,不能保证线程之外的其他操作。(比如:如果当一个线程正在get或set时,又有另一个线程同时在进行release操作,可能会直接crash)

3.被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?

释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
sideTable结构

struct SideTable {
// 保证原子操作的自旋锁
    spinlock_t slock;
    // 引用计数的 hash 表
    RefcountMap refcnts;
    // weak 引用全局 hash 表
    weak_table_t weak_table;
}

4.关联对象有什么应用,系统如何管理关联对象?其被释放的时候需要手动将所有的关联对象的指针置空么?

关联对象的应用:

  1. 添加公共属性。当需要在分类中添加属性,可以用到runtime关联对象;
  2. 添加私有成员变量。如为UI控件关联事件Block体
  3. 关联观察者对象。有时候我们在分类中使用NSNotificationCenter或者KVO,推荐使用关联的对象作为观察者,尽量避免对象观察自身

当对象被释放时,会根据这个策略来决定是否释放关联的对象,当策略是RETAIN/COPY时,会释放(release)关联的对象,当是ASSIGN,将不会释放。
值得注意的是,我们不需要主动调用removeAssociated来接触关联的对象,如果需要解除指定的对象,可以使用setAssociatedObject置nil来实现。

5.KVO的底层实现?如何取消系统默认的KVO并手动触发(给KVO的触发设定条件:改变的值符合某个条件时再触发KVO)?

系统KVO的实现:
当你观察一个对象时,一个新的类会动态被创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。自然,重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象值的更改。最后把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。

6.Autoreleasepool所使用的数据结构是什么?AutoreleasePoolPage结构体了解么?

双向链表。

AutoreleasePoolPage结构体:

class AutoreleasePoolPage 
{
    static size_t const SIZE = PAGE_MAX_SIZE;
    magic_t const magic;                   // 16字节
    id *next;                              // 8字节
    pthread_t const thread;                // 8字节
    AutoreleasePoolPage * const parent;    // 8字节 
    AutoreleasePoolPage *child;            // 8字节
    uint32_t const depth;                  // 4字节
    uint32_t hiwat;                        // 4字节
    ...
}
#define PAGE_MAX_SIZE           PAGE_SIZE
#define PAGE_SIZE              I386_PGBYTES
#define I386_PGBYTES            4096

7.讲一下对象,类对象,元类,跟元类结构体的组成以及他们是如何相关联的?为什么对象方法没有保存的对象结构体里,而是保存在类对象的结构体里?

对象结构体:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

这里的isa 指针,指向的是当前实例的类对象
类对象结构体:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE; // 父类
    const char * _Nonnull name                               OBJC2_UNAVAILABLE; //类名
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE; //实例大小
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;  //成员变量列表
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE; //方法列表
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE; //方法缓存
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE; //协议列表
#endif

}

这里的isa 指针,指向的就是元类
所有的对象调用方法都是一样的,没有必要存在对象中,对象可以有无数个,类对象就有一个所以只需存放在类对象中就可以了。

8.class_ro_t 和 class_rw_t 的区别?

ro标识readonly只读的不可变,class_rw_t rw标识readwrite可读可写

9.iOS 中内省的几个方法?class方法和objc_getClass方法有什么区别?

内省的几个方法:
评估继承关系

  • 使用类和超类的方法
  • 使用isKindOfClass:方法
    方法实现和协议遵循
  • 使用respondsToSelector:方法
  • 使用conformsToProtocol:方法
    对象的比较
  • 使用isEqual:方法
  • 重载isEqual:方法

class方法和objc_getClass方法区别:
调用class方法不能得到isa的指向链,而通过objc_getClass方法,经过多次调用,可以获得isa的类甚至元类。

10.在运行时创建类的方法objc_allocateClassPair的方法名尾部为什么是pair(成对的意思)?

创建class和meta-class

11.一个int变量被__block修饰与否的区别?

int变量被block修饰后能在block修改变量的值。
本质:
block的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。

12.为什么在block外部使用weak修饰的同时需要在内部使用strong修饰?

weak修饰是防止循环引用,在block里面用strong修饰是保证对象不会被释放

13.RunLoop的作用是什么?它的内部工作机制了解么?(最好结合线程和内存管理来说)

RunLoop是保证线程不会退出,并且能在不处理消息的时候让线程休眠,节约资源,在接收到消息的时候唤醒线程做出对应处理的消息循环机制。

14.哪些场景可以触发离屏渲染?(知道多少说多少)

  • shadows(阴影)
  • shouldRasterize(光栅化)
  • masks(遮罩)
  • edge antialiasing(抗锯齿)
  • group opacity(不透明)
  • 复杂形状设置圆角等…
  • 渐变
  • UILabel, CATextLayer, Core Text……