即使在指针设置为 nil 后 UIMenuItem 仍会继续显示

Posted

技术标签:

【中文标题】即使在指针设置为 nil 后 UIMenuItem 仍会继续显示【英文标题】:UIMenuItem continuing to show up even after the pointer is set as nil 【发布时间】:2014-07-06 17:45:51 【问题描述】:

在下面的代码中,当我选择一行时,会显示一个删除菜单。现在,当我开始画一条新线(保留删除菜单原样)之后,第一个顶部的删除菜单应该会消失。那是因为当我开始绘制第二条线时,uimenucontroller 单例对象会破坏,因为指向它的指针设置为 nil。这是应该发生的事情。但显然不是..当我在drawRect方法中记录指针时,它确实显示为nil..那么为什么菜单仍然显示?这是代码---

 -(void)tap:(UIGestureRecognizer *)sender
NSLog(@"Recognized tap-%@",sender);
CGPoint p= [sender locationInView:self];
selectedLine= [self lineAtPoint:p];//Not going to return anything with just a tap which draws a (non drawable) point and not a line.

[linesInProcess removeAllObjects]; //Avoid adding a new line while performing the tap gesture

if (self.selectedLine) 
    NSLog(@"setting up delete menu");
    [self becomeFirstResponder];

    menu= [UIMenuController sharedMenuController];

    //        create a "Delete" menu item
   deleteItem= [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteLine:)];
    [menu setMenuItems:@[deleteItem]];

    //        Tell the menu where it should come from and show it.
    [menu setTargetRect:CGRectMake(p.x, p.y, 2, 2) inView:self];
    [menu setMenuVisible:YES animated:YES];

else
    //        Hide the menu if no line is selected
    [menu setMenuVisible:NO animated:YES];

[self setNeedsDisplay];
NSLog(@"test");


-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
NSLog(@"%@",NSStringFromSelector(_cmd));

//    Silver challenge
selectedLine=nil;
menu=nil

int i=0;
for (UITouch *touch in touches) 
    NSLog(@"%dth iteration using touch- %@",++i,touch);

    //        Use the touch object [packed in an NSValue] as the key
    NSValue *key= [NSValue valueWithNonretainedObject:touch];
    NSLog(@"key-%@ touch-%@",key,touch);
    //        Create a line for the value
    CGPoint loc= [touch locationInView:self];
    Line *newLine= [[Line alloc] initWithLocation:loc];//Line to be drawn..Plus, setting begin and end pts. happens here.
    NSLog(@"new line- %@",newLine);
    //        put pair in dictionary
    [linesInProcess setObject:newLine forKey:key];





 - (void)drawRect:(CGRect)rect

  NSLog(@"%@",NSStringFromSelector(_cmd));

// Drawing code
CGContextRef context= UIGraphicsGetCurrentContext();
CGContextSetLineCap(context, kCGLineCapRound);//style of the line-endpoints
NSLog(@"delete menu-%@",menu);
//    Draw complete lines in black
[[UIColor blackColor] set];
for (Line *line in self.rootObj.completeLines) 
    NSLog(@"Iterating over array");
    NSLog(@"complete line- %@",line);
    NSLog(@"begin pt.- (%f, %f) and end pt.- (%f, %f)",[line begin].x,[line begin].y,[line end].x,[line end].y);
    CGContextMoveToPoint(context, [line begin].x, [line begin].y);
    CGContextAddLineToPoint(context, [line end].x, [line end].y);
    CGContextStrokePath(context);


//    Draw lines in process in red..
[[UIColor redColor] set];
for (NSValue *v in linesInProcess) 
    NSLog(@"Iterating over dictionary");
    Line *line= [linesInProcess objectForKey:v];
    NSLog(@"line being drawn- %@",line);
    CGContextMoveToPoint(context, [line begin].x, [line begin].y);
    CGContextAddLineToPoint(context, [line end].x,[line end].y);
    CGContextStrokePath(context);


