Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

textPublisher crash when using emoji #34

Open
kevinrenskers opened this issue Nov 6, 2020 · 13 comments
Open

textPublisher crash when using emoji #34

kevinrenskers opened this issue Nov 6, 2020 · 13 comments
Labels
bug Something isn't working

Comments

@kevinrenskers
Copy link
Contributor

I have two UITextView's that I am using textPublisher on, to keep them in sync with each other.

Simulator Screen Shot - iPhone 12 Pro - 2020-11-06 at 11 48 58

import UIKit
import Combine
import CombineCocoa

class ViewController: UIViewController {
  @IBOutlet var textView1: UITextView!
  @IBOutlet var textView2: UITextView!

  @Published var value: String? = ""

  private var subscriptions = Set<AnyCancellable>()

  override func viewDidLoad() {
    super.viewDidLoad()

    $value
      .sink {
        self.textView1.text = $0
        self.textView2.text = $0
      }
      .store(in: &subscriptions)

    textView1.textPublisher
      .assign(to: \.value, on: self)
      .store(in: &subscriptions)

    textView2.textPublisher
      .assign(to: \.value, on: self)
      .store(in: &subscriptions)
  }
}

It all works, until you type an emoji: then the app crashes.

2020-11-06 11:49:04.184903+0100 CombineCocoaTest[7757:188602] requesting caretRectForPosition: while the NSTextStorage has oustanding changes {0, 8}
2020-11-06 11:49:04.185022+0100 CombineCocoaTest[7757:188602] !!! _NSLayoutTreeLineFragmentRectForGlyphAtIndex invalid glyph index 7
2020-11-06 11:49:04.191460+0100 CombineCocoaTest[7757:188602] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[NSLayoutManager _fillLayoutHoleForCharacterRange:desiredNumberOfLines:isSoft:] *** attempted layout while textStorage is editing.  It is not valid to cause the layoutManager to do layout while the textStorage is editing (ie the textStorage has been sent a beginEditing message without a matching endEditing.)'
*** First throw call stack:
(
	0   CoreFoundation                      0x00007fff2043a126 __exceptionPreprocess + 242
	1   libobjc.A.dylib                     0x00007fff20177f78 objc_exception_throw + 48
	2   CoreFoundation                      0x00007fff2043a004 -[NSException initWithCoder:] + 0
	3   UIFoundation                        0x00007fff2398ba17 -[NSLayoutManager(NSPrivate) _fillLayoutHoleForCharacterRange:desiredNumberOfLines:isSoft:] + 587
	4   UIFoundation                        0x00007fff23993d19 _NSFastFillAllLayoutHolesForGlyphRange + 1594
	5   UIFoundation                        0x00007fff2399155c -[NSLayoutManager(NSPrivate) _rectArrayForRange:withinSelectionRange:rangeIsCharRange:singleRectOnly:fullLineRectsOnly:inTextContainer:rectCount:rangeWithinContainer:glyphsDrawOutsideLines:rectArray:rectArrayCapacity:] + 750
	6   UIFoundation                        0x00007fff2399456f -[NSLayoutManager(NSPrivate) _rectArrayForRange:withinSelectionRange:rangeIsCharRange:singleRectOnly:fullLineRectsOnly:inTextContainer:rectCount:rangeWithinContainer:glyphsDrawOutsideLines:] + 69
	7   UIFoundation                        0x00007fff239e3541 -[NSLayoutManager rectArrayForCharacterRange:withinSelectedCharacterRange:inTextContainer:rectCount:] + 35
	8   UIKitCore                           0x00007fff24a037e1 -[_UITextContainerView updateInsertionPointStateAndRestartTimer:] + 259
	9   UIFoundation                        0x00007fff2399920e -[NSLayoutManager(NSPrivate) _invalidateLayoutForExtendedCharacterRange:isSoft:invalidateUsage:] + 2247
	10  UIKitCore                           0x00007fff249f0121 -[UITextView setAttributedText:] + 1000
	11  UIKitCore                           0x00007fff249fa518 -[UITextView setText:] + 121
	12  CombineCocoaTest                    0x000000010435e863 $s16CombineCocoaTest20SearchViewControllerC11viewDidLoadyyFySSSgcfU_ + 387
	13  CombineCocoaTest                    0x000000010435ea71 $sSSSgIegg_AAIegn_TR + 33
	14  Combine                             0x00007fff4a6a7566 $s7Combine11SubscribersO4SinkC7receiveyAC6DemandVxF + 54
	15  Combine                             0x00007fff4a6a7c60 $s7Combine11SubscribersO4SinkCy_xq_GAA10SubscriberA2aGP7receiveyAC6DemandV5InputQzFTW + 16
	16  Combine                             0x00007fff4a6b185a $s7Combine16PublishedSubjectC7ConduitC5offeryyxF + 458
	17  Combine                             0x00007fff4a6b2627 $s7Combine16PublishedSubjectC4sendyyxFyAA11ConduitBaseCyxs5NeverOGXEfU_TA + 23
	18  Combine                             0x00007fff4a6e9df4 $s7Combine11ConduitListO7forEachyyyAA0B4BaseCyxq_GKXEKF + 196
	19  Combine                             0x00007fff4a6b13c8 $s7Combine16PublishedSubjectC4sendyyxF + 216
	20  Combine                             0x00007fff4a6d09ed $s7Combine9PublishedV18_enclosingInstance7wrapped7storagexqd___s24ReferenceWritableKeyPathCyqd__xGAHyqd__ACyxGGtcRld__CluisZTf4ngdgd_n + 221
	21  Combine                             0x00007fff4a6d02cb $s7Combine9PublishedV18_enclosingInstance7wrapped7storagexqd___s24ReferenceWritableKeyPathCyqd__xGAHyqd__ACyxGGtcRld__CluisZ + 27
	22  CombineCocoaTest                    0x000000010435d8e1 $s16CombineCocoaTest20SearchViewControllerC5valueSSSgvs + 145
	23  CombineCocoaTest                    0x000000010435d79a $s16CombineCocoaTest20SearchViewControllerC5valueSSSgvpACTk + 138
	24  libswiftCore.dylib                  0x00007fff2f222913 $ss26NonmutatingWritebackBufferCfD + 67
	25  libswiftCore.dylib                  0x00007fff2f421470 _swift_release_dealloc + 16
	26  Combine                             0x00007fff4a712737 $s7Combine11SubscribersO6AssignC7receiveyAC6DemandVq_F + 407
	27  Combine                             0x00007fff4a712a00 $s7Combine11SubscribersO6AssignCy_xq_GAA10SubscriberA2aGP7receiveyAC6DemandV5InputQzFTW + 16
	28  Combine                             0x00007fff4a72d7a5 $s7Combine10PublishersO11ConcatenateV5InnerC13suffixReceiveyAA11SubscribersO6DemandV6OutputQzF + 149
	29  Combine                             0x00007fff4a72d6ff $s7Combine10PublishersO11ConcatenateV5InnerC16SuffixSubscriberV7receiveyAA11SubscribersO6DemandV6OutputQzF + 15
	30  Combine                             0x00007fff4a72d981 $s7Combine10PublishersO11ConcatenateV5InnerC16SuffixSubscriberVy_xq__qd___GAA0F0A2aKP7receiveyAA11SubscribersO6DemandV5InputQzFTW + 17
	31  Combine                             0x00007fff4a735ce0 $s7Combine10PublishersO3MapV5Inner33_5A6CD15A64659A6248DAF677D4BB6188LLV7receiveyAA11SubscribersO6DemandV6OutputQzF + 144
	32  Combine                             0x00007fff4a735ce0 $s7Combine10PublishersO3MapV5Inner33_5A6CD15A64659A6248DAF677D4BB6188LLV7receiveyAA11SubscribersO6DemandV6OutputQzF + 144
	33  Combine                             0x00007fff4a6a8583 $s7Combine16AnySubscriberBoxC7receiveyAA11SubscribersO6DemandV5InputQzF + 35
	34  Combine                             0x00007fff4a6a9b32 $s7Combine13AnySubscriberV7receiveyAA11SubscribersO6DemandVxF + 18
	35  CombineCocoaTest                    0x0000000104376736 $s12CombineCocoa13DelegateProxyC26interceptSelectorPublishery0A003AnyG0VySayypGs5NeverOG10ObjectiveC0F0VFyAE0H10SubscriberVyAhJGcfU_yAHcfU_ + 70
	36  CombineCocoaTest                    0x000000010436ff6f $sSayypGIegg_AAytIegnr_TR + 15
	37  CombineCocoaTest                    0x00000001043762b1 $sSayypGIegg_AAytIegnr_TRTA + 17
	38  CombineCocoaTest                    0x000000010437601f $sSayypGytIegnr_AAIegg_TR + 63
	39  CombineCocoaTest                    0x0000000104375f78 $s12CombineCocoa13DelegateProxyC19interceptedSelector_9argumentsy10ObjectiveC0F0V_SayypGtF + 536
	40  CombineCocoaTest                    0x00000001043760e9 $s12CombineCocoa13DelegateProxyC19interceptedSelector_9argumentsy10ObjectiveC0F0V_SayypGtFTo + 105
	41  CombineCocoaTest                    0x00000001043790f3 -[ObjcDelegateProxy forwardInvocation:] + 115
	42  CoreFoundation                      0x00007fff2043e3f0 ___forwarding___ + 859
	43  CoreFoundation                      0x00007fff20440698 _CF_forwarding_prep_0 + 120
	44  UIFoundation                        0x00007fff23a13e16 -[NSTextStorage processEditing] + 316
	45  UIFoundation                        0x00007fff23a13a60 -[NSTextStorage endEditing] + 83
	46  UIFoundation                        0x00007fff23a13af9 -[NSTextStorage coordinateEditing:] + 53
	47  UIKitCore                           0x00007fff249d2071 -[UITextInputController _insertText:fromKeyboard:] + 395
	48  UIKitCore                           0x00007fff249d2cc8 -[UITextInputController insertText:] + 333
	49  UIKitCore                           0x00007fff249f58f5 -[UITextView insertText:] + 62
	50  UIKitCore                           0x00007fff24450cfe -[UIKeyboardImpl insertText:updateInputSource:] + 254
	51  UIKitCore                           0x00007fff2444a5c0 -[UIKeyboardImpl _performKeyboardOutput:shouldCheckDelegate:] + 1046
	52  UIKitCore                           0x00007fff2444a16a -[UIKeyboardImpl performKeyboardOutput:] + 25
	53  UIKitCore                           0x00007fff244491e7 __55-[UIKeyboardImpl handleKeyboardInput:executionContext:]_block_invoke_2 + 418
	54  UIKitCore                           0x00007fff2447bc3d -[UIKeyboardTaskEntry execute:] + 147
	55  UIKitCore                           0x00007fff2447a8d1 -[UIKeyboardTaskQueue continueExecutionOnMainThread] + 310
	56  Foundation                          0x00007fff2086f973 __NSThreadPerformPerform + 204
	57  CoreFoundation                      0x00007fff203a8845 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
	58  CoreFoundation                      0x00007fff203a873d __CFRunLoopDoSource0 + 180
	59  CoreFoundation                      0x00007fff203a7c1f __CFRunLoopDoSources0 + 248
	60  CoreFoundation                      0x00007fff203a23f7 __CFRunLoopRun + 878
	61  CoreFoundation                      0x00007fff203a1b9e CFRunLoopRunSpecific + 567
	62  GraphicsServices                    0x00007fff2b793db3 GSEventRunModal + 139
	63  UIKitCore                           0x00007fff2466d40f -[UIApplication _run] + 912
	64  UIKitCore                           0x00007fff24672320 UIApplicationMain + 101
	65  libswiftUIKit.dylib                 0x00007fff53c487b2 $s5UIKit17UIApplicationMainys5Int32VAD_SpySpys4Int8VGGSgSSSgAJtF + 98
	66  CombineCocoaTest                    0x000000010436026a $sSo21UIApplicationDelegateP5UIKitE4mainyyFZ + 122
	67  CombineCocoaTest                    0x00000001043601de $s16CombineCocoaTest11AppDelegateC5$mainyyFZ + 46
	68  CombineCocoaTest                    0x00000001043602b9 main + 41
	69  libdyld.dylib                       0x00007fff20257409 start + 1
	70  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[NSLayoutManager _fillLayoutHoleForCharacterRange:desiredNumberOfLines:isSoft:] *** attempted layout while textStorage is editing.  It is not valid to cause the layoutManager to do layout while the textStorage is editing (ie the textStorage has been sent a beginEditing message without a matching endEditing.)'
terminating with uncaught exception of type NSException
CoreSimulator 732.17 - Device: iPhone 12 Pro (F1EB105F-0A1A-4AB4-91C9-728A05B22C85) - Runtime: iOS 14.1 (18A8394) - DeviceType: iPhone 12 Pro
(lldb) 
@kevinrenskers kevinrenskers added the bug Something isn't working label Nov 6, 2020
@kevinrenskers
Copy link
Contributor Author

kevinrenskers commented Nov 6, 2020

When I switch to the good ol' textViewDidChange delegate method, no crash. It also doesn't crash using UITextField, only UITextView.

@freak4pc
Copy link
Member

Can you attach a repro project to save some time? @kevinrenskers
Thanks

@kevinrenskers
Copy link
Contributor Author

Will do.

@kevinrenskers
Copy link
Contributor Author

CombineCocoaTest.zip

@freak4pc
Copy link
Member

Hey @kevinrenskers - I don't think there's any bug with the library per-se here, since if you replace it with a print, everything works:

image

I think the real problem is that you're introducing reentrancy - you are assigning a value of something that effects that very same value. In this specific case the crash is:

Attempted layout while textStorage is editing. It is not valid to cause the layoutManager to do layout while the textStorage is editing (ie the textStorage has been sent a beginEditing message without a matching endEditing.)

Which seems reasonable (you are editing and writing, e.g. causing a layout at the exact same time or at a timing that causes a reentrancy/race).

@kevinrenskers
Copy link
Contributor Author

But it works with all text just fine, just not emoji.

@freak4pc
Copy link
Member

Yup, I imagine drawing grapheme clusters is a different task that requires different handling. Regardless this error comes from NSTextStorage and not from CombineCocoa itself. I'm not too sure if I have an idea for something that could be done here.

@kevinrenskers
Copy link
Contributor Author

I wonder if it's something in didProcessEditingRangeChangeInLengthPublisher.

Like I said, if you build the exact same functionality using good old textViewDidChange, it doesn't crash.

import UIKit
import Combine
import CombineCocoa

class SearchViewController: UIViewController {
  @IBOutlet var textView1: UITextView!
  @IBOutlet var textView2: UITextView!

  @Published var value: String? = ""

  private var subscriptions = Set<AnyCancellable>()

  override func viewDidLoad() {
    super.viewDidLoad()

    $value
      .sink {
        self.textView1.text = $0
        self.textView2.text = $0
      }
      .store(in: &subscriptions)
  }
}

extension SearchViewController: UITextViewDelegate {
  func textViewDidChange(_ textView: UITextView) {
    value = textView.text
  }
}

@freak4pc
Copy link
Member

There is nothing reentrant about this code though. I don't think the delegate gets called when the sink hits the .text setter, but it does happen with assign for some reason.

@kevinrenskers
Copy link
Contributor Author

So, it's not the assigning that's the problem (I still have the $value.sink code), but reading the value that seems to trip it up - the textPublisher.

@kevinrenskers
Copy link
Contributor Author

It also doesn't crash if you replace both UITextView with a UITextField, which don't use the didProcessEditingRangeChangeInLengthPublisher, which is why my mind is going in that direction.

@freak4pc
Copy link
Member

freak4pc commented Nov 22, 2020 via email

@ZvonimirMedak
Copy link

With the addition on my PR #73, the example @kevinrenskers provided doesn't crash but you do get a circular dependency, and the app loops since you're saving the values from the textView.textPublisher to another publisher and then assigning the same value to the textView again.
Screenshot 2022-07-07 at 16 01 16
Why the textView.textPublisher gets called after adding an emoji is that the emoji uses a different font and it will send the editingAttributes action to the didProcessEditingRangeChangeInLengthPublisher.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants