美文网首页
多线程、Runloop、Runtime

多线程、Runloop、Runtime

作者: Coder_JdHo | 来源:发表于2020-12-16 10:18 被阅读0次

多线程

iOS多线程技术有哪几种方式
pthread、NSThread、GCD、NSOperation

1. NSOperation

NSOperationQueue *queue = [[NSOperationQueue alloc] init];(子线程)  [NSOperationQueue mainQueue](主线程)
[queue addOperationWithBlock:^{}]; 添加operation
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{ }]
[queue addOperation:operation1];
queue.maxConcurrentOperationCount = 3; //最大并发数
[queue cancelAllOperations]; //取消
[operationB addDependency:operationA]; //线程之间的依赖

2. GCD

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_async(dispatch_get_main_queue(), ^{

3. GCD ground

- (void)gcdGroup
{
    // 使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //dispatch_async(queue, ^{ });
    dispatch_group_async(group, queue, ^{
        for(int i = 30030; i > 0 ; i--) {
            for(int i = 30030; i > 0 ; i--) {
                
            }
        }
        NSLog(@"加载图片1");
        
    });
    dispatch_group_async(group, queue, ^{NSLog(@"加载图片2");});
    dispatch_group_async(group, queue, ^{NSLog(@"加载图片3");});
    
    // 当并发队列组中的任务执行完毕后才会执行这里的代码
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"合并图片");
    });
    
    
}

4. 栅栏函数

- (void)gcdBarrier
{
    //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 1.创建并发队列 (队列要用这种创建方式才可以)
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    
    // 2.向队列中添加任务
    dispatch_async(queue, ^{  // 1.2是并行的
        for(int i = 30030; i > 0 ; i--) {
            for(int i = 30030; i > 0 ; i--) {
                
            }
        }
        NSLog(@"任务1, %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任务2, %@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"任务 barrier, %@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{   // 这两个是同时执行的
        NSLog(@"任务3, %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任务4, %@",[NSThread currentThread]);
    });
    
    // 输出结果: 任务1 任务2 ——》 任务 barrier ——》任务3 任务4
    // 其中的任务1与任务2,任务3与任务4 由于是并行处理先后顺序不定。
}

5. 同步死锁

//使用sync函数在当前串行队列执行任务就会产生死锁
- (void)deadLock
{
    NSLog(@"1");
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2");
    });
    
    NSLog(@"3");
    
    //dispatch_sync:立马在当前线程执行
    dispatch_sync(dispatch_get_main_queue(), ^{  //执行到dispatch_sync同步方法,主线程进入等待,不往下执行,死锁
        NSLog(@"4");
    });
    NSLog(@"5");
    //但是如果是换一个队列,或者使用并发队列的话就不会死锁

    //dispatch_get_main_queue,队列先进先出,执行完当前任务后才能执行新的任务
    //dispatch_async,异步,不要求立马在当前线程执行
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
}

死锁

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

四个必要条件:
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

预防死锁:
1.互斥条件一般都不会去更改
2.“ 一次性分配”方案,一次性申请所有所需资源;(针对请求和保持条件)
3.申请了A资源后,必须释放A才能申请B(针对请求和保持条件)
4.低优先级线程的资源可以被高优先级线程抢占(针对不剥夺条件)
5.按顺序请求资源,将系统中的所有资源统一编号,所有申请必须按照资源的编号顺序(升序)提出(针对环路等待条件)

runloop

它是运行循环,它内部就是do-while循环,在这个循环内部不断地处理各种任务。

runloop五个相关的类
a.CFRunloopRef
b.CFRunloopModeRef【Runloop的运行模式】
c.CFRunloopSourceRef【Runloop要处理的事件源】
d.CFRunloopTimerRef【Timer事件】
e.CFRunloopObserverRef【Runloop的观察者(监听者)】

