使用Objective-C实现自定义的RunLoop

本文介绍了一个自定义的RunLoop实现,该实现支持定时器事件和消息事件的处理,并使用C11原子操作确保线程安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们知道,由Apple主推的伴随Objective-C的运行时库——Foundation,其核心部分就是NSRunLoop类了。Foundation库将多线程封装得特别好,我们只需通过NSObject的performSelector:onThread:withObject:waitUntilDone:方法即可将一条消息分发到指定的一个线程上执行;通过使用performSelector:withObject:afterDelay:即可将指定的消息延迟指定的时间后发送给消息接收者之行。

然而,在不少已有的框架体系下含有自己的消息循环,比如GTK+3.0中就用了glib中的g_main_loop函数作为消息循环等待函数,此时我们使用NSRunLoop的run方法就只会接收GNUStep库所定制好的消息,而无法接收到GTK库中的事件。所以这里,我将提供一个简短的代码,以抛砖引玉的方式给各位呈现如何实现一个简单的RunLoop。以下代码既可以在macOS下运行,也可以在各个Unix BSD/Linux下执行,不过需要使用LLVM Clang编译器以及GCD库。具体安装方法可参考此贴:http://blog.youkuaiyun.com/zenny_chen/article/details/52507022

以下这段代码仅实现了自己的performSelector:withObject:afterDelay:方法以及performSelectorOnMainThread:withObject:方法。等我完成了对NSThread改造之后就可以实现performSelector:onThread:withObject:方法了。

//
//  main.m
//  ZCRunLoop
//
//  Created by Zenny Chen on 2016/11/21.
//  Copyright © 2016年 GreenGames Studio. All rights reserved.
//

@import Foundation;

#include <sys/event.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#include <semaphore.h>

#include <stdbool.h>
#include <stdint.h>
#include <stdatomic.h>
#include <stdalign.h>


#if defined(__i386__) || defined(__x86_64__)
#define CPU_PAUSE()     asm("pause")
#elif defined(__arm__) || defined(__arm64__)
#define CPU_PAUSE()     asm("yield")
#else
#define CPU_PAUSE()
#endif


#pragma mark - ZCEvent

/** 自己定制的事件类 */
@interface ZCEvent : NSObject

/** 事件响应时,将消息所发送给的目标 */
@property (nonatomic, retain) id target;

/** 事件响应时,对目标所发送的消息,这里使用NSValue其实是对SEL类型的封装 */
@property (nonatomic, retain) NSValue *message;

/** 将消息发送给目标时,随带的用户自定义参数 */
@property (nonatomic, retain) id parameter;

@end

@implementation ZCEvent

@synthesize target, message, parameter;

- (void)dealloc
{
    self.target = nil;
    self.message = nil;
    self.parameter = nil;
    
    [super dealloc];
}

@end


#pragma mark - ZCTimerEvent

/** 自己定制的定时器事件类 */
@interface ZCTimerEvent : ZCEvent
{
@public
    
    /** 当前定时器到期时间 */
    struct timeval expireDate;
}

@end

@implementation ZCTimerEvent

@end


#pragma mark - ZCRunLoop

/** 自己定制的消息循环类 */
@interface ZCRunLoop : NSObject

/** 获取ZCRunLoop单例实例对象 */
+ (instancetype)runLoop;

/**
 * 添加定时器事件
 * @param target 消息接收者
 * @param selector 消息签名
 * @param param 用户自定义参数
 * @param timeoutInterval 超时时间,单位为秒
 */
- (void)addTimerEvent:(id)target message:(SEL)selector userParam:(id)param timeout:(NSTimeInterval)timeoutInterval;

/**
 * 添加消息事件,用于在当前消息队列中处理
 * @param target 消息接收者
 * @param selector 消息签名
 * @param param 用户自定义参数
 */
- (void)addMessageEvent:(id)target message:(SEL)selector param:(id)param;

/** 运行消息循环 */
- (void)run;

@end

static ZCRunLoop *sMainRunLoop;

