Mac开发之一个Bug引发的三个思考

既然是一个Bug引发的思考,自然要先上Bug,如上动图所示,在输入了空格标题之后,引发一个问题,就是光标依然在文本框内,再敲击键盘依然可以输入改变文本框。


先弹窗后辞掉第一响应者.gif

gif不太好看,再来看下截图


光标仍然存在.jpg
    本人本着不服输的精神对此问题进行了深入研究,结果引发出来三个新的知识点,分别是:
    ①:光标还在文本框内,并不是NSALert窗口引发的Bug,但是NSAlert确实会打断当前RunLoop循环内的事件传递响应链,同时还会影响本次循环内后续的部分UI更新功能,比如说,约束!。重要强调一下,是部分,不是所有!!!这个问题可以用下面的知识点③来解决,当然还有其它办法,详见
    ②:光标还在文本框内,原因是resignFirstResponder并不能真正辞掉NSTextField的第一响应者身份(这点跟iOS不同)。而且,当NStextField在编辑状态中时,设置editable属性为NO,能让NSTextField变为不可编辑,但必须是需要在当前编辑完成之后才可以,下一次的编辑文本框内容才不被请求。即使这两个方法同时被调用,也不能结束当前NSTextField的编辑状态,需要调用NSTextField的abortEditing方法来结束编辑状态
     ③:当调用performSelectorOnMainThread: withObject: waitUntilDone:方法,当最后一个参数传YES时,不仅仅会阻塞当前线程,而且会阻塞当前线程上的当前RunLoop循环。传NO,则分两种情况,一是在主线程上调用,会阻塞主线程,但不会阻塞当前RunLoop循环;二是在子线程上调用,什么都不会阻塞。

===========================我是分割线===========================
枯燥的文字描述已经结束,下面就来讲述我们的探究历程:
先奉上这段问题代码:
'''

  • (IBAction)editTitleBtnClick:(id)sender {
    self.titleLabel.editable = YES;
    [self.titleLabel becomeFirstResponder];

    __weak typeof(self) weakSelf = self;
    NSString *oldTitle = self.titleLabel.stringValue;

    self.mEventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask | NSLeftMouseDraggedMask | NSLeftMouseUpMask handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {

      NSPoint p = [event locationInWindow];
      NSPoint newP = [weakSelf.titleLabel convertPoint:p fromView:nil];
      
    
      //当点击区域在TextField外时
      if (!CGRectContainsPoint(weakSelf.titleLabel.bounds, newP)) {
    
          [NSEvent removeMonitor:self.mEventMonitor];
          weakSelf.mEventMonitor = nil;
          NSString *newTitle = [weakSelf.titleLabel.stringValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
          
          
          if (newTitle.length <= 0){
              weakSelf.titleLabel.stringValue = oldTitle;
              [YHNAlertWindow alertToShowMessageWithButtonTitle:@"确定" AndMessageText:@"请输入分组名称"];
    

// [weakSelf performSelectorOnMainThread:@selector(showMessage) withObject:nil waitUntilDone:NO]; //标记1
}else if(![oldTitle isEqualToString:newTitle]){
[weakSelf editFinishedBtnClick];
}

        [weakSelf.titleLabel resignFirstResponder];
        weakSelf.titleLabel.editable = NO;
        //回复文本框为Label
        return event;//标记2
    }
    return event;
}];

}

  • (void)showMessage{
    [YHNAlertWindow alertToShowMessageWithButtonTitle:@"确定" AndMessageText:@"请输入分组名称"];//标记3
    }
    '''
    一、关于NSTextField辞去第一响应者的问题
    首先一开始,确实以为NSTextField不能正常辞掉第一响应者身份是NSAlert引发的,所以把弹框和辞掉响应者的代码位置调整了一下,然后变成先辞掉第一响应者,再弹窗,下面请看问题Gif


    先辞掉第一响应者后弹窗.gif

再来看下截图


先辞掉第一响应者后弹窗2.jpg

我去,这下问题更大了,不仅光标没去掉,还可以接受文本编辑事件,NSTextField更是没有自适应大小!你妹啊。
没办法,自好再去考虑其他的问题,想来想去,可能是因为我这段代码是在锚点事件回调内部产生的,所以可能会受到点影响,于是就去外面写了一个Demo(代码特别简单,就不再奉上了)测试下,结果意外发现,在没有NSAlert弹窗和锚点事件回调的影响的情况下,NSTextField依然没有辞掉第一响应者身份,光标也依然在,文本也在改变。苍天啊,大地啊,resignFirstResponder不好使了嘛?我以前在iOS里都是这么调的啊。没办法,只好去翻NSTextField的头文件,不出意外,在仔仔细细看了一整遍NSTextField.h文件后,依然一无所获,在此有种想哭的感觉。然后看NSTextField继承,结果在NSControl.h中发现了一个 abortEditing 方法。如或至宝啊,那就拿过来试试,恩,果然好使。
好了,问题解决了,然后经过把下面三个方法各种组合,在NSTextField各种状态下缜密测试,得出下面结论:
1、resignFirstResponder:确实没有使NSTextField辞掉第一响应者身份
2、setEditable:设置editable属性为NO,也确实能让NSTextField变为不可编辑。但设置是,必须确保NSTextField不再编辑状态下,否则当前还是可以继续编辑文本框内容的,下一次的编辑文本框内容才不被接受。
3、abortEditing:强制NSTextField退出文本编辑状态,但是有一个Bug,那就是拼音输入汉字的时候,如果没有点空格就调用此方法,会把一堆字母加单引号输进去,目前除了去controlTextDidEndEditing里做拦截,还真没想到其它好的方法。

    在刚总结完上面结论的时候,发现了第二个问题,那就是我的点击事件被拦截了!!!来,先看看Gif,确定下发生了什么事情:
阻挡事件传递.gif

当我在点击好友,以此来触发弹窗的时候,我点击好友竟然没效果!!!有弹窗,有锚点事件,断点调试不太友好,所以只好苦逼的去打Log,结果发现event被return出去了,也就是说我的代码正常被执行了,却没出来该有的效果。
涉及到这方面的问题,我第一个想到的就是RunLoop,姑且算是死马当活马医吧,于是也用了一个死马当活马医的尝试,就是用performSelectorOnMainThread:withObject:waitUntilDone:函数回调到主线程来弹窗。在主线程里回调主线程去抛弹窗,脑子有问题吧,O__O "…就当是吧,反正试一试又不会怀孕,万一要是解决问题了呢?
恩,还真就解决问题了呢!来,再看gif:


完美.gif

好了,问题是解决了,我们来分析下,为什么回调主线程就能解决问题呢?
二、关于模态窗口打断事件传递响应链的问题分析
首先,我们找到苹果官方文档中对于NSAlert的描述,有这么一段话
'''
An alert appears onscreen either as an app-modal dialog or as a sheet attached to a document window. The methods of the NSAlert class allow you to specify alert level, alert text, button titles, and a custom icon should you require it. The class also lets your alerts display a help button and provides ways for apps to offer help specific to an alert.
'''
很长的描述,但是对我们有用的只有第一句话,就是An alert appears onscreen either as an app-modal dialog or as a sheet attached to a document window.这句,用我的理解翻译过来就是,NSAlert弹出的是一个模态窗口。但是这种窗口老霸道了,当它启动以后,仅它自己可以接收和相应用户操作,无法切换到其他窗口操作,其他窗口也不能接收处理系统内部各种事件。同时,如果在你弹出模态窗口的的RunLoop循环内,当前的系统事件没有传递完的话,也会被打断,无法继续传递下去,但是,关于这,貌似有一个小Bug,请看Gif。

    好了,既然说明了问题所在,那我们就来说明为什么这么做就能解决问题:
    performSelectorOnMainThread:withObject:waitUntilDone:当在在主线程中调用此函数的时候,①如过末尾参数传递YES;则不仅仅会阻塞线程,还会阻塞当前RunLoop循环。这样就相当于把回调的selector方法里的代码放到当前位置执行,这样就只有一个结果,那就是没任何效果。换成白话讲就是,在代码中标记1到标记3的执行顺序是:标记1→标记3→标记2,而且这三处代码在同一个RunLoop循环内执行。
   ②如果末尾参数传NO,则会产生另外一种代码执行顺序:标记1→标记2→标记3。客官,请注意,当前改变的不仅仅是代码的执行顺序,还有另外一层更重要的改变,就是在执行完标记2代码后,并不是立刻执行标记3的代码,而是等到下一次RunLoop循环的时候才会由系统调用执行标记3的代码。说到这里就已经很明白了,由于弹出Alert窗口的RunLoop循环已经不再是原来的循环,等及弹窗的时候原来的循环早已执行完毕,用户的点击事件也早已在那个循环内被传递并且响应完毕,故就能解决上述问题。
   ③上述问题还有多种解决方案,那就是多线程,具体做法就是在主线程内开启异步回调主线程进行弹框,至于原理,跟上述一样,都是利用代码的执行时间差来解决问题,这或许是另外的一种“异步”吧。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • *7月8日上午 N:Block :跟一个函数块差不多,会对里面所有的内容的引用计数+1,想要解决就用__block...
    炙冰阅读 2,441评论 1 14
  • 2017.02.22 可以练习,每当这个时候,脑袋就犯困,我这脑袋真是神奇呀,一说让你做事情,你就犯困,你可不要太...
    Carden阅读 1,251评论 0 1
  • 原创洋酱 在日本有种说法,称2岁左右的孩子为“魔の二歳”,意思是说,两岁的孩子就像个谜团一样无法理解,会做出各种各...
    洋酱阅读 1,024评论 0 3
  • ——安迪 安德鲁斯《上得天堂,下得地狱》 勇气,亦或叫做激情与洒脱更准确。但我更喜欢勇气这个词。激情的背后是疯狂和...
    宁谧随风阅读 177评论 0 0