分类和扩展的区别?
分类(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) |
| 添加方法 | 可以 | 可以 |
| 添加属性 | 只能添加关联对象 | 可以添加实例变量和属性 |
| 加载时机 | 运行时 | 编译时 |
| 命名 | 必须有名称 | 匿名 |
分类:的用途
- 为现有类添加新方法
- 将大型类分解为多个文件
- 实现非正式协议
- 为类添加默认实现(通过+load方法)
扩展:的用途
- 声明私有方法\属性
- 重新声明公有属性为可读写
- 遵循的私有协议
分类的局限性
- 不能添加实例变量:分类只能通过关联对象(Associated Object)来模拟属性
- 方法名冲突:如果分类方法与原类方法同名,分类方法会覆盖原类方法
- 属性限制:分类中声明的属性不会自动生成getter/setter方法和实例变量
- 加载顺序不确定:多个分类的方法加载顺序不确定
分类的结构体成员
这个结构体在运行时被用来将分类的内容附加到主类上。
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通常与static或extern结合使用。
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. static 和 const 结合使用
在 Objective-C 中,static 和 const 经常一起使用,表示一个 文件内部的静态常量(不可修改,且仅当前文件可见)。
示例(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 高级面试题







网友评论