欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OC之RunLoop了解一下

程序员文章站 2024-03-20 17:10:10
...

一、进程

     进程是指在系统中正在运行的一个应用程序每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内比如同时打开QQ、Xcode,系统就会分别启动2个进程

二、线程

        是用来执行任务的,线程只要彻底执行完A才能去执行B,这就是线程的安全,为了防止任务能同时进行,引入了多线程。

        1、多线程:其实是CPU对任务的调度,任务的优先级越高,CPU被调度的就越快,对于单核CPU只能在同一时间处理一条线程,多线程并发执行实质是任务线程之间的不断切换,因为切换的太快,所以造成了同一时间执行的假象;比如两个线程A、B;A执行到某一时间,切换到B,但是A还没有执行完,系统会把A当前的位置和数据保存在栈中,依次循环,线程对CPU的消耗是比较大的,ios中不建议开多条线程

         2、线程分为两种:主线程和子线程

         在当前线程中开启多个新线程,不按照顺序执行叫异步,在当前线程中的任务按顺序执行,不开启新的线程,叫同步。队列是状态线程任务的结构:包括并发队列和穿行队列;并发队列的线程可以同时一起执行,串行队列只能依次逐一先后有序的执行

三、多线程的优缺点:

优点:1、提高CPU的利用率,不让它闲着

           2、提高程序的执行效率,避免线程阻塞造成的卡顿

缺点:1、大量的线程降低代码的可读性

           2、需要大量的内存空间,降低程序的性能

           3、多个线程同时争夺同一资源,容易线程不安全,比如mutalArray的例子

死锁:A在等待B的结果,B在等待A的结果

多线程的实现方法有四种:Pthreads、CGD、NSThread、NSOpreation

多线程就是开辟线程、添加队列、队列中同步或者异步执行

四、主线程

一个ios程序运行后,系统会默认开启一条线程,称为UI线程或者主线程,主线程是用来显示、刷新界面、处理UI事件的,假如没有RunLoop程序一运行就结束了,看不到持续运行的app

五、RunLoop的作用;学习原链接RunLoop深入

1、保持程序的持续运行;程序一启动就会开一个主线程,主线程一开起来就会跑一个主线程对应的RunLoop,是Runloop保证主线程不被销毁

2、处理项目中各种事件,比如:selector事件、定时器、触摸事件等

3、节省CPU的资源,提高程序性能。当有操作的时候runloop会通知CPU该做什么,没有事情的时候让它休息

Runloop实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口的函数,线程执行了这个函数就会一直处于这个函数内部接受消息----等待消息----处理消息,直到这个循环结束

RunLoop在哪里开启:在UIApplicationMain函数中启动了RunLoop,程序不会马上退出而是保持运行状态,程序运行,主线程开启,和主线程对应的Runloop开启,那么runloop肯定是在程序的入口main函数中开启的

在iOS中有两套API访问和使用RunLoop

Foundation:NSRunloop

CoreFoundation :CFRunLoopRef 他是基于pthread来管理的

NSRunloop是基于CFRunLoopRef的一层OC的包装

