应用程序在快速文本绘制过程中挂起

时间:2015-02-20 12:00:39

标签: ios multithreading uitableview nsoperationqueue terminal-emulator

我正在使用终端仿真应用程序通过telnet连接到我的unix服务器,我正在使用tableview(一个单元格用于一个文本行)。令我烦恼的是,当服务器发送大量文本数据以响应某些命令时,这会使应用程序响应(延迟)触摸屏幕上的任何按钮或任何手势,因为绘图在主线程中,不允许其他进程工作,除非它完了。

解决这个问题应该是什么样的完美方法,我是否需要OperationQueue,我对此并不了解。

我需要像绘图一样的东西,我可以顺利地与app交互(这可以暂停绘图)。

我认为drawRect我需要更长的时间,实际上我正在做的不是reloadData或ReloadTableCell,我在tableview的文本行单元格中有一个可变的属性字符串,并且需要添加文本时对于那个单元格,我只是替换该可变属性字符串中的字符,并为调用drawRect的单元格调用setNeedsDisplay。在drawRect中,我的代码如下 -

-(void)drawRect:(CGRect)rect
{
    _cursorView.frame = CGRectMake(([_delegate cursorXInTextLine:self] - 1 )* 6, 0, 6, 10);

    [self drawText:0 yPosition:0 canvasWidth:self.bounds.size.width canvasHeight:10];
}


- (void)drawText:(CGFloat)xPosition yPosition:(CGFloat)yPosition canvasWidth:(CGFloat)canvasWidth canvasHeight:(CGFloat)canvasHeight
{
    //Draw Text
    CGRect textRect = CGRectMake(xPosition, yPosition, canvasWidth, canvasHeight);

    [self.textLineText drawInRect: textRect];
}

执行textReplacement的其他方法如下 -

-(void)initializeWithStringChar:(NSString*)charString
{
    NSInteger len = charString.length;

    while (len > 0) {

        //length can be displayed.
        NSInteger cutLength = totalCols - cursorX + 1;

        if (len < cutLength) {
            cutLength = len;
        }

        NSString *str = [charString substringToIndex:cutLength];
        [self placeTextInCurrentTextLine:str :YES :YES :YES];

        charString = [charString substringFromIndex:cutLength];
        len -= cutLength;
    }
}
-(void)placeTextInCurrentTextLine:(NSString *)text :(BOOL)needsToReplace :(BOOL)useCurrentCharAttrs :(BOOL)adjustCursor{

    TextLineCell *textLineCell = (TextLineCell *)[self.telnet_TableView cellForRowAtIndexPath:currentLineIndexPath];

    if (cursorX > totalCols) {
        if (self.autowrap == YES) {
            [self goNewLineAndResetCursor:YES];

            textLineCell = (TextLineCell *)[self.telnet_TableView cellForRowAtIndexPath:currentLineIndexPath];
        }
        else
            cursorX--;
    }

    NSAttributedString *attrText;

    if (useCurrentCharAttrs) {
        //Use current chars attributes
        attrText = [[NSAttributedString alloc]initWithString:text attributes:currentCharAttrDict];
    }
    else{
        //Use default chars attributes
        attrText = [[NSAttributedString alloc]initWithString:text attributes:defaultCharAttrDict];
    }

    NSMutableAttributedString *attrLineText = [telnet_ScreenData objectAtIndex:currentLineIndexPath.row];

    if (needsToReplace) {
        //Replace chars with existing
        [attrLineText replaceCharactersInRange:NSMakeRange(cursorX - 1, text.length) withAttributedString:attrText];
    }
    else{
        //Insert and shift chars
        [attrLineText insertAttributedString:attrText atIndex:cursorX - 1];

        //shift out the last characters
        attrLineText = [[NSMutableAttributedString alloc]initWithAttributedString:[attrLineText attributedSubstringFromRange:NSMakeRange(0, totalCols)]];
    }

    [telnet_ScreenData replaceObjectAtIndex:currentLineIndexPath.row withObject:attrLineText];

    if (adjustCursor) {
         cursorX = cursorX + (int)text.length;
    }

    if (textLineCell) {
        textLineCell.textLineText = attrLineText;

        [textLineCell setNeedsDisplay];
    }
    else{
        [_telnet_TableView scrollToRowAtIndexPath:homeIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
    }
}

1 个答案:

答案 0 :(得分:1)

编辑:placeTextInCurrentTextLine::::(方法名称不好)通过调用cellForRowAtIndexPath:直接编辑表格视图单元格。这迫使系统创建一个可能不在屏幕上的单元格(甚至可能弹出缓存中的单元格)。如果生成了大量单元格,这实际上可能会强制创建否则永远不需要的单元格。数据源的工作是被动,并在请求时配置单元格。

当文本出现时,您应该找出哪些文本在哪些行上,然后调用insertRowsAtIndexPaths:withRowAnimation:告诉表视图有新数据。然后等待它询问您特定的行,当它发生时,为它配置一个单元格。您没有维护表视图单元格的集合。您只需配置您要求的一个单元格,理想情况下使用dequeueReusableCellWithIdentifier:forIndexPath:从现有单元格中删除现有单元格。在任何给定的时间,应该只存在与屏幕上的行一样多的单元格(可能还有一些用于滚动)。有关详细信息,请参阅Populating a Dynamic Table View with Data


tableview是一个有趣的解决方案。我会选择UITextView,因为它更适合处理大型文本块。但谁知道,tableview可能是一个相当聪明的解决方案(我可以看到它甚至可能比我的方式更好)。

所有性能问题的主要问题是实际问题发生的地方。您需要先使用Time Profiler运行Instruments,然后查看您在哪里花费时间。然后你可以研究如何改进它。

也就是说,有几个相当明显的事情你可能做错了。首先,您必须正确管理表格单元格重用。在cellForRowAtIndexPath:中,您应该获取以前使用过的已离开屏幕的单元格(使用dequeueReusableCellWithIdentifier:),然后只需使用新数据重新配置它。您应该在每次调用cellForRowAtIndexPath:时创建一个新单元格。你应该让你的单元格尽可能简单(你可能只需要一个UILabel修改文本)。

您还必须非常小心,以避免致电reloadData。由于您将继续添加到最后,因此您需要致电insertRowsAtIndexPaths:withRowAnimation:。这种方式UITableView不必重新计算所有内容。

在后端,如果您发现将传入流量解析为行是昂贵的,那么您一定要将解析移出主队列。原则上,NSOperationQueue是“最高抽象”,通常使用可用的最高抽象是一个很好的建议。也就是说,我发现大多数人“获取”调度队列比操作队列更容易。许多操作队列API都基于pre-GCD并发系统,因此大部分文档都涵盖了当今很少使用的主题。我建议您阅读Dispatch Queues section of the Concurrency Programming Guide以熟悉它们。管理调度队列是iOS开发人员的一项关键且非常常见的技能。

但是第一步,就像表现一样,是对乐器感到满意,并确保你知道你的节目实际发生了什么。