【问题标题】:NSTableView sorting decimal column causes exceptionNSTableView 排序小数列导致异常
【发布时间】:2019-02-22 12:32:57
【问题描述】:

我有一个 NSTableView,我用它来试验排序列。字符串、整数和日期列似乎工作正常,但小数列导致崩溃,我正在摸不着头脑。

数据结构如下:

class LearningStruct : NSObject, Decodable {
    @objc var ID : Int
    @objc var Text : String
    @objc var Description : String
    @objc var CreationDate : Date
    @objc var UpdateDateTime : Date?
    @objc var ValueX: Decimal
    @objc var IsTrue : Bool
}

(注意@objc 和 NSObject 的使用,显然 Swift 无法原生处理排序所需的键值编码)

我创建了一些测试 JSON 数据:

let learningJSON = "{\"Items\": [{\"ID\": 1, \"Text\": \"Item 1 text\", \"Description\": \"This is item 1 description\", \"CreationDate\": \"02/17/2019\", \"ValueX\": 123.45, \"UpdateDate\": \"02/17/2019\", \"IsTrue\": true}, {\"ID\": 2, \"Text\": \"Item 2 text\", \"Description\": \"This is item 2 description\", \"CreationDate\": \"02/14/2019\", \"ValueX\": 789.01, \"UpdateDate\": \"02/14/2019\", \"IsTrue\": false}]}"
internal var learningItems = [LearningStruct]()

然后我使用解码器填充数组:

            let decoder = JSONDecoder()
            let dateformatter = DateFormatter()
            dateformatter.dateFormat = "MM/dd/yyyy" // "MM/dd/yyyy'T'HH:mm:ss"
            decoder.dateDecodingStrategy = .formatted(dateformatter)
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            let learningData = try decoder.decode(LearningItems.self, from: learningJSON.data(using: .utf8)!) as LearningItems
            learningItems = learningData.Items

为了便于映射,NSTableColumns 将其标识符设置为情节提要中的属性名称。

           for prop in properties {
                let colindex = learningTableView.column(withIdentifier: NSUserInterfaceItemIdentifier.init(prop)) // Get the column index number for that property key
                if (colindex >= 0) { // If the column exists
                    let descriptorID = NSSortDescriptor(key: prop, ascending: true)
                    learningTableView.tableColumns[colindex].sortDescriptorPrototype = descriptorID
                }
            }

排序逻辑如下:

/// Sorting logic
    func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {

        print("--- Sorting logic begin ---")

        // Debug print the sorters
        for col in learningTableView.tableColumns {

            if col.sortDescriptorPrototype != nil {
                let sorter = col.sortDescriptorPrototype!.key!
                let direction = col.sortDescriptorPrototype!.ascending
                print("Column \(col.identifier.rawValue): sort descriptor: \(sorter), is ascending: \(direction)")
            } else {
                print("Column \(col.identifier.rawValue): no sort descriptor")
            }
        }

        print("+++ Sort descriptors list")
        let learningItemsMutable = NSMutableArray(array: learningItems)
        let sorters = tableView.sortDescriptors
        for sortdesc in sorters {
            print("Sort descriptor: \(sortdesc.key!)")
        }
        print("Sorting...")
        learningItemsMutable.sort(using: sorters)
        learningItems = learningItemsMutable as! [LearningStruct]
        tableView.reloadData()
        print("--- Sorting logic end ---")
    }

当我运行代码时,当我点击一个列直到我到达“ValueX”列时,它会完美地对所有内容进行排序。下面的示例是我按顺序单击每一列,以“Value X”结尾。当我点击它时,我得到了一个对我没有帮助的异常......有人可以帮忙吗?

