iOS开发-RunLoop

RunLoop

从字面意思来看:跑圈、运动循环
基本用法:保持程序持续运行、处理App中的各种事件(触摸事件、定时器事件、SEL等等)
为什么需要它:节省CPU资源、 提高性能

如果没有RunLoop,程序在执行到7行就结束了。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

有Runloop后,程序就相当于一直在做循环

在这个循环内部不断地处理各种任务(比如Source、Timer、Observer)

int main(int argc, const char * argv[]) {
   
    BOOL running = YES;
    do{
        // 执行各种任务,处理各种时间
        
    }while (running);
    
    return 0;
}

程序中的Runloop----UIApplicationMain

int main(int argc, char *argv[]) 
{
       @autoreleasepool { 
              return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 
       }
 }

1.在UIApplicationMain函数内部就启动了一个RunLoop
2.UIApplicationMain函数一直没有返回,保持了程序的持续运行
3.这个默认启动的RunLoop是主线程关联的。
4.一个线程对应一个RunLoop,主线程的RunLoop默认已经启动
5.子线程的RunLoop得手动启动(调用run方法)
6.RunLoop只能选择一个Mode启动,如果当前Mode中没有任Source、Timer、Observer,那么就直接退出RunLoop

RunLoop里面有两套api用来访问和使用RunLoop
1、Foundation--NSRunLoop
2、Core Foundation --- CFRunloopRef
二者异同点:
NSRunLoop和CFRunloopRef都代表RunLoop对象,NSRunLoop是对CFRunloopRef一层OC的封装

RunLoop与线程:

每条线程都有一个RunLoop对象,主线程默认已经创建好了,子线程需要主动创建
Runloop在第一次获取时创建,在线程结束后销毁。

   /* 1. Foundation  */
    // 获取当前线程
    NSRunLoop *roop = [NSRunLoop currentRunLoop];
    // 获取主线程
    [NSRunLoop mainRunLoop];

   /*2. Core Foundation  */
    // 获取当期线程
    CFRunLoopGetCurrent();
    // 获取主线程
    CFRunLoopGetMain();

RunLoop相关类(Runloop中如果没有Source,Observre,Timer,Mode,就会结束)

    // Runloop对象
    CFRunLoopRef;
 
    // Runloop事件源(数据源)
    CFRunLoopSourceRef;
    
    // Runloop观察者
    CFRunLoopObserverRef;
    
    // Runloop时间源
    CFRunLoopTimerRef;

    // Runloop模式
    CFRunloopModeRef;

Runloop里面相关类的互相关系-

Paste_Image.png
    // 获取当前Runloop的模式
    NSString *runloopMode = [NSRunLoop currentRunLoop].currentMode;

1.同一时间只可以运行其中的一种model
2.切换Model只能退出Runloop,重新进入
3.系统默认注册了5个Mode

//App默认的Mode,通常主线程是在这个Mode下运行
NSDefaultRunLoopMode:
//界面追踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
UITrackingRunLoopMode
//这是一个占位用的Mode,不是一种正真的Mode
NSRunLoopCommonModes

//在刚启动App进入的第一个Mode,启动完成以后就不再使用
UIInitializationRunLoopMode
//接收系统事件的内部Mode,通常用不到
GSEventReceiveRunLoopMode

CFRunLoopTimerRef

CFRunLoopTimerRef是基于时间的触发器

CFRunLoopTimerRef基本上说就是NSTimer,它受RunLoop的Mode影响

NSTimer *time = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
// 定时器只运行在NSDefaultRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作
[[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode];
    
// 定时器会跑在标记为common modes的模式下
// 标记为common modes的模式:包含:UITrackingRunLoopMode和kCFRunLoopDefaultMode
[[NSRunLoop currentRunLoop] addTimer:time forMode:NSRunLoopCommonModes];

GCD不受RunLoop的Mode影响

    // 获取一个全局并发队列
    /*
     #define DISPATCH_QUEUE_PRIORITY_HIGH 2
     #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0  默认
     #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
     #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
     */
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 1. 设置定时器
    // dispatchQueue :决定了将来回调的方法在哪里执行。
    // dispatch_source_t timer  是一个OC对象
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    // 2.指定定时期开始的时间和间隔的时间
    // DISPATCH_TIME_NOW :第2个参数:定时器开始时间   (也可以抽出来,设置间隔时间)
        dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 1.0 *NSEC_PER_SEC);
    
    // intervalInSeconds :第三个参数:定时器开始后的间隔时间
    // leewayInSeconds:第四个参数:间隔精准度,0代标最精准,传入一个大于0的数,代表多少秒的范围是可以接收的,主要为了提高程序性能,积攒一定的时间,Runloop执行完任务会睡觉,这个方法让他多睡一会,积攒时间,任务也就相应多了一点,而后一起执行
    // 开始时间

    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    
    // 3.指定定时器回调方法
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"___________");
    });
    // 开启定时器
    dispatch_resume(timer);

