NSTableView排序小数列导致异常
NSTableView sorting decimal column causes exception
我有一个 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
)
Decimal
与 NSDecimalNumber
的桥接方式可能存在一些问题
尝试在 LearningStruct
class 中使用 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 class,否则它不会看到您的 Swift class 的属性。
在Objective-CNSDecimal
中使用的类型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
使用位域,这是关键问题。
使它工作的每一次尝试(这应该是可能的)都不是开箱即用的 Codable。
我有一个 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
)
Decimal
与 NSDecimalNumber
尝试在 LearningStruct
class 中使用 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 class,否则它不会看到您的 Swift class 的属性。
在Objective-CNSDecimal
中使用的类型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
使用位域,这是关键问题。
使它工作的每一次尝试(这应该是可能的)都不是开箱即用的 Codable。