在源码中创建的线程核心代码

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
	t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    //如果没有线程,创建一个
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
        //创建一个可变的字典
	CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        //将主线程作为参数创建一个Runloop
	CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        //将主线程的RunLoop和主线程以key/value的形式保存,可以看到线程和RunLoop是一一对应的
        
	CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
	if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
	    CFRelease(dict);
	}
	CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    
    //当输入一个 currentRunLoop 时,会通过当前线程这个Key,在字典中寻找对应的runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    
    //如果没有找到
    if (!loop) {
        
        //重新创建一个
	CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
        //然后将runloop和线程以 key/value的形式保存
	loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
	if (!loop) {
	    CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
	    loop = newLoop;
	}
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
	CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

很好的runloop帖子:Runloop全面学习

六、线程与runloop的关系

程序启动时,系统会自动创建主线程的RunLoop,每条线程都有唯一的一个与之对应的RunLoop对象,主线程的Runloop已经自动创建好了,子线程的runloop需要自己手动创建,runloop在第一次获取时创建,在线程结束时销毁

获取当前线程的Runloop,runloop是懒加载的,在发送消息时自动创建对象

[NSRunLoop currentRunLoop];

在iOS开发中,会遇到两个线程对象:pthread_t 和 NSThread,NSThread只是对pthread的封装,两者肯定是一一对应的,我们可以通过pthread_main_thread_np()或者 [NSThread mainThread]来获取主线程,也可以通过pthread_self() [NSThread currentThread]来获取当前的线程。

苹果是不允许直接来创建Runloop的。他只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()

七、RunLoop相关类

我尝试着打印了 [NSRunLoop mainRunLoop];

 mainRunLoop == <CFRunLoop 0x6040001f9a00 [0x103291c80]>
{wakeup port = 0xc03, stopped = false, ignoreWakeUps = true, 
  current mode = (none),
  common modes = <CFBasicHash 0x6040002557b0 [0x103291c80]>
{type = mutable set, count = 1,
entries =>
	2 : <CFString 0x103267818 [0x103291c80]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = (null),
modes = <CFBasicHash 0x604000255780 [0x103291c80]>{type = mutable set, count = 1,
entries =>
	2 : <CFRunLoopMode 0x604000192f10 [0x103291c80]>{name = kCFRunLoopDefaultMode, port set = 0xd03, queue = 0x60400014f4c0, source = 0x600000192e40 (not fired), timer port = 0x1503, 
	sources0 = (null),
	sources1 = (null),
	observers = (null),
	timers = (null),
	currently 556728920 (19355243452143) / soft deadline in: 1.84467247e+10 sec (@ -1) / hard deadline in: 1.84467247e+10 sec (@ -1)
},

}
}

可以看到Corefoundation中关于runloop的五个类

CFRunLoopRef

CFRunLoopModeRef

CFRunLoopSourceRef

CFRunLoopTimerRef

CFRunLoopObserverRef

 从打印中可以看出,一个RunLoop想要跑起来,必须要有Model支持,而Mode里边必须有(NSSet *)soure (NSArray *)Timer

这时候的(NSArray *)Observer是用来监听runloop的状态,这时候是不会**的

1、CFRunloopModeRef

代表runloop运行模式,每个runloop都包含几个mode。每个mode又包含若干个source、timer、observer,每次runloop启动时,只能指定其中一个mode,这个mode被称作currentMode,如果需要切换mode,只能退出runloop,再重新指定一个mode进入,这样做主要是为了分开不同的source、timer、observer,让他们互不影响

runloop默认注册了5个mode

NSDefaultRunLoopMode:app默认的mode,通常主线程是在这个mode下运行的

UITrackingRunLoopMode:界面跟踪mode。用于scorllview追踪触摸滑动,保证界面滑动时不受影响

UIInitializationRunLoop:刚启动时app进入第一个mode,启动完成就不再使用

GSRunLoopReceiveRunLoopMode:接受系统时间的内部mode,绘图服务,通常用不到

NSRunLoopCommonModes:这是一个占位用的mode,不是一种真的mode

几个mode的区别

    NSTimer * time = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runloopIsRun) userInfo:nil repeats:YES];

    [[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode];

    [[NSRunLoop currentRunLoop] addTimer:time forMode:UITrackingRunLoopMode];

    [[NSRunLoop currentRunLoop] addTimer:time forMode:NSRunLoopCommonModes];

后面这一句相当于前两者写在一起的模式下运行(####其实这里我也不是很明白####)

2、CFRunLoopTimeRef

CFRunLoopTimeRed是基于时间的触发器,应该说就是NSTimer,它手RunLoop的mode影响

创建Timer的两种方式

1> NSTimer * time = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runloopIsRun) userInfo:nil repeats:YES];

这种方式必须手动添加到 RunLoop 中去才会被调用

2> 通过方法scheduledTimerWithTimeInterval: target :  selector

    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    /*
     注意:调用了 scheduledTimer 返回的定时器,已经自动被添加到当前runLoop 中,而且是NSDefaultRunLoopMode ,想让上述方法起作用,必须先让添加了上述 timer的RunLoop 对象 run 起来,通过scheduledTimerWithTimeInterval 创建的 timer 可以通过以下方法修改 mode
     */
    [[NSRunLoop currentRunLoop] addTimer:time forMode:UITrackingRunLoopMode];

****提示****  GCD的定时器不受RunLoop的Mode影响

 CFRunLoopSourceRef  事件的输入源

source的分类:

1 》port-Based Source  基于端口,跟其他线程进行交互的,属于mac内核发过来的一些消息

2》 custom input Source 自定义输入源

3》cocoa perform selector  sourcees

按照函数条用栈可分为

1》source0:基于port的触摸事件、点击事件

2》source1:基于port的,通过内核和其他线程通信,接受分发系统事件,是和硬件的交互,触摸首先在屏幕上包装成一个event事件,在通过source1进行分发到source0,最后通过source0来处理

逻辑图如下 

OC之RunLoop了解一下