CFRunLoopSourceRef(事件源、输入源)

Port-Based Sources (端口)
Custom Input (自定义事件)
Cocoa Perform Selector Sources 

按照函数的调用栈
Source0:非基于Port的
Source1:基于Port 通过内核和其他线程通信,接收分发系统事件

CFRunLoopObserverRef(观察者)

能够监听RunLoop的状态改变


Paste_Image.png

CFRunLoopObserverRef

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),      // 1
    kCFRunLoopBeforeTimers = (1UL << 1), // 2
    kCFRunLoopBeforeSources = (1UL << 2), // 4
    kCFRunLoopBeforeWaiting = (1UL << 5), // 32
    kCFRunLoopAfterWaiting = (1UL << 6), // 64
    kCFRunLoopExit = (1UL << 7),       // 128
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

创建一个Observer

 /*
     第1个参数:如何给Observer分配存储空间
     第2个参数:需要监听的状态类型/kCFRunLoopAllActivities监听所有状态
     第3个参数:是否每次需要监听?
     第4个参数:优先级(0)
     第5个参数:监听状态改变后的回调
     */
    
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即将进入Runloop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即将处理timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即将处理Source");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即将睡眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"刚从休眠中唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"即将退出RunLoop");
                break;   
            default:
                break;
        }
    });
    /*
    第1个参数:要给那个RunLoop添加观察者
    第2个参数:添加的Observer对象
    第3个参数:在那种模式下监听
     */
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);

    //  释放对象
    CFRelease(observer);

由于ARC只管理Foundation框架的内容,所以我们在Core Foundation 框架创建的对象必须手动释放。
规律:
凡是带有copy、create、retain等字眼的函数,创建出来的CF对象,都需要在最后做一次release

官方对于RunLoop的解释:

Paste_Image.png

RunLoop处理逻辑,整理:自动释放池的生命周期

RunLoop在进入这个 kCFRunLoopBeforeWaiting时,会对自动释放池销毁


Paste_Image.png

Runloop:在开发中有什么作用?

1.NSTimer
2.ImageView的显示
3.PerformSelector
4.常驻线程
5.自动释放池

PerformSelector

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{  
  /*
     - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSString *> *)modes;
     */
    // 指定模式下进行特定的操作
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"rightPic"] afterDelay:2.0 inModes:@[NSDefaultRunLoopMode]];
}

常驻线程:
默认情况下,一个线程只能使用一次,也就是只能执行一个操作
我们需要做得到就是强引用,保留线程,同时添加Source Or Timer,注:系统只会监测Source Or Timer,不会检查Observer

1.子线程的Runloop需要手动创建
2.子线程的Runloop需要手动开启
3.如果子线程的NSRunLoop没有设置Source Or Timer 那么子线程会立刻关闭。

- (void)ViewDidLoad
{
    [super viewDidLoad];
    SYThread *thread = [[SYThread alloc] initWithTarget:self selector:@selector(show) object:nil];
    self.thread = thread;
    [thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self performSelector:@selector(test) onThread:self.thread withObject:@"时间计算" waitUntilDone:YES modes:@[NSRunLoopCommonModes]];
}

- (void)show
{
    NSLog(@"%s", __func__);
    // 这句话并没有意义,只是保证线程不死
    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
    [[NSRunLoop currentRunLoop] run];
    // 上面线程不死,这句话永远不会打印
    NSLog(@"-------$$$$");
}
- (void)test
{
    NSLog(@"11111");
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容