@implementation ZCRunLoop
{
@private
    
    /** 用于对时间事件队列操作的循环锁的原子标志 */
    atomic_bool alignas(64) mTimerEventFlag;
    
    /** 定时器事件队列 */
    NSMutableArray<ZCTimerEvent*> *mTimerEvents;
    
    /** 用于即将处理的定时器事件队列 */
    NSArray<ZCTimerEvent*> *mTimerEventsForProcessing;
    
    /** 消息事件队列 */
    NSMutableArray<ZCEvent*> *mMessageEvents;
    
    /** 信号量,当当前没有消息时,将当前线程阻塞 */
    sem_t *mSemaphore;
    
    /** 当前即将到期的事件索引 */
    int mMinimumIntervalTimeEventIndex;
    
    /** 用于标识当前消息循环是否即将退出 */
    volatile BOOL mWillBeTerminated;
    
    /** 用于对消息事件队列操作的循环锁的原子标志 */
    atomic_bool alignas(64) mMessageEventFlag;
}

+ (instancetype)runLoop
{
    return sMainRunLoop;
}

/** 定时器响应函数 */
static void alarm_wakeup(int i)
{
    [[ZCRunLoop runLoop] addCurrentTimerEventsToProcess];
}

- (instancetype)init
{
    self = [super init];
    
    atomic_init(&mTimerEventFlag, true);
    atomic_init(&mMessageEventFlag, true);
    
    mTimerEvents = [[NSMutableArray alloc] initWithCapacity:128];
    mMessageEvents = [[NSMutableArray alloc] initWithCapacity:128];
    mMinimumIntervalTimeEventIndex = -1;
    mSemaphore = sem_open("My semaphore", O_CREAT, S_IRUSR | S_IWUSR, 0);
    
    signal(SIGALRM, alarm_wakeup);
    
    return self;
}

- (void)dealloc
{
    [mTimerEvents removeAllObjects];
    [mTimerEvents release];
    
    [mMessageEvents removeAllObjects];
    [mMessageEvents release];
    
    if(mTimerEventsForProcessing != nil)
        [mTimerEventsForProcessing release];
    
    sem_close(mSemaphore);
    
    NSLog(@"ZCRunLoop deallocated!");
    
    [super dealloc];
}

- (void)addMessageEvent:(id)target message:(SEL)selector param:(id)param
{
    ZCEvent *event = [ZCEvent new];
    event.target = target;
    event.message = [NSValue valueWithPointer:selector];
    event.parameter = param;
    
    while(!atomic_exchange(&mMessageEventFlag, false))
        CPU_PAUSE();
    
    [mMessageEvents addObject:event];
    [event release];
    
    atomic_store(&mMessageEventFlag, true);
    
    sem_post(mSemaphore);
}

- (void)addTimerEvent:(id)target message:(SEL)selector userParam:(id)param timeout:(NSTimeInterval)timeoutInterval
{
    ZCTimerEvent *newEvent = [ZCTimerEvent new];
    newEvent.target = target;
    newEvent.message = [NSValue valueWithPointer:selector];
    newEvent.parameter = param;
    
    struct timeval specDate;
    
    typeof(specDate.tv_sec) secInterval = (typeof(specDate.tv_sec))timeoutInterval;
    typeof(specDate.tv_usec) usecInterval = (timeoutInterval - (double)secInterval) * 1000000.0;
    specDate.tv_sec = secInterval;
    specDate.tv_usec = usecInterval;
    
    // 每添加了一个新的事件,说明当前run-loop一直会处于运行状态
    mWillBeTerminated = NO;
    
    // 上旋锁
    while(!atomic_exchange(&mTimerEventFlag, false))
        CPU_PAUSE();
    
    struct timeval currTime;
    gettimeofday(&currTime, NULL);
    
    // 将specDate设置为到期日期,根据指定的超时时间
    timeradd(&specDate, &currTime, &specDate);
    
    newEvent->expireDate = specDate;
    
    [mTimerEvents addObject:newEvent];
    [newEvent release];
    
    struct itimerval tout_val;
    tout_val.it_interval.tv_sec = 0;
    tout_val.it_interval.tv_usec = 0;
    tout_val.it_value.tv_sec = 0;
    tout_val.it_value.tv_usec = 0;
    
    if(mMinimumIntervalTimeEventIndex == -1)
    {
        // 用于处理加入第一个事件的时候
        mMinimumIntervalTimeEventIndex = 0;
        tout_val.it_value.tv_sec = secInterval;
        tout_val.it_value.tv_usec = usecInterval;
    }
    else
    {
        ZCTimerEvent *minEvent = mTimerEvents[mMinimumIntervalTimeEventIndex];
        
        // 将当前离到期日期最近的日期与新添加的到期日期进行比较
        if(timercmp(&minEvent->expireDate, &specDate, >))
        {
            // 倘若当前离到期日期最近的日期比新添加的到期日期要大,
            // 那么将新添加的到期日期作为最小超时时间,并重新设定定时器的值
            mMinimumIntervalTimeEventIndex = (int)(mTimerEvents.count - 1);
            tout_val.it_value.tv_sec = secInterval;
            tout_val.it_value.tv_usec = usecInterval;
        }
    }
    
    if((tout_val.it_value.tv_sec > 0 || tout_val.it_value.tv_usec > 0))
        setitimer(ITIMER_REAL, &tout_val, NULL);
    
    // 解旋锁
    atomic_store(&mTimerEventFlag, true);
}

