美文网首页
iOS 高级面试题--含答案

iOS 高级面试题--含答案

作者: lukyy | 来源:发表于2025-04-02 15:55 被阅读0次
分类和扩展的区别?

分类(Category):主要用于扩展功能,但不能增加实例变量,方法对所有人可见。
扩展(Extension):主要用于类的内部实现,可以添加实例变量,方法和属性仅限本类使用。

/*
 * Extension(扩展)是一种特殊的分类,它没有名称,通常用于声明:私有方法和属性。
 * 匿名分类(Anonymous Category)
 通常在 .m 文件中声明,不对外暴露。
 适用于隐藏内部实现细节,提高封装性和安全性
 */
// 扩展(匿名分类)
@interface People () <SomePrivateProtocol>
// 私有属性
@property (nonatomic, copy) NSString *privateID;
// 私有方法
- (void)privateMethod;
// 对内读写
@property (nonatomic, copy, readwrite) NSString *identifier;
@end
主要区别
特性 分类(Category) 扩展(Extension)
可见性 公开的 私有的(通常用于类实现文件)
声明位置 单独.h/.m文件 与类实现同一文件(.m)
添加方法 可以 可以
添加属性 只能添加关联对象 可以添加实例变量和属性
加载时机 运行时 编译时
命名 必须有名称 匿名
分类:的用途
  1. 为现有类添加新方法
  2. 将大型类分解为多个文件
  3. 实现非正式协议
  4. 为类添加默认实现(通过+load方法)
扩展:的用途
  1. 声明私有方法\属性
  2. 重新声明公有属性为可读写
  3. 遵循的私有协议
分类的局限性
  1. 不能添加实例变量:分类只能通过关联对象(Associated Object)来模拟属性
  2. 方法名冲突:如果分类方法与原类方法同名,分类方法会覆盖原类方法
  3. 属性限制:分类中声明的属性不会自动生成getter/setter方法和实例变量
  4. 加载顺序不确定:多个分类的方法加载顺序不确定
分类的结构体成员

这个结构体在运行时被用来将分类的内容附加到主类上。

struct category_t {
    const char *name;  // 分类名称
    classref_t cls;    // 所属类
    struct method_list_t *instanceMethods;  // 实例方法列表
    struct method_list_t *classMethods;     // 类方法列表
    struct protocol_list_t *protocols;      // 实现的协议列表
    struct property_list_t *instanceProperties; // 属性列表(不会自动合成)
    // ......
}
讲一下atomic的实现机制;为什么不能保证绝对的线程安全(最好可以结合场景来说)?

atomic是property的修饰词之一,表示是原子性的,使用方式为@property(atomic)int age;,此时编译器会自动生成getter/setter方法,最终会调用objc_getProperty和objc_setProperty方法来进行存取属性。进行枷锁PropertyLocks处理。
atomic在getter/setter方法中加锁,只能保证一个函数是安全的,不能保证读/取同时安全。

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

1.对象被释放的时候,会把weak指针自动置位nil。
2.runTime会把对weak修饰的对象放到一个全局的哈希表中(指针引用计数表),用weak修饰的对象的内存地址为key,weak指针为值,在对象进行销毁时,用通过自身地址去哈希表中查找到所有指向此对象的weak指针,并把所有的weak指针置位nil。(内存key、指针value)
3.sideTable的结构

struct SideTable {
    spinlock_t slock;     //操作SideTable时用到的锁
    RefcountMap refcnts;  //引用计数器的值
    weak_table_t weak_table;//存放weak指针的哈希表
};
iOS中关联对象有什么应用?系统如何管理关联对象?其被释放的时候需要手动将所有的关联对象的指针置空么?

1.关联对象(Associated Objects) 是一种动态为类添加属性的机制,通过 Objective-C 的运行时特性实现。
2.不需要手动置空,因为在对象的dealloc中,若发现对象有关联对象时,会调用_object_remove_assocations方法来移除所有的关联对象,并根据内存策略,来判断是否需要对关联对象的值进行release。

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

Autoreleasepool是由多个AutoreleasePoolPage以双向链表的形式连接起来的,

