OC面试题

19 阅读4分钟

NSString属性,使用什么关键字修饰,使用copy和strong修饰,有什么区别?

因为 NSString 有可变子类:NSMutableString
copy 修饰符可以确保你的属性始终保存的是一个不可变副本,防止外部传入可变字符串后被意外修改。

@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end

NSMutableString *str = [NSMutableString stringWithString:@"Tom"];
Person *p = [Person new];
p.name = str;           // strong 修饰,p.name 指向 str
[str appendString:@"my"]; // 修改原字符串
NSLog(@"%@", p.name);   // 输出 Tommy ❌

因为把 str (NSMutableString) 赋值给了 name (NSString),name 属性用了strong,所以他不会copy 一份副本,而是增加了一个引用计数。所以单修改 str ,同样也么会把 name 修改掉

说说你对自动释放池的理解,它是什么时候释放的,为什么用__weak修饰的变量所指向的对象在释放时会自动把变量指针置为nil?

自动释放池(Autorelease Pool) 是 Objective-C 中用于延迟释放对象的一种机制。
它的核心目的是:

✅ 让对象在“稍后某个安全的时机”释放,而不是立即释放。

@autoreleasepool {
    NSString *str = [NSString stringWithFormat:@"hello %@", @"world"];
    // 此处 str 是 autoreleased 对象
}
// 离开 autoreleasepool 时,str 会被 release
本质上,这段代码会被翻译成
void *pool = objc_autoreleasePoolPush()
id obj = [[NSString alloc] initWithFormat:@"hello %@", @"world"];
[obj autorelease];
objc_autoreleasePoolPop(pool)

自动释放池什么时候释放

自动释放池在其作用域结束时(或主线程 RunLoop 进入休眠前)释放池中所有的对象。
当离开 @autoreleasepool 块时,自动调用 objc_autoreleasePoolPop()
内部会对池中所有对象执行一次 release

id 声明的对象有什么特性?

它是一个指向 任意 Objective-C 对象实例的指针
换句话说,只要是继承自 NSObject 的对象(或者任何 @interface 定义的类实例),id 都可以指向。
id → 编译时不检查,运行时解析;

Category 和 Extension 的区别

概念定义
Category(分类)已有类添加新的方法(实例方法或类方法),不需要访问源代码。
Extension(扩展)类的匿名分类(Unnamed Category) ,通常在 .m 文件中,用于为类添加私有属性、方法声明。

Category 和 Extension 都可以添加方法,但是 Extension 是声明方法,需要在原始的.m 文件中实现。
Category 不能添加成员变量,Extension 可以
Category 有单独的.h 和 .m 文件,Extension 没有。
Category 的方法是运行时添加,Extension 是编译期整合。
Category 的 +load 方法 和 原类的 +load 方法都会调用。
Category 的方法列表会整合到原类中,调用的是inser(0),插入到第一位,所以多个 Category 有多个同名的方法A,调用方法A,最后真正执行的方法体和多个Category编译的顺序有关。

你了解isa 指针吗?

  1. 作用

    • 指向对象所属类的 Class 对象;
    • runtime 通过 isa 找到类的方法列表、父类、协议等信息;
    • 是实现动态消息发送(objc_msgSend)的关键。
  2. 优化(现代 64 位 iOS):

    • isa 已经被 isa 压缩(isa_t / non-pointer isa) ,低位存储标志位:

      • 对象引用计数(retainCount)
      • 是否有关联对象(associated objects)
      • 是否是弱引用指针表(weak table)
      • 是否正在 dealloc
    • 真正指向 Class 的地址在高位。

  3. 类型

    • 32 位:直接指向 Class 对象
    • 64 位:pointer isa + flag bits(优化内存占用和访问效率)

NSObject 类对象和 实例对象的内存结构

对象内存布局示意:

内存区域说明
isa 指针← 指向 Class
父类实例变量← NSObject 本身通常没有 ivar
子类实例变量← 自定义属性/成员变量

类对象内存布局

内存区域说明
isa 指针← 指向元类(Meta-class)
superclass 指针← 指向父类的 Class 对象
方法列表method list
属性列表ivar list
协议列表protocols

谈谈对 OC 的 block 的理解

Block 是一种 封装了函数和捕获上下文的匿名函数对象

Objective-C 下 Block 主要有三种类型:

类型内存位置特点
NSGlobalBlock全局区不捕获外部变量,常驻内存,生命周期整个程序
NSStackBlock栈区捕获局部变量,生命周期随栈帧结束;默认局部 Block 都是栈 Block
NSMallocBlock堆区通过 [block copy] 或 ARC 自动 copy,安全跨栈使用

Block 本质上是一个 对象(结构体),简化结构如下:

struct __block_impl {
    void *isa;                     // 指向 Block 类
    int flags;                     // 标志位
    int reserved;                  // 保留
    void (*invoke)(void *, ...);   // 指向执行函数
    struct __block_descriptor *descriptor; // 元信息
};
-   **invoke**:Block 执行入口
-   **descriptor**:Block 的大小、copy、dispose 方法
-   **捕获变量**:按值或按引用存储在 Block 内部

成员变量必须要copy 修饰符
@property (nonatomic, copy) void (^completion)(void);
确保栈的block,copy 到堆上。