- (void)addCurrentTimerEventsToProcess
{
    // 上旋锁
    while(!atomic_exchange(&mTimerEventFlag, false))
        CPU_PAUSE();
    
    if(mTimerEventsForProcessing != nil)
        [mTimerEventsForProcessing release];
    
    mTimerEventsForProcessing = [[NSArray alloc] initWithArray:mTimerEvents];
    
    // 解旋锁
    atomic_store(&mTimerEventFlag, true);
}

/** 处理定时器事件 */
- (void)processTimerHandler
{
    struct timeval currTime;
    gettimeofday(&currTime, NULL);
    
    @autoreleasepool {
        
        NSMutableArray *clearArray = [NSMutableArray arrayWithCapacity:128];
        
        // 遍历每一个事件,看看当前是否即将或已经到期的事件,整个处理过程无需上锁
        for(ZCTimerEvent *event in mTimerEventsForProcessing)
        {
            struct timeval eventTime;
            timersub(&event->expireDate, &currTime, &eventTime);
            
            // 计算当前事件的到期日期离当前日期相差多少微秒
            __auto_type interval = eventTime.tv_sec * 1000000L + eventTime.tv_usec;
            
            // 这里设定小于10微秒的事件作为到期时间
            if(interval < 10)
            {
                // 执行相应的消息发送
                [event.target performSelector:(SEL)[event.message pointerValue] withObject:event.parameter];
                
                // 准备将当前事件移除
                [clearArray addObject:event];
            }
        }
        
        [mTimerEventsForProcessing release];
        mTimerEventsForProcessing = nil;
        
        // 上旋锁
        while(!atomic_exchange(&mTimerEventFlag, false))
            CPU_PAUSE();
        
        [mTimerEvents removeObjectsInArray:clearArray];
        
        const NSUInteger length = mTimerEvents.count;
        
        // 如果事件队列中还存有事件,那么挑选出最小的到期时间,并重新设置定时器
        if(length > 0)
        {
            mMinimumIntervalTimeEventIndex = 0;
            struct timeval minimumTime = mTimerEvents[0]->expireDate;
            
            for(int i = 1; i < length; i++)
            {
                if(timercmp(&minimumTime, &mTimerEvents[i]->expireDate, >))
                {
                    mMinimumIntervalTimeEventIndex = i;
                    minimumTime = mTimerEvents[i]->expireDate;
                }
            }
            
            struct itimerval tout_val;
            tout_val.it_interval.tv_sec = 0;
            tout_val.it_interval.tv_usec = 0;
            timersub(&minimumTime, &currTime, &tout_val.it_value);
            
            setitimer(ITIMER_REAL, &tout_val, NULL);
        }
        else    // 否则即将退出当前消息循环
            mWillBeTerminated = YES;
    }
    
    // 解旋锁
    atomic_store(&mTimerEventFlag, true);
}