class AutoreleasePoolPage {
    magic_t const magic;
    id *next;//下一个存放autorelease对象的地址
    pthread_t const thread; //AutoreleasePoolPage 所在的线程
    AutoreleasePoolPage * const parent;//父节点
    AutoreleasePoolPage *child;//子节点
    uint32_t const depth;//深度,也可以理解为当前page在链表中的位置
    uint32_t hiwat;
}
使用method swizzling要注意什么?

1.在 Swizzling 之前,使用 class_getInstanceMethod 或 class_getClassMethod 检查目标方法和原始方法是否存在。如果方法不存在,直接跳过 Swizzling,避免运行时崩溃。
2.使用 dispatch_once 确保 Swizzling 只执行一次,防止多次交换导致逻辑混乱。(多人协作开发,可能同时使用了Swizzling)
3.苹果可能拒绝调用私有 API 的应用上架。如果必须使用,需做好动态检查和回退。

static和const有什么区别?
1. const(常量)
  • 作用:变量值不可修改,通常用于定义全局常量或局部常量。
  • 特点
    const 修饰的变量在编译时就必须初始化,且之后不能重新赋值。
    可以修饰全局变量、局部变量、函数参数等。
    在 Objective-C 中,const 通常与 staticextern 结合使用。
2. static(静态变量)
  • 作用
    函数/方法内部 使用时,使变量的生命周期延长到整个程序运行期间(只会初始化一次)。
    全局作用域 使用时,限制变量的作用域仅在当前文件(避免全局变量被其他文件访问)。
  • 使用场景
    用于函数内部的静态变量(保持值的持久性)。
    用于限制全局变量的作用域(文件私有全局变量)。

示例(Objective-C):

// 文件内静态全局变量(其他文件无法访问)
static NSString *staticGlobalVar = @"Static";
// 函数内部的静态变量
- (void)exampleMethod {
    static int count = 0; // 只会初始化一次
    count++;
    NSLog(@"Count: %d", count);
}

示例(Swift):static 用于类型属性(类/结构体/枚举的静态成员):

class MyClass {
    static let staticValue = 123 // 类常量
    static var staticVariable = "ABC" // 类变量
}
3. staticconst 结合使用

在 Objective-C 中,staticconst 经常一起使用,表示一个 文件内部的静态常量(不可修改,且仅当前文件可见)。

示例(Objective-C):

// 当前文件私有的常量
static const CGFloat AnimationDuration = 0.3;
static NSString *const MyPrivateConstant = @"Secret";
关键区别总结:
关键字 作用 生命周期 作用域 是否可修改
const 定义常量 取决于声明位置 取决于声明位置 ❌ 不可修改
static 延长变量生命周期或限制作用域 整个程序运行期 当前文件或函数内部 ✅ 可修改(除非同时用 const
常见用途:
  • const:定义不可变的常量(如 API 密钥、固定数值等)。
  • static
    函数内部的持久化变量(如计数器)。
    文件私有的全局变量或常量(避免命名冲突)。
了解内联函数么?

内联函数是为了减少函数调用的开销,编译器在编译阶段把函数体内的代码复制到函数调用处。
内联函数通过 inline 关键字(或编译器特性)提示编译器将函数体直接插入调用处,而非生成传统的函数调用指令。

什么时候会出现死锁?如何避免?

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。 发生死锁的四个必要条件:

1.互斥条件:一个资源每次只能被一个线程使用。
2.请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺。
4.循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
只要上面四个条件有一个条件不被满足就能避免死锁

二叉树题:如何反转二叉树?如何验证两个二叉树是完全相等的?

反转二叉树:(也称为镜像二叉树)是指将二叉树的每个节点的左右子树互换。
验证两个二叉树是否完全相同,包括结构和节点值(两种方式):
1.递归实现
2.迭代实现(使用栈)
复杂度分析
两种操作的时间复杂度都是O(n),其中n是树中节点的数量,因为我们需要访问每个节点一次。空间复杂度在最坏情况下(树退化为链表)也是O(n),平均情况下为O(log n)。

参考:
iOS 高级面试题

相关文章

网友评论

      本文标题:iOS 高级面试题--含答案

      本文链接:https://www.haomeiwen.com/subject/svgbvjtx.html