CFRunLoopObserver : 是观察者,能够监听runloop的状态改变,主要监听以下几个时间节点,在文档的.h中这么声明
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), //即将进入Loop
    kCFRunLoopBeforeTimers = (1UL << 1), //即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), //即将处理source
    kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),//刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),         //即将退出  Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU  //监听所有事件
};
    /**
    创建观察者,监听runloop
     
     该方法可以在添加timer之前做点事情。可以在添加source之前搞点事情
     参数一:CFAllocatorRef allocator CFAllocatorGetDefault 默认值
     参数二:CFOptionFlags activities  监听runloop的活动,也就是之前的枚举
     参数三:Boolean repeats  是否重复监听
     参数四:CFIndex order  写0
     参数五:block
     */
   CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopRunHandledSource, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
    });
    //添加观察者,监听当前的runloop
    CFRunLoopAddObserver(CFRunLoopGetCurrent(),observer , kCFRunLoopCommonModes);
    //cf的是不ARC管理内存的,需要释放
    CFRelease(observer);

 整个runloop的运行逻辑如下图:

OC之RunLoop了解一下

八、RunLoop的应用

1 >    NSTimer  需求让定时器在其他线程开启

  /*

    > 需求

    > 有时候,用户拖拽scrollView的时候,mode:UITrackingRunLoopMode,显示图片,如果图片很大,会渲染比较耗时,造成不好的体验,因此,设置当用户停止拖拽的时候再显示图片,进行延迟操作

    - 方法1:设置scrollView的delegate  当停止拖拽的时候做一些事情

    - 方法2:使用performSelector 设置模式为default模式 ,则显示图片这段代码只能在RunLoop切换模式之后执行

   */

 

// 加载比较大的图片时,

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

    // inModes 传入一个 mode 数组,这句话的意思是

    // 只有在 NSDefaultRunLoopMode 模式下才会执行 seletor 的方法显示图片

    UIImageView * imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];

    [imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"avater"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];

}

效果为:当用户点击之后,下载图片,但是图片太大,不能及时下载。这时用户可能会做些其他 UI 操作,比如拖拽,但是如果用户正在拖拽浏览其他的东西时,图片下载完毕了,此时如果要渲染显示,会造成不好的用户体验,所以当用户拖拽完毕后,显示图片。这是因为,用户拖拽,处于 UITrackingRunLoopMode 模式下,所以图片不会显示

2、常驻线程

> 需求

  搞一个线程一直存在,一直在后台做一些操作 比如监听某个状态, 比如监听是否联网。

    //一个线程对应一个runloop,创建一个线程会自动开辟一个runloop
    NSThread * runloopTherd = [[NSThread alloc]initWithTarget:self selector:@selector(runLoop) object:nil];
    [runloopTherd start];
    

-(void)runLoop
{
    NSLog(@"-----");
    /*
     * 创建RunLoop,如果RunLoop内部没有添加任何Source Timer
     * 会直接退出循环,因此需要自己添加一些source才能保持RunLoop运转
     */
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    
}

3、自动释放池

Runloop循环时,在进入睡眠之前会清掉自动释放池,并且创建一个新的释放池,用于内部变量的销毁。在子线程开RunLoop的时候一定要自己写一个autoreleasepool。一个runloop对应一条线程,自动释放池是针对当前线程里边的对象

    NSThread * releaseTherd = [[NSThread alloc]initWithTarget:self selector:@selector(releaseRun) object:nil];
    [releaseTherd start];

-(void)releaseRun
{
    //这样保证了内存的安全
    @autoreleasepool{
        NSTimer * timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runLoop) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    }
}

4、runloop与网络请求、AFNetWorking ---摘自博客

iOS中,关于网络请求的接口从上到下有:

CFSocket    最底层的接口,只负责socket通信

CFNetwork        -----> ASIHttpRequest   基于CFSocket的上层封装

NSURLConnection   ----->  AFNetworking   基于CFNetwork的分装,提供面向对象的接口

NSURLSession        ------->AFNetworking  iOS7中新增的接口,内部用到了NSURLConnection

五、PerformSelecter,这个单独放出来,这里只做简单介绍

当调用NSObject的performSelecter:afterDelay:方法后,其实内部会创建一个timer,并添加到当前的线程的runloop中,所以如果当前的线程没有runloop,则这个方法会失效

========稍后我会将RunLoop源代码的注释贴上=========

=======可能理解有错或者不到位欢迎指出========

=========相关帖子=======

iOS底层原理总结 - RunLoop

深入理解RunLoop

OS底层原理总结 - RunLoop2

 

 

 

 

 

 

 

 

相关标签: runloop 线程 oc

上一篇: @synchronized详解

下一篇: