【问题标题】:Blocking communication with ios accessory阻止与 ios 附件的通信
【发布时间】:2026-02-16 14:55:02
【问题描述】:

Apple 似乎建议使用运行循环与外部附件进行数据通信。但是,除非我遗漏了什么,否则 runloop 不太适合某些类型的通信。

我们有一个 -experimental- 附件,我们需要向其发送任意数量的字节(最多 1024 个字节),然后是附件处理该数据(可变延迟,例如 1 毫秒到 1000 毫秒之间),随后是来自附件的可变长度响应(最多 1024 个字节)。

我们想开发一个用于与配件通信的静态库(框架)。基本上,这个库会有一个函数,它接受一个 NSArray 或 NSMutableArray 作为输入,并返回包含响应的 NSArray 或 NSMutableArray。

问题在于推荐的runloops策略不太适合这种类型的应用程序。在静态库函数中,在准备好要传输的数据并调度传输之后,我们不得不进入某种“等待”状态。但是,这种等待状态不能基于轮询方法(例如等待接收路由设置的 -synchronized- 变量),因为这样接收例程永远不会执行(因为它们在同一个线程上) .

如果我们不使用runloops,那么我们就无法知道何时读取数据,因为我们不知道数据何时到达。

关于如何解决这个问题的任何想法或建议?有没有例子?

【问题讨论】:

    标签: ios objective-c cocoa-touch external-accessory


    【解决方案1】:

    这不是 runLoop 或 ExternalAccessories 问题。 这是一个日常的 OOP 问题。

    最好的方法是创建一个可以写入 outputStream 并等待响应的 Communication 对象。 使用@protocols 来做到这一点! (事件监听器驱动的过程)

    试试这个:

    首先,您必须将输入/输出流附加到 runLoop:

    [[session inputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [[session inputStream] open];
    [[session outputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [[session outputStream] open];
    

    成为他们的代表:

    [[session outputStream] setDelegate:self];
    [[session inputStream] setDelegate:self];
    

    一旦你成为委托,你必须实现这个方法:

    -(void)stream:handleEvent:{};
    

    这是将数据写入流的命令:

    /* data is a NSData containing data to transmit. */
    [[session outputStream] write:(uint8_t *)[data bytes] maxLength:[data length]];
    

    这是一个示例代码,(一旦你创建了会话,而我们期望的答案是一个字节):

    在 Comm.h 中:

    /* Define your protocol */
    @protocol CommDelegate <NSObject>
        -(void)byteReceived: (char) byte;
    @end
    
    @interface Comm <NSObject> {
        [...]
        id<CommDelegate> delegate;
    }
    @end
    
    @property (nonatomic, retain) id<CommDelegate> delegate;
    

    在 Comm.m 中:

    @implementation Comm
    
    [...]
    -(id)init {
        [...]
        delegate = nil;
        [...]
    }
    
    -(void)write: (NSData *) data {
        [[session outputStream] write:(uint8_t *)[data bytes] maxLength:[data length]];
    }
    
    -(void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)_event {
        switch (_event)
        {
            case NSStreamEventHasBytesAvailable:
                /* This part will be executed every time your rx buffer contains at least 1 byte */
                switch(state) {
                    uint8_t ch;
                    /* Read byte per byte */
                    [stream read:&ch maxLength:1];
                    /* now ch contains a byte from your MFI device
                    ** and 'read' function decrease the length of the rx buffer by -1 */
    
                    /* Now you can notify this to the delegate
                    */
                    if(self.delegate != nil)
                        [delegate byteReceived: ch];
                }
                break;
        }
    }
    

    your_app_controller.h:

    @interface MyApp : UIViewController <CommDelegate> {
        Comm comm;
    }
    @end
    

    your_app_controller.m:

    @implementation MyApp
    
    -(id)init {
        [...]
        comm = [[Comm alloc] init];
        [comm setDelegate: self];   /* Now your thread is listening your communication. */
    }
    
    -(void)write {
        byte out = 'X';
        [comm write: [NSData dataWithBytes: &out length: 1]];
    }
    
    -(void)bytereceived:(char)reply {
        if(reply == 'Y') {
            [self write];
            //[self performSelectorInBackground:@selector(write) withObject:nil]; IT'S BETTER!!!
        }
    
    }
    
    @end
    

    希望这会有所帮助!

    【讨论】:

    • 但在您的示例中,您在“currentRunLoop”上调度流,而不是在另一个线程上调度它们。读写操作似乎也在主线程上执行。
    • 一个“runloop”在线程空闲时间运行并且它没有阻塞......在这种情况下,当一个字节变得可用时,会触发一个通知(NSStreamEventHasBytesAvailable)。我可以使用“[stream read:&ch maxLength:1];”读取字节。此操作速度快于从向量中读取一个字节。是的,当然字节是在主线程中读取的,但它没有阻塞。
    • 感谢您的回答!无论如何,我没有说明我评论背后的原因,抱歉:P 我不是说你错了或者你的解决方案有问题,而只是说评论'现在你可以通知委托人(你的主线程) ' 有点误导,因为您已经在主线程上:)