OC之RunLoop了解一下

一、进程

     进程是指在系统中正在运行的一个应用程序每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内比如同时打开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来处理

逻辑图如下 

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的运行逻辑如下图:

八、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

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值