目录
- Runtime
- 消息发送机制
- isMemberOfClass 和 isKindOfClass
- Super 和 Self
- @synthesize/@dynamic
- RunLoop
- 线程和进程
- 什么是优先级反转?
- 自旋锁、互斥锁比较
1. Runtime
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行;OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数;平时编写的OC代码,底层都是转换成了Runtime API进行调用。
应用:
- 利用关联对象(AssociatedObject)给分类添加属性
- 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
- 交换方法实现(交换系统的方法)
- 利用消息转发机制解决方法找不到的异常问题
- Runtime 相关API
1、涉及类的API:
& 动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
& 注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls)
& 销毁一个类
void objc_disposeClassPair(Class cls)
& 获取isa指向的Class
Class object_getClass(id obj)
& 设置isa指向的Class
Class object_setClass(id obj, Class cls)
& 判断一个OC对象是否为Class
BOOL object_isClass(id obj)
& 判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)
& 获取父类
Class class_getSuperclass(Class cls)
2、涉及成员变量的API :
& 获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name)
& 拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
& 设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
& 动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
& 获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
3、涉及属性的API
& 获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)
& 拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
& 动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
& 动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
& 获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
4、涉及方法的API
& 获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
& 方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)
& 拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
& 动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
& 动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
& 获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
& 选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
& 用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
2. 消息发送机制
OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)。
objc_msgSend底层有3大阶段:消息发送(当前类、父类中查找)、动态方法解析、消息转发。
- 消息发送
- 动态方法解析
- 消息转发
3. isMemberOfClass 和 isKindOfClass
4. Super 和 Self
- self 调用方法时会使用 objc_msgSend 函数: id objc_msgSend(id theReceiver, SEL theSelector, ...)。第 一个参数是消息接收者,第二个参数是调用的具体类方法的 selector,后面是 selector 方法的可变参数。
- 当使用 super 调用时,会使用 objc_msgSendSuper 函数:id objc_msgSendSuper(struct objc_super *super, SEL op, ...)第一个参数是个objc_super的结构体(如下),第二个参数是调用的具体类方法的 selector。
struct objc_super {
id receiver; receiver是消息接收者
Class superClass; 父类
}
- self 和 super 指向的是同一个对象,只是查找方法起始位置不同,一个是从本类开始,一个是从本类的父类开始;
- self是类,super是预编译指令(指代从父类方法列表开始查询方法的self);
5. @synthesize/@dynamic
@property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;
@synthesize 表示如果属性没有手动实现setter和getter方法,编译器会自动加上这两个方法。
@dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。
6. RunLoop
-
6.1 RunLoop的基本作用:
- 保持程序的持续运行(线程常驻)
- 处理App中的各种事件(比如触摸事件、定时器事件等)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
- 实际应用:控制线程生命周期(线程保活、解决NSTimer在滑动时停止工作的问题、监控应用卡顿、性能优化
-
6.2 RunLoop和线程的关系:
- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
- 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建, RunLoop会在线程结束时销毁
- 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
-
6.3 RunLoop相关的类
CFRunLoopModeRef:RunLoop的运行模式
- 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
- RunLoop启动时只能选择其中一个Mode,作为currentMode
- 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入;不同组的Source0 / Source1 / Timer / Observer 能分隔开来,互不影响
- 如果Mode里没有任何Source0 / Source1 / Timer / Observer,RunLoop会立马退出
常见的几种Mode
- kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行。
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
- NSRunLoopCommonModes:是伪模式,model集合,指可以在标记为Common Modes的模式下运行,目前被标记为Common Modes的模式: kCFRunLoopDefaultMode,UITrackingRunLoopMode。
- UIInitializationRunLoopMode:在刚启动App时第进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
CFRunLoopSourceRef:输入源 / 事件源。 Source 有两个版本:Source0 和 Source1。Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source0 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source1 能主动唤醒 RunLoop 的线程。
CFRunLoopTimerRef:基于时间的触发器。它和 NSTimer 是 toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop 会注册对应的时间点,当时间点到时,RunLoop 会被唤醒以执行那个回调。
CFRunLoopObserverRef:观察监听RunLoop的活动状态。每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观察到的状态如下图:
-
6.4 RunLoop的运行逻辑
7. 线程和进程的区别
- 调度 :在引入线程的操作系统中,线程是调度和分配的基本单位 ,进程是资源拥有的基本单位 。把传统进程的两个属性分开,线程便能轻装运行,从而可显著地提高系统的并发程度。 在同一进程中,线程的切换不会引起进程的切换;在由一个进程中的线程切换到另一个进程中的线程时,才会引起进程的切换。
- 并发性 :在引入线程的操作系统中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间亦可并发执行,因而使操作系统具有更好的并发性,从而能更有效地使用系统资源和提高系统吞吐量。
- 拥有资源 :不论是传统的操作系统,还是设有线程的操作系统,进程都是拥有资源的一个独立 单位,它可以拥有自己的资源。 一般地说,线程自己不拥有系统资源(只有一些必不可少的资源),但它可以访问其隶属进程的资源。
- 系统开销: 由于在创建或撤消进程时,系统都要为之分配或回收资源,因此,操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。 进程切换的开销也远大于线程切换的开销。
8. 什么是优先级反转?
(1) 简单从字面上来说,就是低优先级的任务先于高优先级的任务执行了,优先级搞反了。那在什么情况下会生这种情况呢?
假设三个任务准备执行,A,B,C,优先级依次是A>B>C;
首先:C处于运行状态,获得CPU正在执行,同时占有了某种资源;
其次:A进入就绪状态,因为优先级比C高,所以获得CPU,A转为运行状态;C进入就绪状态;
第三:执行过程中需要使用资源,而这个资源又被等待中的C占有的,于是A进入阻塞状态,C回到运行状态;
第四:此时B进入就绪状态,因为优先级比C高,B获得CPU,进入运行状态;C又回到就绪状态;
第五:如果这时又出现B2,B3等任务,他们的优先级比C高,但比A低,那么就会出现高优先级任务的A不能执行,反而低优先级的B,B2,B3等任务可以执行的奇怪现象,而这就是优先反转。
(2)如何解决优先级反转
高优先级任务A不能执行的原因是C霸占了资源,而C如果不能获得CPU,不释放资源,那A也只好一直等在那,所以解决优先级反转的原则肯定就是让C尽快执行,尽早把资源释放了。基于这个原则产生了两个方法:
2.1 优先级继承
当发现高优先级的任务因为低优先级任务占用资源而阻塞时,就将低优先级任务的优先级提升到等待它所占有的资源的最高优先级任务的优先级。
2.2 优先级天花板
优先级天花板是指将申请某资源的任务的优先级提升到可能访问该资源的所有任务中最高优先级任务的优先级.(这个优先级称为该资源的优先级天花板)
2.3 两者的区别
优先级继承:只有一个任务访问资源时一切照旧,没有区别,只有当高优先级任务因为资源被低优先级占有而被阻塞时,才会提高占有资源任务的优先级;而优先级天花板,不论是否发生阻塞,都提升,即谁先拿到资源,就将这个任务提升到该资源的天花板优先级。
9. 自旋锁、互斥锁比较
什么情况使用自旋锁比较划算?
- 预计线程等待锁的时间很短
- 加锁的代码(临界区)经常被调用,但竞争情况很少发生
- CPU资源不紧张
- 多核处理器
什么情况使用互斥锁比较划算?
- 预计线程等待锁的时间较长
- 单核处理器
- 临界区有IO操作
- 临界区代码复杂或者循环量大
- 临界区竞争非常激烈
推荐学习资料:
如果需要跟我交流的话:
※ Github: https://github.com/wsl2ls
※ 简书:https://www.jianshu.com/u/e15d1f644bea
※ 微信公众号:iOS2679114653
※ QQ群:835303405