CFRunLoopModeRef代表RunLoop的运行模式,一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
每次RunLoop启动时,只能指定其中一个Mode,这个Mode被称作CurrentMode
如果需要切换Mode,只能退出Loop ,在重新指定一个Mode进入
这样做主要是为了分割开不同组的Source/Timer/Observer,让其互不影响

runloop模式 (系统默认注册了5个Mode常用的有3个)
kCFRynLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响 (正常情况下不会执行定时器,拖拽UIScrollview的时候才会执行定时器任务)
kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode (NSRunLoopCommonModes = NSDefaultRunLoopMode && UITrackingRunLoopMode)
UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后不再使用
GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到

CFRunLoopSourseRef事件源
有两种:
sourse0:非基于port的,自己写的方法、响应
sourse1:基于port的,系统提供的

runloop和线程的关系
主线程默认开启的runloop, 一个线程对应一个runloop,线程也可以没有runloop,runloop保证了线程的持续运行。

Runloop的启动方法

[[NSRunLoop mainRunLoop]run]; //不建议使用,因为这个接口会导致Run Loop永久性的运行在NSDefaultRunLoopMode模式,即使使用CFRunLoopStop(runloopRef);也无法停止Run Loop的运行,要用特殊的方法停止
[[NSRunLoop mainRunLoop]runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];  //此方法有停止时间

可参考:https://www.jianshu.com/p/2d3c8e084205

NSTimer

如何让定时器调用一个类方法
定时器只能调用实例方法,但是可以在这个实例方法里面调用静态方法。

使用定时器需要注意
定时器一定要加入RunLoop中,并且选好model才能运行。scheduledTimerWithTimeInterval方法创建一个定时器并加入到RunLoop中所以可以直接使用。
如果定时器的repeats选择YES说明这个定时器会重复执行,一定要在合适的时机调用定时器的invalid。不能在dealloc中调用,因为一旦设置为repeats 为yes,定时器会强持有self,导致dealloc永远不会被调用,这个类就永远无法被释放。比如可以在viewDidDisappear中调用,这样当类需要被回收的时候就可以正常进入dealloc中了。

 [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];

-(void)timerMethod
{
//调用类方法
    [[self class] staticMethod];
}

-(void)invalid
{
    [timer invalid];
    timer = nil;
}

NSTimer创建后,会在哪个线程运行。
手动创建的Timer,加入到哪个线程的RunLoop中就运行在哪个线程。
但用scheduledTimerWithTimeInterval创建的,在哪个线程创建就会被加入哪个线程的RunLoop中就运行在哪个线程(因为它被加入当前线程的runloop里)

制作一个定时器,使它在滑动列表时不受影响
解决方法其一是将timer加入到NSRunloopCommonModes中。其二是将timer放到另一个线程中,然后开启另一个线程的runloop,这样可以保证与主线程互不干扰。

// 方法1
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// 方法2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:nil repeats:true];
    [[NSRunLoop currentRunLoop] run];
});

iOS开发中方法延迟执行的几种方式

  1. performSelector方法
  2. NSTimer定时器
  3. NSThread线程的sleep
  4. GCD (dispatch after 和 设置精准定时器)
//使用GCD制作精准定时器
- (void)gcdTime
{
    /** 创建定时器对象
     * para1: DISPATCH_SOURCE_TYPE_TIMER 为定时器类型
     * para2-3: 中间两个参数对定时器无用
     * para4: 最后为在什么调度队列中使用
     */
    _gcdTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    /** 设置定时器
     * para2: 任务开始时间
     * para3: 任务的间隔
     * para4: 可接受的误差时间,设置0即不允许出现误差
     * Tips: 单位均为纳秒
     */
    dispatch_source_set_timer(_gcdTimer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);
    /** 设置定时器任务
     * 可以通过block方式
     * 也可以通过C函数方式
     */
    dispatch_source_set_event_handler(_gcdTimer, ^{
        static int gcdIdx = 0;
        NSLog(@"GCD Method: %d", gcdIdx++);
        NSLog(@"%@", [NSThread currentThread]);
        
        if(gcdIdx == 5) {
            // 终止定时器
            dispatch_suspend(_gcdTimer);
        }
    });
    // 启动任务,GCD计时器创建后需要手动启动
    dispatch_resume(_gcdTimer);
}

