如何将 NSFetchedResultsController 的部分 sectionNameKeyPath 设置为属性的第一个字母,而不仅仅是 Swift 中的属性,而不是 ObjC
How can I set NSFetchedResultsController's section sectionNameKeyPath to be the first letter of a attribute not just the attribute in Swift, NOT ObjC
我正在 swift 中重写一个旧的 obj-c 项目,它有一个带有 sectionIndex
的 tableView
我设置了一个谓词,因此它只 returns 个具有相同国家/地区属性的对象
我想根据国家属性的首字母制作栏目索引
在 obj-c 中,我创建了这样的 fetchedResultsController
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:@"name.stringGroupByFirstInitial" cacheName:nil];
我在 NSString 上有一个扩展
@implementation NSString (Indexing)
- (NSString *)stringGroupByFirstInitial {
if (!self.length || self.length == 1)
return self;
return [self substringToIndex:1];
}
这与这两种方法结合使用效果很好
- (NSArray *) sectionIndexTitlesForTableView: (UITableView *) tableView{
return [self.fetchedResultsController sectionIndexTitles];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index{
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
在 swift 中,我尝试创建一个类似的扩展,名称更简洁 :)
extension String {
func firstCharacter()-> String {
let startIndex = self.startIndex
let first = self[...startIndex]
return String(first)
}
}
这在 playground 中工作正常,返回您调用它的任何字符串的第一个字符的字符串。
但在 swift 中使用类似的方法创建 fetchedResultsController,...
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: (dataModel?.container.viewContext)!,
sectionNameKeyPath: "country.firstCharacter()",
cacheName: nil)
...导致异常。这是控制台日志
2018-02-23 11:41:20.762650-0800 Splash[5287:459654] *** 由于未捕获的异常 'NSUnknownKeyException' 而终止应用程序,原因:'[valueForUndefinedKey:]: 这个 class 与键 firstCharacter() 的键值编码不兼容。'
任何建议作为解决此问题的正确方法将不胜感激
这不是所建议问题的重复,因为这与如何在 Swift 中实现这一点特别相关。我在另一个问题
中添加了在 Obj -c 中实现此目的的简单正确方法
加一个函数给你的DiveSite
class到return第一个字母country
:
@objc func countryFirstCharacter() -> String {
let startIndex = country.startIndex
let first = country[...startIndex]
return String(first)
}
然后使用该函数名称(不带 ()
)作为 sectionNameKeyPath
:
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: (dataModel?.container.viewContext)!,
sectionNameKeyPath: "countryFirstCharacter",
cacheName: nil)
请注意,为了使 (Swift) 函数对 (Objective-C) FRC 可见,这里需要 @objc
。 (遗憾的是,您不能只将 @objc
添加到您的字符串扩展中。)
最快的解决方案(适用于 Swift 4)
pbasdf 的解决方案有效。它比使用瞬态 属性 快得多。
我有大约 700 个名称的列表要显示在表视图中,并且从 CoreData 中获取相对复杂。
只是进行初步搜索:
- 瞬态 属性 返回 'name' 的首字母大写:加载需要 2-3 秒。有些峰值长达 4 秒!
- 使用 pbasdf 的解决方案(@obj func nameFirstCharacter() -> String,这个加载时间下降到 0.4 - 0.5s。
- 最快的解决方案仍然是向模型添加常规 属性 'nameFirstChar'。并确保它已编入索引。在这种情况下,同一提取查询的加载时间下降到 0.01 - 0.02 秒!
我必须同意我同意一些评论,即向模型添加 属性 看起来有点脏,只是为了在表视图中创建部分。但考虑到性能提升,我会坚持使用该选项!
对于最快的解决方案,这是 Person+CoreDataProperties.swift 中的改编实现,因此在更改名称 属性 时会自动更新 namefirstchar。请注意,此处关闭了代码自动生成。 :
//@NSManaged public var name: String? //removed default
public var name: String? //rewritten to set namefirstchar correctly
{
set (new_name){
self.willChangeValue(forKey: "name")
self.setPrimitiveValue(new_name, forKey: "name")
self.didChangeValue(forKey: "name")
let namefirstchar_s:String = new_name!.substring(to: 1).capitalized
if self.namefirstchar ?? "" != namefirstchar_s {
self.namefirstchar = namefirstchar_s
}
}
get {
self.willAccessValue(forKey: "name")
let text = self.primitiveValue(forKey: "name") as? String
self.didAccessValue(forKey: "name")
return text
}
}
@NSManaged public var namefirstchar: String?
如果您不喜欢或不能添加新的 属性,或者如果您希望保持代码自动生成,这是我从 pbasdf 调整的解决方案:
1) 扩展 NSString
extension NSString{
@objc func firstUpperCaseChar() -> String{ // @objc is needed to avoid crash
if self.length == 0 {
return ""
}
return self.substring(to: 1).capitalized
}
}
2) 进行排序,无需使用排序键name.firstUpperCaseChar
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
let personNameSort = NSSortDescriptor(key: "name", ascending: true, selector: #selector(NSString.caseInsensitiveCompare))
let personFirstNameSort = NSSortDescriptor(key: "firstname", ascending: true, selector: #selector(NSString.caseInsensitiveCompare))
fetchRequest.sortDescriptors = [personNameSort, personFirstNameSort]
3) 然后用右边的sectionNameKeyPath
进行fetch
fetchRequest.predicate = ... whatever you need to filter on ...
fetchRequest.fetchBatchSize = 50 //or 20 to speed things up
let fetchedResultsController = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: managedObjectContext,
sectionNameKeyPath: "name.firstUpperCaseChar", //note no ()
cacheName: nil)
fetchedResultsController.delegate = self
let start = DispatchTime.now() // Start time
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Failed to initialize FetchedResultsController: \(error)")
}
let end = DispatchTime.now() // End time
let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds // time difference in nanosecs
let timeInterval = Double(nanoTime) / 1_000_000_000
print("Fetchtime: \(timeInterval) seconds")
我正在 swift 中重写一个旧的 obj-c 项目,它有一个带有 sectionIndex
的 tableView我设置了一个谓词,因此它只 returns 个具有相同国家/地区属性的对象
我想根据国家属性的首字母制作栏目索引
在 obj-c 中,我创建了这样的 fetchedResultsController
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:@"name.stringGroupByFirstInitial" cacheName:nil];
我在 NSString 上有一个扩展
@implementation NSString (Indexing)
- (NSString *)stringGroupByFirstInitial {
if (!self.length || self.length == 1)
return self;
return [self substringToIndex:1];
}
这与这两种方法结合使用效果很好
- (NSArray *) sectionIndexTitlesForTableView: (UITableView *) tableView{
return [self.fetchedResultsController sectionIndexTitles];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index{
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
在 swift 中,我尝试创建一个类似的扩展,名称更简洁 :)
extension String {
func firstCharacter()-> String {
let startIndex = self.startIndex
let first = self[...startIndex]
return String(first)
}
}
这在 playground 中工作正常,返回您调用它的任何字符串的第一个字符的字符串。
但在 swift 中使用类似的方法创建 fetchedResultsController,...
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: (dataModel?.container.viewContext)!,
sectionNameKeyPath: "country.firstCharacter()",
cacheName: nil)
...导致异常。这是控制台日志
2018-02-23 11:41:20.762650-0800 Splash[5287:459654] *** 由于未捕获的异常 'NSUnknownKeyException' 而终止应用程序,原因:'[valueForUndefinedKey:]: 这个 class 与键 firstCharacter() 的键值编码不兼容。'
任何建议作为解决此问题的正确方法将不胜感激
这不是所建议问题的重复,因为这与如何在 Swift 中实现这一点特别相关。我在另一个问题
中添加了在 Obj -c 中实现此目的的简单正确方法加一个函数给你的DiveSite
class到return第一个字母country
:
@objc func countryFirstCharacter() -> String {
let startIndex = country.startIndex
let first = country[...startIndex]
return String(first)
}
然后使用该函数名称(不带 ()
)作为 sectionNameKeyPath
:
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: (dataModel?.container.viewContext)!,
sectionNameKeyPath: "countryFirstCharacter",
cacheName: nil)
请注意,为了使 (Swift) 函数对 (Objective-C) FRC 可见,这里需要 @objc
。 (遗憾的是,您不能只将 @objc
添加到您的字符串扩展中。)
最快的解决方案(适用于 Swift 4)
pbasdf 的解决方案有效。它比使用瞬态 属性 快得多。 我有大约 700 个名称的列表要显示在表视图中,并且从 CoreData 中获取相对复杂。 只是进行初步搜索:
- 瞬态 属性 返回 'name' 的首字母大写:加载需要 2-3 秒。有些峰值长达 4 秒!
- 使用 pbasdf 的解决方案(@obj func nameFirstCharacter() -> String,这个加载时间下降到 0.4 - 0.5s。
- 最快的解决方案仍然是向模型添加常规 属性 'nameFirstChar'。并确保它已编入索引。在这种情况下,同一提取查询的加载时间下降到 0.01 - 0.02 秒!
我必须同意我同意一些评论,即向模型添加 属性 看起来有点脏,只是为了在表视图中创建部分。但考虑到性能提升,我会坚持使用该选项!
对于最快的解决方案,这是 Person+CoreDataProperties.swift 中的改编实现,因此在更改名称 属性 时会自动更新 namefirstchar。请注意,此处关闭了代码自动生成。 :
//@NSManaged public var name: String? //removed default
public var name: String? //rewritten to set namefirstchar correctly
{
set (new_name){
self.willChangeValue(forKey: "name")
self.setPrimitiveValue(new_name, forKey: "name")
self.didChangeValue(forKey: "name")
let namefirstchar_s:String = new_name!.substring(to: 1).capitalized
if self.namefirstchar ?? "" != namefirstchar_s {
self.namefirstchar = namefirstchar_s
}
}
get {
self.willAccessValue(forKey: "name")
let text = self.primitiveValue(forKey: "name") as? String
self.didAccessValue(forKey: "name")
return text
}
}
@NSManaged public var namefirstchar: String?
如果您不喜欢或不能添加新的 属性,或者如果您希望保持代码自动生成,这是我从 pbasdf 调整的解决方案:
1) 扩展 NSString
extension NSString{
@objc func firstUpperCaseChar() -> String{ // @objc is needed to avoid crash
if self.length == 0 {
return ""
}
return self.substring(to: 1).capitalized
}
}
2) 进行排序,无需使用排序键name.firstUpperCaseChar
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
let personNameSort = NSSortDescriptor(key: "name", ascending: true, selector: #selector(NSString.caseInsensitiveCompare))
let personFirstNameSort = NSSortDescriptor(key: "firstname", ascending: true, selector: #selector(NSString.caseInsensitiveCompare))
fetchRequest.sortDescriptors = [personNameSort, personFirstNameSort]
3) 然后用右边的sectionNameKeyPath
fetchRequest.predicate = ... whatever you need to filter on ...
fetchRequest.fetchBatchSize = 50 //or 20 to speed things up
let fetchedResultsController = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: managedObjectContext,
sectionNameKeyPath: "name.firstUpperCaseChar", //note no ()
cacheName: nil)
fetchedResultsController.delegate = self
let start = DispatchTime.now() // Start time
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Failed to initialize FetchedResultsController: \(error)")
}
let end = DispatchTime.now() // End time
let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds // time difference in nanosecs
let timeInterval = Double(nanoTime) / 1_000_000_000
print("Fetchtime: \(timeInterval) seconds")