- (void)processMessages
{
    while(!atomic_exchange(&mMessageEventFlag, false))
        CPU_PAUSE();
    
    // 处理当前每一个消息事件
    for(ZCEvent *event in mMessageEvents)
    {
        // 将消息事件放到定时器事件队列中处理,默认延迟100微秒
        [self addTimerEvent:event.target message:event.message.pointerValue userParam:event.parameter timeout:0.0001];
    }
    
    // 最后将所有消息事件全都移除
    [mMessageEvents removeAllObjects];
    
    atomic_store(&mMessageEventFlag, true);
}

- (void)run
{
    // 如果当前不退出消息循环,则挂起当前线程
    while(!mWillBeTerminated)
    {
        sem_wait(mSemaphore);
        
        // 处理当前的定时器消息
        [self processTimerHandler];
        
        // 唤醒一次之后处理所有消息事件
        [self processMessages];
    }
}

@end


#pragma mark - ZCObject

@interface ZCObject : NSObject

@end

@implementation ZCObject

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
{
    [[ZCRunLoop runLoop] addTimerEvent:self message:aSelector userParam:anArgument timeout:delay];
}

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg
{
    // 延迟100微秒在主线程的run-loop中发送消息
    [[ZCRunLoop runLoop] addMessageEvent:self message:aSelector param:arg];
}

@end

#pragma mark - test

@interface MyObject : ZCObject

- (void)hello:(NSNumber*)delaySeconds;
- (void)hey:(NSNumber*)delaySeconds;

@end


@implementation MyObject

- (void)hello:(NSNumber*)delaySeconds
{
    NSLog(@"Hello, world! delayed: %@ seconds", delaySeconds);
}

- (void)hey:(NSNumber*)delaySeconds
{
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^void(void) {
        [self performSelectorOnMainThread:@selector(hello:) withObject:delaySeconds];
    });
}

- (void)hi:(NSNumber*)delaySeconds
{
    NSLog(@"Hi, The following operation will be delayed for %@ seconds", delaySeconds);
    
    [self performSelector:@selector(hello:) withObject:delaySeconds afterDelay:delaySeconds.doubleValue];
}

- (void)dealloc
{
    NSLog(@"MyObject deallocated!");
    
    [super dealloc];
}

@end


int main(int argc, const char * argv[])
{
    sMainRunLoop = [ZCRunLoop new];
    
    MyObject *obj = [MyObject new];
    [obj performSelector:@selector(hello:) withObject:@2.0 afterDelay:2.0];
    
    [obj performSelector:@selector(hello:) withObject:@1.5 afterDelay:1.5];
    
    [obj performSelector:@selector(hello:) withObject:@5.0 afterDelay:5.0];
    
    [obj performSelector:@selector(hello:) withObject:@4.0 afterDelay:4.0];
    
    [obj performSelector:@selector(hello:) withObject:@3.0 afterDelay:3.0];
    
    [obj performSelector:@selector(hi:) withObject:@8.5 afterDelay:8.5];
    
    [obj performSelector:@selector(hey:) withObject:@7.75 afterDelay:7.75];
    
    [obj performSelector:@selector(hello:) withObject:@22.0 afterDelay:22.0];
    
    [obj performSelectorOnMainThread:@selector(hey:) withObject:@0.1];
    
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^void(void) {
        [obj performSelectorOnMainThread:@selector(hello:) withObject:@6.5];
        [obj performSelectorOnMainThread:@selector(hello:) withObject:@6.7];
    });
    
    [obj release];
    
    NSLog(@"Running...");
    
    [[ZCRunLoop runLoop] run];
    
    [sMainRunLoop release];
    
    return 0;
}

以上代码使用了C11标准中的原子操作对定时器事件队列与消息事件队列进行操作。由于这些容器可能会有用户线程中的例程对其进行操作,所以我们必须保证每一步修改都应该是原子的。代码比较简单,注释也比较详细,各位可以慢慢欣赏。这里比较复杂的就是processTimerHandler这个方法,我在定时器处理例程中将当前处理事件全都放在另一个准备处理的数组中,这样确保在后续操作的时候不会因为外部新增事件列表而引发冲突,所以在处理用户事件的前后也不需要上锁,使得处理更加快捷。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值