NSLog(@"selected line: %@",self.selectedLine);
//    Change the color of selected line. Redraw that line as green colored.
if (selectedLine) 
    NSLog(@"reseting color");
    [[UIColor greenColor] set];
    CGContextMoveToPoint(context, [selectedLine begin].x, [selectedLine begin].y);
    CGContextAddLineToPoint(context, [selectedLine end].x, [selectedLine end].y);
    CGContextStrokePath(context);

NSLog(@"drawing ended");

我正在通过 Big Nerd Ranch Guide 解决这个问题。在 touchesBegan: 和 tap: 方法中,使用了 'menu'。它是一个 ivar。请注意 touchesBegan: 没有调用 setNeedsDisplay。这只是一个代码 sn-p。在实际程序中,setNeedsDisplay 已从其他方法(如 touchesMoved: 和 touchesEnded:)调用。我只是想知道为什么视图上的菜单会保留,当我在 touchesBegan: 方法中通过将其指针设置为 nil 来破坏它时。

在选择第一行以显示删除菜单期间和之后的控制流程是这样的:

    点击消息传递,菜单对象被创建,所有相关设置都完成。 操作:- 第一行被选中,删除菜单显示在其顶部。 操作:- 然后(保留第一行的删除菜单),正在绘制第二行。 touchesBegan:消息被传递到视图并且菜单指针设置为 nil。 tap 方法可能会被调用,也可能不会被调用,如果调用了,则 if 块不会运行,因此永远不会重新实例化“菜单”。 最终drawRect:消息传递,实现所有线条和设置。 第一行的删除菜单保持原样。 (这就是问题所在)

【问题讨论】:

【参考方案1】:

由于您的控制器是单例,它可能有一个 strong 引用作为其自身实例的 static,并且该实例是在您调用 sharedInstance 时返回给您的指针的内容。 (我假设sharedMenuController 方法是)

+ (id)sharedMenucontroller 
    static UIMenuController *sharedMenuController = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
        sharedMenuController = [[self alloc] init];
    );
    return sharedMyManager;

这意味着 arc 不会通过将指向该单例的静态变量的指针设置为 nil 来清除它。 如果你只是想隐藏它,调用setVisible:NO就可以了,是最简单的方法。

【讨论】:

“什么类型”是什么意思?是 UIMenucontroller 指针...? UIMenuController 内部的静态实现。我不明白。 我已经解决了将 setVisible: 消息传递给菜单对象。但这对我来说似乎不合逻辑:当你放开所谓的唯一一个强指针(菜单)时,如何不破坏对象? 是的,我很抱歉问“什么类型”。我的意思是我找不到仍然指向该对象的 UIMenucontroller 指针。是的,代码很有意义。 sharedMenuController(是一个静态的)将永久指向该对象,直到应用程序被杀死。谢谢。 这是单例的权衡。在 sharedMenuController 被调用一次后,UIMenuController 会保留它自己的一个实例。可能有一些方法可以使 arc 用于释放这些实例,但您不能指望这一点,您必须保护您的代码。尽管如此,在指向单例时将变量设置为 nil 仍然是一个好习惯,如果确实存在这些 arc 方法,则保持对该单例的强引用将使其不会被清理。 我猜想将 menu var 设置为 nil 会降低保留计数(menu 也是一个强指针)。所以在这样做的过程中,即使 UIMenuController 没有被破坏,我们仍然保持低内存占用。我在这里可能大错特错。请协助。我对此比较陌生。

以上是关于即使在指针设置为 nil 后 UIMenuItem 仍会继续显示的主要内容,如果未能解决你的问题,请参考以下文章

free() 在 c 代码和代码中收集垃圾值即使在释放后将指针设置为 NULL 也不起作用

Objective-C在ARC下,把strong指针设置为nil,请问这样不会不释放指针的内存空间?

即使在分配值后显示 nil 的弱可选变量?

使用 json 包解码指针值后是不是需要添加 nil 检查?

哪些编程语言会阻止空指针异常?

即使指针事件设置为无,也使元素可点击