Runtime

Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一,我们平时编写的OC代码,底层都是基于它来实现的。

Runtime实现的机制是什么
1). 使用时需要导入的头文件 <objc/message.h> <objc/runtime.h>
2). Runtime 运行时机制,它是一套C语言库。
3). 实际上我们编写的所有OC代码,最终都是转成了runtime库的东西。
比如:
类转成了 Runtime 库里面的结构体等数据类型,
方法转成了 Runtime 库里面的C语言函数,
平时调方法都是转成了 objc_msgSend 函数(所以说OC有个消息发送机制)

OC是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
[stu show]; 在objc动态编译时,会被转意为:objc_msgSend(stu, @selector(show));
4). 因此,可以说 Runtime 是OC的底层实现,是OC的幕后执行者。

Method Swizzle什么时候用
它是一个在运行时候改变一个已存在的选择器对应的实现的过程
1). 可以用 method_exchangeImplementations 来交换2个方法中的IMP。
2). 可以用 class_replaceMethod 来修改类。
3). 可以用 method_setImplementation 来直接设置某个方法的IMP。
(注:IMP有点类似函数指针,指向具体的方法实现)

** _objc_msgForward**
_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。

其他
在#import <objc/runtime.h> 下能看到相关的方法,
objc_getClass()和class_copyMethodList()获取过私有API;
Method method1 = class_getInstanceMethod(cls, sel1);

方法和选择器有何不同

selector是一个方法的名字,方法是一个组合体,包含了名字和实现

OC中的反射机制

1). class反射
    通过类名的字符串形式实例化对象。
        Class class = NSClassFromString(@"student"); 
        Student *stu = [[class alloc] init];
    将类名变为字符串。
        Class class =[Student class];
        NSString *className = NSStringFromClass(class);
2). SEL的反射
    通过方法的字符串形式实例化方法。
        SEL selector = NSSelectorFromString(@"setName");  
        [stu performSelector:selector withObject:@"Mike"];
    将方法变成字符串。
        NSStringFromSelector(@selector*(setName:));

相关文章

  • ios面试题

    runtime介绍 runLoop与多线程关系 多线程原理(GCD、NSOperation) AFNetworki...

  • 总结

    重点: UI视图,OC语言,Runtime,内存,Block,多线程,Runloop,网络,设计模式,架构/框架,...

  • 2022-09-20

    Runloop runtime kvo kvo多线程sdwebimage afnetwork底层原理内存管理定时器...

  • iOS开发需要掌握的原理

    目录:1.Runtime2.NSNotification相关3.RunLoop4.多线程相关5.KVO6.Bloc...

  • 多线程、Runloop、Runtime

    多线程 iOS多线程技术有哪几种方式pthread、NSThread、GCD、NSOperation 1. NSO...

  • 面试清单

    1. 多线程(基础) 2. Runtime(基础) 3. Runloop(基础) 4. AFN原理(基础) 5. ...

  • iOS 面试题集合(持续更新.....)

    概述 UI 视图 OC语言相关的面试题 Runtime 内存管理 Block 多线程 RunLoop 网络 设计模...

  • 面试知识点 总结

    1,runtime 2,runloop 3,优化卡顿问题(数据处理的异步线程)。 4,多线程 5,核心动画

  • iOS进阶指南

    目录 Runtime Runloop 多线程 第三方知名库源码解析a. Alamofireb. Cachec. J...

  • iOS底层原理--oc

    在长期iOS开发中,oc是iOS的基础也是重中之重,相比runtime,runloop,多线程等知识都要重要的多,...

网友评论

      本文标题:多线程、Runloop、Runtime

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