Вы нашли ошибку в Swift (и, возможно, не только в библиотеках Swift, может быть, в чем-то более фундаментальном).
Так что же происходит?
Вы сможете увидеть это немного лучше, если создадите нумерованный список, а не маркированный список.Вам не нужно делать никаких копий и вставок, просто:
- Введите «aa», нажмите return, введите «bb»
- Выберите все и отформатируйте как нумерованный список
- Поместите курсор в конец «aa» и нажмите «Return» ...
То, что вы видите, - беспорядок, но вы можете видеть, что два исходных числа все еще там иНовый средний элемент списка, который вы начали, нажимая return, - это место, где весь беспорядок.
Когда вы нажимаете return, система текста должна перенумеровать элементы списка, поскольку вы только что вставили новый элемент.Во-первых, оказывается, что он выполняет это «перенумерацию», даже если это маркированный список, именно поэтому вы видите беспорядок в своем примере.Во-вторых, он выполняет перенумерацию, начиная с начала списка и пронумеровывая каждый элемент списка и вставляя новый номер для только что созданного элемента.
Процесс в задаче-C
Если вы переведете свой код Swift в эквивалентный Objective-C и проследите за ним, вы сможете наблюдать за процессом.Начиная с:
1) aa
2) bb
внутренний буфер выглядит примерно так:
\t1)\taa\n\t2)\tbb
сначала вставляется возврат:
\t1)\taa\n\n\t2)\tbb
, а затем внутренняя подпрограмма _reformListAtIndex:
вызывается и начинается «перенумерация».Сначала он заменяет \t1)\t
на \t1)
- номер не изменился.Затем он вставляет \t2)\t
между двумя новыми строками, так как на данный момент мы имеем:
\t1)\taa\n\t2)\t\n\t2)\tbb
, а затем заменяет исходный \t2)\t
на \t3)\t
, давая:
\t1)\taa\n\t2)\t\n\t3)\tbb
и работа сделана.Все эти замены основаны на указании диапазона символов для замены, вставка использует диапазон длины 0 и проходит:
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString * _Nonnull)str
, который в Swift заменяется на:
override func replaceCharacters(in range: NSRange, with str: String)
Процесс в Swift
В строках Objective-C есть ссылочная семантика , измените строку и все части кода со ссылкой на строку, см.менять.В Swift строки имеют семантику значений , а строки копируются (по крайней мере условно) при передаче в функции и т. Д .;если копия изменяется в вызываемой функции, вызывающая сторона не увидит это изменение в своей копии.
Текстовая система была написана в (или для) Objective-C, и разумно предположить, что она может воспользоватьсясемантика ссылки.Когда вы заменяете часть своего кода на Swift, код Swift должен немного потанцевать, на этапе перенумерации списка, когда вызывается replaceCharacters()
, стек будет выглядеть примерно так:
#0 0x0000000100003470 in SwiftTextStorage.replaceCharacters(in:with:)
#1 0x0000000100003a00 in @objc SwiftTextStorage.replaceCharacters(in:with:) ()
#2 0x00007fff2cdc30c7 in -[NSMutableAttributedString replaceCharactersInRange:withAttributedString:] ()
#3 0x00007fff28998c41 in -[NSTextView(NSKeyBindingCommands) _reformListAtIndex:] ()
#4 0x00007fff284fd555 in -[NSTextView(NSKeyBindingCommands) insertNewline:] ()
Frame # 4 isкод Objective-C, вызываемый при нажатии return, после вставки новой строки вызывает внутреннюю подпрограмму _reformListAtIndex:
, кадр № 3, чтобы выполнить перенумерацию.Это вызывает другую подпрограмму Objective-C в кадре № 2, которая, в свою очередь, вызывает кадр № 1, что, по его мнению, является методом Objective-C replaceCharactersInRange:withString:
, но на самом деле является заменой Swift.Эта замена немного танцует, преобразуя ссылочные семантические строки Objective-C в строки семантики значений Swift, а затем вызывает кадр № 0, Swift replaceCharacters()
.
Танцы жесткие
Если вы проследите свой код Swift так же, как вы делали перевод Objective-C, когда перенумерация переходит к этапу изменения оригинала с \t2)\t
на \t3)\t
, вы увидите ошибку, диапазон, заданный для оригинала \t2)\t
- это то, что было до того, как новый \t2)\t
был вставлен на предыдущем шаге (т.е. он отключен на 4 позиции) ... и вы получите беспорядок и еще несколько танцевальных шагов позжекод вылетает со строковой ссылкой на ошибку, так как все индексы неверны.
Это говорит о том, что код Objective-C опирается на эталонную семантику, и хореограф танца Swift, преобразующего ссылку на значение и обратно в эталонную семантику, не смог удовлетворить ожидания кода Objective-C: так что либо когда Objective-Код C или некоторый код Swift, который заменил его, вычисляет диапазон исходного \t2)\t
, который он делает для строки, которая не была изменена предыдущей вставкой нового \t2)\t
.
Смущенный?Хорошо, танцы иногда могут вызвать у вас головокружение; -)
Исправить?
Кодировать ваш подкласс NSTextStorage
в Objective-C, перейти к bugreport.apple.com
и сообщитьошибка.
HTH (больше, чем у вас кружится голова)