现在的位置: 首页 > 综合 > 正文

iOS两个线程间嵌套发送同步消息

2013年06月11日 ⁄ 综合 ⁄ 共 4456字 ⁄ 字号 评论关闭

 先上代码,主要逻辑可看注释。最好是直接下载demo再往下看了。demo下载地址:http://download.csdn.net/detail/hursing/5159144

@implementation ViewController 
 
#define kLevelsOfNesting 5 
NSString *const kParameter = @"Parameter"; 
NSString *const kRunLoop = @"RunLoop"; 
 
- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 
    // 在界面上增加一个button,点击后才触发发送消息的流程 
    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 
    [button setTitle:@"click me" forState:UIControlStateNormal]; 
    [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside]; 
    button.frame = CGRectMake(50, 50, 100, 30); 
    [self.view addSubview:button]; 
    [NSThread mainThread].name = @"Main Thread"; 
    // 创建一个worker线程 
    m_thread = [[NSThread alloc] initWithTarget:self selector:@selector(secondaryThreadMain:) object:nil]; 
    [m_thread start]; 
} 
 
- (void)sendSynchronousMessageToTheOtherThread:(NSMutableDictionary*)info 
{ 
    // 不能存NSRunLoop进去,它非线程安全 
    NSValue *runLoop = [NSValue valueWithPointer:CFRunLoopGetCurrent()]; 
    [info setObject:runLoop forKey:kRunLoop]; 
    NSThread *targetThread = [NSThread isMainThread] ? m_thread : [NSThread mainThread]; 
    [self performSelector:@selector(executeOperation:) onThread:targetThread withObject:info waitUntilDone:NO]; 
    CFRunLoopRun();  // 开始一个嵌套的RunLoop 
} 
 
- (void)executeOperation:(NSMutableDictionary*)info 
{ 
    NSNumber* number = [info objectForKey:kParameter]; 
    CFRunLoopRef runLoop = [(NSValue*)[info objectForKey:kRunLoop] pointerValue]; 
    int count = [number intValue]; 
    NSLog(@"count is reduced to %d on %@", --count, [NSThread currentThread].name); 
    if (count) 
    { 
        number = [NSNumber numberWithInt:count]; 
        [info setObject:number forKey:kParameter]; 
        [self sendSynchronousMessageToTheOtherThread:info]; 
    } 
    CFRunLoopStop(runLoop);  // 停止掉RunLoop栈栈顶的RunLoop 
} 
 
- (void)buttonClicked:(UIButton*)button 
{ 
    static bool mainThreadFirst = false; 
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kLevelsOfNesting], kParameter, nil]; 
    mainThreadFirst = !mainThreadFirst; 
    if (mainThreadFirst) 
    { 
        [self sendSynchronousMessageToTheOtherThread:dictionary]; 
        NSLog(@"-------Because of nesting synchonization, this log is printed at the end.-------"); 
    } 
    else 
        [self performSelector:@selector(sendSynchronousMessageToTheOtherThread:) onThread:m_thread withObject:dictionary waitUntilDone:NO]; 
     
} 
 
- (void)secondaryThreadMain:(id)para 
{ 
    @autoreleasepool { 
        [NSThread currentThread].name = @"Secondary Thread"; 
        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; 
        NSThread *thread = [NSThread currentThread]; 
        NSPort *port = [NSMachPort port];       // 阻塞RunLoop 
        [currentRunLoop addPort:port forMode:NSDefaultRunLoopMode]; 
        NSDate *distantFuture = [NSDate distantFuture]; 
         
        while (!thread.isCancelled) 
        { 
            NSAutoreleasePool *pool = [NSAutoreleasePool new]; 
            NSLog(@"RunLoop runs once");     
            [currentRunLoop runUntilDate:distantFuture]; // 这里会阻塞,直到有事件触发 
            [pool drain]; 
        } 
         
        [currentRunLoop removePort:port forMode:NSDefaultRunLoopMode]; 
    } 
} 
 
- (void)didReceiveMemoryWarning 
{ 
    [super didReceiveMemoryWarning]; 
    // Dispose of any resources that can be recreated. 
} 
 
@end 

运行demo工程,点击一下button,看看log输出就懂有什么用了。或者在第61行

CFRunLoopStop(runLoop);  // 停止掉RunLoop栈栈顶的RunLoop 

加个断点,看看堆栈,会在两个线程都看到嵌套的RunLoop.堆栈截图如下:

主线程的堆栈

子线程的堆栈

实用例子
子线程想创建一个UIView,发同步消息到主线程创建(UIKit对象都得主线程操作),主线程创建过程中又需要去子线程执行一段代码做些判断,这又需要主线程发同步消息回子线程;如果使用锁技术,这就是死锁。
RunLoop的东西很复杂,认真看文档最实际了。也可以重点看CFRunLoopRun和CFRunLoopStop两个函数。
附:
关于NSRunLoop和CFRunLoop需要注意的地方:
Warning:  The NSRunLoop class is generally not considered to be thread-safe and its methods should only be called within the context of the current thread. You should never try to call the methods of an NSRunLoop object running in a different thread, as doing so might cause unexpected results.
Although they are not toll-free bridged types, you can get a CFRunLoopRef opaque type from an NSRunLoop object when needed. The NSRunLoop class defines a getCFRunLoop method that returns a CFRunLoopRef type that you can pass to Core Foundation routines. Because both objects refer to the same run loop, you can intermix calls to the NSRunLoop object and CFRunLoopRef opaque type as needed.
Thread safety varies depending on which API you are using to manipulate your run loop. The functions in Core Foundation are generally thread-safe and can be called from any thread. If you are performing operations that alter the configuration of the run loop, however, it is still good practice to do so from the thread that owns the run loop whenever possible.

The Cocoa NSRunLoop class is not as inherently thread safe as its Core Foundation counterpart. If you are using the NSRunLoop class to modify your run loop, you should do so only from the same thread that owns that run loop. Adding an input source or timer to a run loop belonging to a different thread could cause your code to crash or behave in an unexpected way.
【上篇】
【下篇】

抱歉!评论已关闭.