Post

Replies

Boosts

Views

Activity

Reply to NSInternalInconsistencyException
Ok, I understand why this happens. I think there isn't anything you can do about this when it happens infrequently like this. There are certain cases in which you can make this worse, though, and you should avoid doing those. As far as I understand, this happens because: UITextFields contain an internal TextKit view called a _UITextLayoutFragmentView. These _UITextLayoutFragmentViews change on a regular basis when you're typing into them. Especially if they have text correction squigglies in them. If a _UITextLayoutFragmentView's layer that is in the CALayer hierarchy gets removed from the layer hierarchy while you hold a pointer to it, if you attempt to call drawInContext: on this pointer that you hold later, fire will come forth from the NSTextLineFragment and consume you. CoreAnimation queues stuff up to do later, and may render a layer after it has been removed from the layer hierarchy. In this case, by no fault of your own, fire comes forth from the NSTextLineFragment and consumes you. You can make this worse if you hold pointers to a _UITextLayoutFragmentView's layer and try to render them yourself. In that case, fire will come forth from the NSTextLineFragment and consume you, and you will die after the NSInternalInconsistencyException. Trying to catch the NSInternalInconsistencyException is a bad idea. I do not recommend it. You could swizzle -[NSTextLineFragment _drawTexCorrectionMarker:characterRange:atOrigin:graphicsContext:] to check for a zero characterRange and return rather than asserting; but, as saith the ancients, -[UIViewController attentionClassDumpUser:yesItsUsAgain:althoughSwizzlingAndOverridingPrivateMethodsIsFun:itWasntMuchFunWhenYourAppStoppedWorking:pleaseRefrainFromDoingSoInTheFutureOkayThanksBye:]. I have written a minimal reproducer, but it seems I cannot link to it here, or attach it. I will e-mail it to Quinn. The essence of it is as follows: @interface ViewController () @property (nonatomic, weak) IBOutlet UITextField *textField; @end @implementation ViewController - (void)renderTextfield { NSMutableArray <CALayer *> *layers = [NSMutableArray new]; void __weak __block (^splatter_w)(CALayer *layer); void __block (^splatter)(CALayer *layer) = ^(CALayer *layer) { [layers addObject:layer]; for (CALayer *sub in layer.sublayers) { splatter_w(sub); } }; splatter_w = splatter; splatter(_textField.layer); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^() { NSMutableData *data = [NSMutableData dataWithCapacity:16]; CGDataConsumerRef consumer = CGDataConsumerCreateWithCFData((CFMutableDataRef)data); CGRect r = CGRectMake(0, 0, 1000, 1000); CGContextRef ctx = CGPDFContextCreate(consumer, &r, nil); CGDataConsumerRelease(consumer); CGPDFContextBeginPage(ctx, nil); for (CALayer *layer in layers) { if ([NSStringFromClass(layer.delegate.class) hasSuffix:@"FragmentView"]) { NSLog(@"LAYER: %@", layer); // note: this is constantly changing when you type... } [layer drawInContext:ctx]; } CGPDFContextEndPage(ctx); CFRelease(ctx); }); } - (void)viewDidLoad { [super viewDidLoad]; [NSTimer scheduledTimerWithTimeInterval:0.1 repeats:true block:^(NSTimer *t) { [self renderTextfield]; }]; } @end
Dec ’21