--- Sorting logic begin ---
Column ID: sort descriptor: ID, is ascending: true
Column Text: sort descriptor: Text, is ascending: true
Column Description: sort descriptor: Description, is ascending: true
Column CreationDate: sort descriptor: CreationDate, is ascending: true
Column ValueX: sort descriptor: ValueX, is ascending: true
Column IsTrue: sort descriptor: IsTrue, is ascending: true
+++ Sort descriptors list
Sort descriptor: ValueX
Sort descriptor: CreationDate
Sort descriptor: Description
Sort descriptor: Text
Sort descriptor: ID
Sorting...
2019-02-22 12:29:27.093825+0000 NSTableView Learning[8571:1649436] [General] -[NSInvocation getArgument:atIndex:]: struct with unknown contents found while getting argument at index -1
2019-02-22 12:29:27.095961+0000 NSTableView Learning[8571:1649436] [General] (
    0   CoreFoundation                      0x00007fff4aecaded __exceptionPreprocess + 256
    1   libobjc.A.dylib                     0x00007fff76f96720 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff4ae22af3 -[NSInvocation getArgument:atIndex:] + 473
    3   Foundation                          0x00007fff4d251a85 _NSGetValueWithMethod + 174
    4   Foundation                          0x00007fff4d184494 -[NSObject(NSKeyValueCoding) valueForKey:] + 284
    5   Foundation                          0x00007fff4d193d68 _sortedObjectsUsingDescriptors + 389
    6   Foundation                          0x00007fff4d1be3c9 -[NSMutableArray(NSKeyValueSorting) sortUsingDescriptors:] + 497
    7   NSTableView Learning                0x000000010000d3c9 $S20NSTableView_Learning0cB10ControllerC05tableB0_24sortDescriptorsDidChangeySo0aB0C_SaySo16NSSortDescriptorCGtF + 5289
    8   NSTableView Learning                0x000000010000dacf $S20NSTableView_Learning0cB10ControllerC05tableB0_24sortDescriptorsDidChangeySo0aB0C_SaySo16NSSortDescriptorCGtFTo + 111
    9   AppKit                              0x00007fff48b9e388 -[NSTableView _sendDataSourceSortDescriptorsDidChange:] + 82
    10  AppKit                              0x00007fff4862275a -[NSTableView setSortDescriptors:] + 327
    11  AppKit                              0x00007fff48b9e5ca -[NSTableView _changeSortDescriptorsForClickOnColumn:] + 542
    12  AppKit                              0x00007fff48b81d73 -[NSTableHeaderView _trackAndModifySelectionWithEvent:onColumn:stopOnReorderGesture:] + 1000
    13  AppKit                              0x00007fff48b84eeb -[NSTableHeaderView mouseDown:] + 596
    14  AppKit                              0x00007fff4859f1eb -[NSWindow(NSEventRouting) _handleMouseDownEvent:isDelayedEvent:] + 5668
    15  AppKit                              0x00007fff484d3223 -[NSWindow(NSEventRouting) _reallySendEvent:isDelayedEvent:] + 2319
    16  AppKit                              0x00007fff484d26c9 -[NSWindow(NSEventRouting) sendEvent:] + 481
    17  AppKit                              0x00007fff4836f954 -[NSApplication(NSEvent) sendEvent:] + 336
    18  AppKit                              0x00007fff4835d19d -[NSApplication run] + 755
    19  AppKit                              0x00007fff4834c8a3 NSApplicationMain + 780
    20  NSTableView Learning                0x000000010001085d main + 13
    21  libdyld.dylib                       0x00007fff78064ed9 start + 1
    22  ???                                 0x0000000000000003 0x0 + 3
)

【问题讨论】:

  • 一个我忘了提的快速评论:布尔列也正确排序。
  • 在索引 -1 处获取参数时发现具有未知内容的结构。这以某种方式告诉我您正在处理 -1 作为索引。设置异常断点,看看为什么会发生
  • 我添加了一些调试打印语句来帮助这篇文章。实际的异常发生在“sorting...”打印语句之后:“learningItemsMutable.sort(using:sorters)”,不幸的是它没有暴露索引的位置。
  • lldb -n "getArgument:atIndex:" + 添加条件$rcx==-1,打印语句浪费时间,异常断点不是打印语句(可能google怎么加)跨度>
  • 我只提供了打印语句来帮助显示代码在哪里崩溃,以便在这篇文章中清楚起见。正如您所建议的,我添加了一个异常断点,该断点生成了一个与我上面提供的非常一致的函数调用列表。很明显,ValueX 的键值排序有问题。你能解释一下如何执行这个命令,“lldb -n”getArgument:atIndex:”+add condition $rcx==-1”吗?

标签: swift cocoa nstableview nssortdescriptor


【解决方案1】:

Objective-C NSDecimal 中使用的类型Decimal 不在支持的键值编码类型列表中。 KVC documentation

typedef struct {
    signed   int _exponent:8;
    unsigned int _length:4;     // length == 0 && isNegative -> NaN
    unsigned int _isNegative:1;
    unsigned int _isCompact:1;
    unsigned int _reserved:18;
    unsigned short _mantissa[NSDecimalMaxSize];
} NSDecimal;

NSDecimal 使用位字段,这是关键问题。

使它工作的每一次尝试(这应该是可能的)都不是开箱即用的可编码的。

【讨论】:

    【解决方案2】:

    DecimalNSDecimalNumber 的桥接方式可能有些问题

    尝试在 LearningStruct 类中使用 NSDecimalNumber 以查看崩溃仍然存在。

    或者创建一个新的只读属性并将其用于排序:

    class LearningStruct: NSObject, Decodable {
        @objc var ID: Int
        @objc var Text: String
        @objc var Description: String
        @objc var CreationDate: Date
        @objc var UpdateDateTime: Date?
        @objc var ValueX: Decimal
        @objc var IsTrue: Bool
    
        @objc var ValueXNumber: NSDecimalNumber {
            return ValueX as NSDecimalNumber
        }
    }
    

    相关说明:在 Swift 中,实例变量通常以小写首字母命名。并且@objc 是必需的,因为NSSortDescriptor 是一个Objective-C 类,否则它不会看到您的Swift 类的属性。

    【讨论】:

    • 我都试过了,不幸的是这两个选项都不起作用。将该属性用作 NSDecimalNumber 会导致该类不再可解码。并且使用 getter 方法不起作用,因为我使用 Mirror() 来获取属性名称并且它没有显示出来。
    • 我将重命名实例变量......虽然我实际上是在使用它们来匹配 JSON 并且我没有使用它们来创建列标题,所以这可能会很棘手.
    • KVC (getValue:forKey) with structs (NSDecimal 在你的情况下) 只适用于某些结构类型developer.apple.com/library/archive/documentation/Cocoa/…
    • 谢谢!这显然是答案 - Decimal 类型不在支持的 KVC 标量类型列表中。等待进一步测试,似乎对我有用的解决方案是在 NSObject 类中使用 Double 而不是 Decimal 。 @Marek H,您能否在评论中点击“回答”按钮,我会将其标记为解决方案。
    猜你喜欢
    • 1970-01-01
    • 2014-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多