# iOS技术研究项目
**Repository Path**: wubinnjj/technical-research-project
## Basic Information
- **Project Name**: iOS技术研究项目
- **Description**: 研究iOS技术知识点,每个知识点打一个分支,切换分支查看对应知识点运行
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2022-08-08
- **Last Updated**: 2025-05-23
## Categories & Tags
**Categories**: Uncategorized
**Tags**: IOS, oc, Objective-c
## README
# iOS技术知识点
#### 1、weak指针的原理?
```
当一个对象要释放时,会自动调用dealloc,接下来的调用轨迹是
dealloc
_objc_rootDealloc
rootDealloc
object_dispose
objc_destructInstance(obj->clearDeallocation 将弱引用对象置为nil)、free
答案:
将弱引用对象存到一个哈希表里,当这个对象要销毁,取出当前对象对应的弱引用表,把弱引用表里存储的弱引用到清除掉
```
#### 2、ARC都帮我们做了什么?
```
ARC是LLVM编译器(Xcode架构编译器的框架系统,c++语言) 和 Runtime 互相协作的一个结果
ARC利用LLVM编译器在大括号快结束的时候自动生成retain、release、autorelease代码
ARC利用Runtime在程序运行中处理弱应用等相关操作
```
#### 3、iOS中的常见多线程方案?
| 技术方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
| :-----: | :---- | :----: | :----: | :----: |
| pthread | 1、 一套通用的多线程API
2、 适用于Unix\Linux\Windows等系统
3、跨平台\可移植
4、使用难度大 | C | 程序员管理 | 几乎不用 |
| NSThread | 1、 使用更加面向对象
2、 简单易用、可直接操作线程对象 | OC | 程序员管理 | 偶尔使用 |
| GCD | 1、 旨在替代NSThread等线程技术
2、 充分利用设备的多核 | C | 自动管理 | ***经常使用*** |
| NSOperation | 1、 基于GCD(底层是GCD)
2、比GCG多了一些简单实用的功能
3、使用更加面向对象 | OC | 自动管理 | ***经常使用*** |
#### 4、iOS NSThread GCD NSOperation的优缺点?
```
** NSthread: **
优点:比其他两种轻量级。
缺点:需要自己管理线程的生命周期,线程同步。 线程同步对数据的加锁会有一定的开销。
** Operation、GCD: **
优点:不需要关心线程管理,数据同步的事情。
两者区别:NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。GCD主要与block结合使用。代码简洁高效
1. 性能:GCD更接近底层,而NSOperationQueue则更高级抽象,所以GCD在追求性能的底层操作来说,是速度最快的。这取决于使用Instruments进行代码性能分析,如有必要的话
2. 从异步操作之间的事务性,顺序行,依赖关系。GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持
3. 如果异步操作的过程需要更多的被交互和UI呈现出来,NSOperationQueue会是一个更好的选择。底层代码中,任务之间不太互相依赖,而需要更高的并发能力,GCD则更有优势
个人总结:我们应该忘记微小的性能提升。过早优化是万恶之源。只有Instruments显示有真正的性能提升时才有必要用低级的GCD。
```
#### 5、多线程容易混淆的术语
```
同步和异步主要影响:能不能开启新的线程
同步:在当前线程中执行任务,不具备开启新线程的能力
异步:在新的线程中执行任务,具备开启新线程的能力
并发和串行主要影响:任务的执行方式(不能决定能不能开启线程)
并发队列:多个任务并发(同时)执行
串行队列:一个任务执行完毕后,再执行下个任务
主队列:异步主队列不能创建新的线程,任务会在主线程上执行
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"当前任务 - %@",[NSThread currentThread]);
});
注意:sync往当前的串行队列添加任务就会产生死锁!!!(新任务等旧任务,旧任务等新任务)
```
#### 6、多线程安全隐患的解决方案
```
解决方案:使用线程同步技术(同步,就是协同步调,按预定的先后次序进行)
常见的线程同步技术是:加锁
iOS中的线程同步方案:
1、OSSpinLock:叫做“自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
目前已经不再安全,可能会出出现优先级反转的问题
如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁
需要导入头文件 #import
2、os_unfair_lock:
os_unfair_lock用于取代不安全的OSSpinLock,从iOS10开始才支持
从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
需要导入头文件 #import
3、pthread_mutex:
叫做‘互斥锁’,等待锁的线程会处于休眠状态
需要导入头文件 #import
4、dispatch_semaphore:semaphore叫做‘信号量’
信号量的初始值,可以用来控制线程的并发访问的最大数量
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
5、dispatch_queue(DISPATCH_QUEUE_SERIAL):直接使用GCD的串行队列,也是可以实现线程同步的
6、NSLock:是对mutex普通锁的封装
7、NSRecursiveLock:也是对mutex递归锁的封装,API跟NSLock基本一致
8、NSCondition:是对mutex和cont的封装
9、NSConditionLock
10、@synchronized:是对mutex递归锁的封装
性能从高到低排序:一般推荐使用dispatch_semaphore和pthread_mutex
1、os_unfair_lock(iOS10开始支持)
2、OSSpinLock
3、dispatch_semaphore
4、pthread_mutex
5、dispatch_queue(DISPATCH_QUEUE_SERIAL)
6、NSLock
7、NSCondition
8、pthread_mutex(recursive)
9、NSRecursiveLock
10、NSConditionLock
11、synchronized
```
#### 7、nonatomic和atomic
```
atom:原子
atomic:原子性
给属性加上atomic修饰,可以保证属性的setter和getter都是原子性操作(给setter和getter方法加锁),也就是保证setter和getter内部是线程同步的
缺点:频繁加锁,太耗性能
```
#### 8、iOS多线程之读写安全方案(多读单写)
```
如何实现以下场景?
同一时间,只能有1个线程进行写的操作
同一时间,允许有多个线程进行读的操作
同一时间,不允许既有写的操作,又有读的操作
经常用于文件等数据的读写操作,iOS中的实现方案有
1、pthread_rwlock:
2、dispatch_barrier
```
#### 9、CADisplayLink、NSTimer使用注意
```
CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用
解决方案1:使用block方式
案例:
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
解决方案2:增加一个中间对象,它的类对象弱引用target即可
ViewController强引用timer
NSTimer强引用target
NSProxy弱引用target
继承NSProxy效率更高
因为NSProxy专门用来做消息转发的,会少了从父类查找方法和动态方法解析流程,这样效率更高,推荐使用
案例:
@implementation WBProxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy对象不需要调用init,因为它本来就没有init方法
WBProxy *proxy = [WBProxy alloc];
proxy.target = target;
return proxy;
}
//消息转发代码
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end
在ViewController使用
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[WBProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
```
#### 10、GCD定时器
```
NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时
而GCD的定时器会更加准时
```
#### 11、iOS程序的内存布局
```
低地址 ---> 高地址
保留 代码段(__TEXT) 数据段(__DATA) 堆(heap) 栈(stack) 内核区
字符串常量
已初始化数据
未初始化数据
代码段:编译之后的代码
数据段:字符串常量:比如NSString *str = @“123”
已初始化数据:已初始化的全局变量,静态变量等
未初始化数据:未初始化的全局变量,静态变量等
堆:通过alloc、malloc、calloc等动态分配的空间,分配的内存空间地址越来越大
栈:函数调用开销,比如局部变量,分配的内存空间地址越来越小
```
#### 12、Tagged Pointer
```
从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
在没有使用Tagged Pointer之前,NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
当对象指针的最低有效位是1,则该指针为Tagged Pointer
objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销
如何判断一个指针是否位Tagged Pointer ?
iOS平台,最高有效位是1(第64位)
MAC平台,最低有效位是1
```
#### 13、ARC模式
```
ARC模式:
编译器直接生成成员变量、get、set方法,并实现get、set方法
assgin修饰基本数据类型直接赋值
@property (nonatomic, assign) int age;
eg:
- (void)setAge:(int)age
{
_age = age;
}
- (int)age
{
return _age;
}
retain修饰先判断对象是否相同,若不相同则release,再retain;若相同则什么都不处理
@property (nonatomic, retain) WBDog *dog;
eg:
- (void)setDog:(WBDog *)dog
{
//引用计数+1
if (_dog != dog) {
[_dog release];
_dog = [dog retain];
}
}
- (WBDog *)dog
{
return _dog;
}
```
#### 14、copy
```
iOS提供了2个拷贝的方法
1、copy,不可变拷贝,产生不可变副本
2、mutableCopy,可变拷贝,产生可变副本
浅拷贝和深拷贝
1.深拷贝:内容拷贝,产生新的对象
2.浅拷贝:指针拷贝,没有产生新的对象
```
#### 15、copy策略内部实现原理
```
@property (copy, nonatomic) NSArray *data;
//NSMutableArray不要这样写 使用copy策略会在set方法重新copy成不可变数据,导致添加元素崩溃
//@property (copy, nonatomic) NSMutableArray *data;
//copy策略内部实现
- (void)setData:(NSArray *)data
{
if (_data != data) {
[_data release];
_data = [data copy];
}
}
```
#### 16、自定义对象的copy
```
//自定义对象的copy必须要要实现NSCopying的copyWithZone协议
- (id)copyWithZone:(NSZone *)zone
{
WBPerson *person = [[WBPerson allocWithZone:zone] init];
person.age = self.age;
//可选择属性copy
// person.weight = self.weight;
return person;
}
```
#### 17、block的变量捕获(capture)
```
为了保证block内部能够正常访问外部变量,block有个变量捕获机制
```
| 变量类型 | 捕获到block内部 | 访问方式 |
| :-----: | :----: | :----: |
| auto局部变量 | 是 | 值传递 |
| static局部变量 | 是 | 指针传递 |
| 全局变量 | 否 | 直接访问 |
#### 18、block的类型
```
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
__NSGlobalBlock__
__NSStackBlock__
__NSMallocBlock__
```
#### 19、RunLoop的运行逻辑
```
1、Source0
触摸事件处理
performSelector:onThread:
2、Source1
基于Port的线程通信
系统事件捕捉
3、Times
NSTimer
performSelector:withObject:aferdelay:
4、Observers
用于监听RunLoop的状态
UI刷新(BeforeWaiting)
Autorelease pool
```
#### 20、RunLoop在实际开发中的应用
```
1、控制线程生命周期(线程保活)
核心代码:
//往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStoped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
2、解决NSTimer在滑动时停止工作的问题
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d",++count);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
3、监控应用卡顿
4、性能优化
```
#### 21、objc_msgSend执行流程
```
OC中的方法调用,其实都是转换为objc_msgSend函数的调用
objc_msgSend的执行流程可以分为3大阶段
1、消息发送
a.首先判断receiver是否为nil,如果是nil,则退出;
b.否则从reveiverClass的cache中查找方法,如果找到了方法,调用方法,查找结束;
c.否则从reveiverClass的class_rw_t中查找方法,如果找到了方法,调用方法,结束查找,并将方法缓存到reveiverClass的cache中;
d.否则从superClass的cache中查找方法,如果找到了方法,调用方法,结束查找,并将方法缓存到reveiverClass的cache中;
e.否则从superClass的class_rw_t中查找方法,如果找到了方法,调用方法,结束查找,并将方法缓存到reveiverClass的cache中;
f.否则继续查看上层是否还有superClass,如果有,则继续重复d的操作,直到找到方法为止;
g.否则说明整个消息继承体系没有方法,消息发送阶段结束,进入动态方法解析阶段.
2、动态方法解析
a.是否曾经有动态解析,如果是,进入消息转发;
b.否则调用:
实例方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel
类方法:
+ (BOOL)resolveClassMethod:(SEL)sel
c.标记为已经动态方法解析;
d.消息发送;
没写动态方法解析,则进入消息转发阶段!
3、消息转发
a.调用forwardingTargetForSelector:方法,如果返回值不为nil,则进入objc_msgSend(返回值对象,SEL);
b.否则返回值为nil,则调用methodSignatureForSelector:方法,如果返回值为nil,则调用doesNotRecognizeSelector:方法;
c.方法签名方法返回值不为nil,则调用forwardInvocation:方法.
```
#### 22、self、super、class、superClass
```
NSObject -> Person -> Student
| 方法调用 | 结果 |
| :-----: | :---- : |
| [self class] | Student |
| [self superClass] | Person |
| [super class] | Student |
| [super superClass] | Person |
super 是从父类开始查找方法;消息的接收者还是self;
class 方法底层实现object_getClass(self);
superClass 底层实现 class_getSuperclass(object_getClass(self)) 获取对象的父类
```
#### 23、isKindOfClass和isMemberOfClass的区别
```
1、isMemberOfClass底层实现:
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
判断当前对象是否属于传入的类
2、isKindOfClass底层实现:
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls -> superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls -> superclass) {
if (tcls == cls) return YES;
}
return NO;
}
判断当前对象是否属于传入的类的子类
```
#### 24、Runtime的应用
```
什么是Runtime? 平时项目中有用过吗?
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时进行
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
平时编写的OC代码,底层都是转换成了Runtime API进行调用
具体应用:
1、利用关联对象(AssocitedObject)给分类添加属性
2、遍历类的所有成员变量(修改textField的占位文字颜色、字典转模型、自动归档解档)
3、交换方法实现(交换系统的方法)
Method runMethod = class_getInstanceMethod([WBPerson class], @selector(run));
Method testMethod = class_getInstanceMethod([WBPerson class], @selector(test));
method_exchangeImplementations(runMethod, testMethod);
主要应用: 1、统计按钮点击的次数
· 2、数组、字典的判空处理
3、批量修改字体大小
......
4、利用消息转发机制解决方法找不到的异常问题
```