美文网首页
多线程、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:));
    

    相关文章

      网友评论

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

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