Core Data的UISearchBar性能问题 [英] UISearchBar performance issue with Core Data

查看:158
本文介绍了Core Data的UISearchBar性能问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我使用 UISearchBar 并写一个搜索字符串这整个搜索的东西有点滞后。我的猜测是,我在主线程中的UI东西和核心数据,但我可能是错的。更重要的是,我使用一个实体的所有这些东西,所以没有关系等。这个表中有3321个对象,这个程序是消耗大约 12到14 MB的RAM 你可以看到如下所示:



我认为这会更有效3321对象不是那么多。对于所有核心数据,我使用 MagicalReacord 。我在 NSFetchedResultController 的一个实例上操作,但在主表视图和搜索表视图之间切换 NSPredicate

但是没有什么会比源代码更有价值,所以这里:

  #import GroupsViewController.h
#importCashURLs.h
#importGroup.h
#import< AFNetworking.h>

@interface GroupsViewController()
{
NSPredicate * resultPredicate;
UIRefreshControl * refreshControl;
}

@property(强,非原子)NSMutableArray * selectedGroups;
@property(strong,nonatomic)NSFetchedResultsController * groupsFRC;

@end

@implementation GroupsViewController

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];

//获取所选组的数组
self.selectedGroups = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:@SelectedGroups]];
}

- (void)viewDidLoad
{
[super viewDidLoad];

resultPredicate = nil;

//初始化pull以刷新
refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:@selector(refreshData)forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:refreshControl];

//检查persistat存储中是否至少有一个Group实体
if(![Group MR_hasAtLeastOneEntity]){
[self refreshData];
} else {
[self refreshFRC];
[self.tableView reloadData];
}
}

#pragma mark - 下载

- (void)refreshData
{
//显示刷新控制
[refreshControl beginRefreshing];

//刷新时删除所有以前的组(避免重复和ghost组)
[组MR_truncateAll];

[[AFHTTPRequestOperationManager manager] GET:ALL_GROUPS参数:nil success:^(AFHTTPRequestOperation * operation,id responseObject){
//对于每个组从下载的JSON ...
for(id在responseObject中){
// ...创建实体并用数据填充它
Group * groupEntity = [Group MR_createEntity];

groupEntity.name = [group valueForKey:@name];
groupEntity.cashID = [group valueForKey:@id];
groupEntity.sectionLetter = [[[group valueForKey:@name] substringToIndex:1] uppercaseString];
groupEntity.caseInsensitiveName = [[group valueForKey:@name] lowercaseString];
}

//将组保存到持久存储
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
[self refreshFRC];
[self.tableView reloadData];
[refreshControl endRefreshing];
}失败:^(AFHTTPRequestOperation * operation,NSError * error){
NSLog(@无法加载数据:%@,[error localizedDescription]);
//正在刷新
[refreshControl endRefreshing];

//显示有关互联网连接信息的提醒
UIAlertView * internetAlert = [[UIAlertView alloc] initWithTitle:@Ups! message:@Wyglądana to,żenie maszpołączeniaz internetemdelegate:self cancelButtonTitle:@OKotherButtonTitles:nil,nil];
[internetAlert show];
}];
}

#pragma mark - 表视图

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Count FRC中的部分
return [[self.groupsFRC sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//计算FRC
return [[[self.groupsFRC sections] objectAtIndex:section] numberOfObjects];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//获取可重用单元格
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@GroupCell];

//如果没有任何创建新的
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@GroupCell ];
}

组* group = [self.groupsFRC objectAtIndexPath:indexPath];

cell.textLabel.text = group.name;

//检查组是否早些被选中
if([self.selectedGroups containsObject:@ {@name:group.name,@id:group.cashID}] ){
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
} else {
[cell setAccessoryType:UITableViewCellAccessoryNone];
}

return cell;
}

//为所选单元格添加复选标记
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell * selectedCell = [tableView cellForRowAtIndexPath:indexPath];
Group * group = [self.groupsFRC objectAtIndexPath:indexPath];

//检查所选单元格是否已将辅助视图设置为选中标记并将组添加到选定的组array
if(selectedCell.accessoryType == UITableViewCellAccessoryNone)
{
selectedCell .accessoryType = UITableViewCellAccessoryCheckmark;
[self.selectedGroups addObject:@ {@name:group.name,@id:group.cashID}];
NSLog(@%@,self.selectedGroups);
}
else if(selectedCell.accessoryType == UITableViewCellAccessoryCheckmark)
{
selectedCell.accessoryType = UITableViewCellAccessoryNone;
[self.selectedGroups removeObject:@ {@name:group.name,@id:group.cashID}];
NSLog(@%@,self.selectedGroups);
}

//隐藏动画效果的选择
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

#pragma mark - 过滤/搜索

//如果搜索栏中有任何字符,设置搜索谓词
- (void)searchBar :(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
if(searchText.length> 0){
resultPredicate = [NSPredicate predicateWithFormat:@SELF.caseInsensitiveName CONTAINS [c ]%@,searchText];
} else {
resultPredicate = nil;
}

[self refreshFRC];
}

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
//如果用户取消搜索,我们将谓词设置为nil
resultPredicate =零;
[self refreshFRC];
}

//刷新NSFetchedResultController
- (void)refreshFRC
{
self.groupsFRC = [组MR_fetchAllSortedBy:@caseInsensitiveName
ascending:YES
withPredicate:resultPredicate
groupBy:@sectionLetter
delegate:self];
}

我读了一个线程 CONTAINS 可能是资源消耗,但我真的不知道如何以其他方式实现。 我的其他猜测是把这个搜索放在另一个队列中并且异步执行,将它与UI分离...它不是那么遥远该UI将不得不等待年龄的重新加载的表视图。但是这是正确的方法吗?我必须提高这个 UISearchBar 的性能,因为我不想有不满意的客户。

我希望你能帮助我

解决方案

首先, CONTAINS 很慢。虽然这些信息不能帮助你的问题,但很高兴知道你正在争取开始。



其次,你在每个字母上敲击磁盘按下。这是浪费,当然很慢。



您正在使用Magical Record,似乎正在建立一个新的 NSFetchedResultsController 每个字母按。这是浪费,当然很慢。



你应该怎么办?



简单 NSFetchRequest ,保持批量减小,甚至可以保持获取限制。保留从中产生的 NSArray ,并使用它显示结果。 是的,这会使您的 UITableViewDataSource 更复杂。



请按对现有 NSArray 进行筛选。



如果检测到删除,则清除阵列并从磁盘重建。



这会将您的磁盘匹配限制为仅第一个字母,并且当检测到删除时,会大幅增加您的搜索时间。



更新



关于魔法记录。我对第三方框架有非常灰的观点。我总是建议避免他们。这种避免与代码质量无关,它与保持尽可能接近的金属。 MR是Core Data顶部的一个层,我没有看到一个值。当然,我也不喜欢点语法,所以我用一粒盐的意见:)



是的,您应该将第一个搜索结果存储在数组中,然后针对该数组显示。



CONTAINS ;不知道如果你可以避免它。它是慢,但它的工作,你正在做字符串比较,所以没有太多,你可以做在这方面。所以修复一切,使你不付出比你需要更多的计算税。


When I use UISearchBar and write something as a search string this whole searching thing gets a little laggy. My guess is that I'm messing UI stuff and Core Data in the main thread but I might be wrong. What is more, I use one entity for all of this stuff so there are no relations etc. there are 3321 objects in this table and this app is consuming approximately 12 to 14 MB of RAM what you can see on the screenshot below:

I thought that it'll be more efficent as 3321 objects isn't so much. For all Core Data stuff I use MagicalReacord. I operate on one instance of NSFetchedResultController but switching NSPredicate between main table view alnd search table view.
But nothing will be more valuable that source code so here you go:

#import "GroupsViewController.h"
#import "CashURLs.h"
#import "Group.h"
#import <AFNetworking.h>

@interface GroupsViewController ()
{
    NSPredicate *resultPredicate;
    UIRefreshControl *refreshControl;
}

@property (strong, nonatomic) NSMutableArray *selectedGroups;
@property (strong, nonatomic) NSFetchedResultsController *groupsFRC;

@end

@implementation GroupsViewController

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    // Getting array of selected groups
    self.selectedGroups = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:@"SelectedGroups"]];
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    resultPredicate = nil;

    // Initializing pull to refresh
    refreshControl = [[UIRefreshControl alloc] init];
    [refreshControl addTarget:self action:@selector(refreshData) forControlEvents:UIControlEventValueChanged];
    [self.tableView addSubview:refreshControl];

    // Check if there is at least one Group entity in persistat store
    if (![Group MR_hasAtLeastOneEntity]) {
        [self refreshData];
    } else {
        [self refreshFRC];
        [self.tableView reloadData];
    }
}

#pragma mark - Downloading

-(void)refreshData
{
    // Show refresh control
    [refreshControl beginRefreshing];

    // On refresh delete all previous groups (To avoid duplicates and ghost-groups)
    [Group MR_truncateAll];

    [[AFHTTPRequestOperationManager manager] GET:ALL_GROUPS parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
        // For each group from downloaded JSON...
        for (id group in responseObject) {
            // ... Create entity and filll it with data
            Group *groupEntity = [Group MR_createEntity];

            groupEntity.name = [group valueForKey:@"name"];
            groupEntity.cashID = [group valueForKey:@"id"];
            groupEntity.sectionLetter = [[[group valueForKey:@"name"] substringToIndex:1] uppercaseString];
            groupEntity.caseInsensitiveName = [[group valueForKey:@"name"] lowercaseString];
        }

        // Save Groups to persistent store
        [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
        [self refreshFRC];
        [self.tableView reloadData];
        [refreshControl endRefreshing];
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"Failed to load data: %@", [error localizedDescription]);
        // End refreshing
        [refreshControl endRefreshing];

        // Show alert with info about internet connection
        UIAlertView *internetAlert = [[UIAlertView alloc] initWithTitle:@"Ups!" message:@"Wygląda na to, że nie masz połączenia z internetem" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
        [internetAlert show];
    }];
}

#pragma mark - Table View

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Count sections in FRC
    return [[self.groupsFRC sections] count];
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Count groups in each section of FRC
    return [[[self.groupsFRC sections] objectAtIndex:section] numberOfObjects];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Get reusable cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"GroupCell"];

    // If there isn't any create new one
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"GroupCell"];
    }

    Group *group = [self.groupsFRC objectAtIndexPath:indexPath];

    cell.textLabel.text = group.name;

    // Checking if group has been selected earlier
    if ([self.selectedGroups containsObject:@{@"name" : group.name, @"id" : group.cashID}]) {
        [cell setAccessoryType:UITableViewCellAccessoryCheckmark];
    } else {
        [cell setAccessoryType:UITableViewCellAccessoryNone];
    }

    return cell;
}

// Adding checkmark to selected cell
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
    Group *group = [self.groupsFRC objectAtIndexPath:indexPath];

    // Checking if selected cell has accessory view set to checkmark and add group to selected groups array
    if (selectedCell.accessoryType == UITableViewCellAccessoryNone)
    {
        selectedCell.accessoryType = UITableViewCellAccessoryCheckmark;
        [self.selectedGroups addObject:@{@"name" : group.name, @"id" : group.cashID}];
        NSLog(@"%@", self.selectedGroups);
    }
    else if (selectedCell.accessoryType == UITableViewCellAccessoryCheckmark)
    {
        selectedCell.accessoryType = UITableViewCellAccessoryNone;
        [self.selectedGroups removeObject:@{@"name" : group.name, @"id" : group.cashID}];
        NSLog(@"%@", self.selectedGroups);
    }

    // Hiding selection with animation for nice and clean effect
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

#pragma mark - Filtering/Searching

// Seting searching predicate if there are any characters in search bar
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    if (searchText.length > 0) {
        resultPredicate = [NSPredicate predicateWithFormat:@"SELF.caseInsensitiveName CONTAINS[c] %@", searchText];
    } else {
        resultPredicate = nil;
    }

    [self refreshFRC];
}

-(void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
    // If user cancels searching we set predicate to nil
    resultPredicate = nil;
    [self refreshFRC];
}

// Refreshing NSFetchedResultController
- (void)refreshFRC
{
    self.groupsFRC = [Group MR_fetchAllSortedBy:@"caseInsensitiveName"
                                      ascending:YES
                                  withPredicate:resultPredicate
                                        groupBy:@"sectionLetter"
                                       delegate:self];
}

I read in one thread that CONTAINS could be resource consuming but I really have no idea how to implement it other way. My other guess was to put this searching in another queue and do it asynchronously, separate it from the UI... It isn't so laggy that UI will have to wait for ages for reloaded table view. But is it the right way? I have to improve preformance of this UISearchBar because I don't want to have unhappy customers.
I hope you could proide me with some ideas or you have any improvements for my code

解决方案

First, CONTAINS is slow. While that information doesn't help your problem, it is good to know that you are fighting up hill to start with.

Second, you are hitting the disk on every letter pressed. That is wasteful and of course slow.

You are using Magical Record which appears to be building a new NSFetchedResultsController on every letter press. That is wasteful and of course slow.

What should you do?

On the first letter press do a simple NSFetchRequest and keep the batch size down and maybe even keep the fetch limit down. Retain the NSArray that comes out of that and use it to display the results. Yes this will make your UITableViewDataSource more complicated.

On the second and subsequent letters press you filter against the existing NSArray. You do not go back out to disk.

If a delete is detected you blow away the array and rebuild it from disk.

This will limit your disk hits to just the first letter and when deletes are detected which will radically increase your search times.

Update

Regarding Magical Record. I have very graybeard opinions on third party frameworks. I always recommend avoiding them. That avoidance has nothing to do with code quality it has to do with staying as close to the metal as possible. MR is a layer on top of Core Data that I don't see a value in. Of course I also don't like dot syntax so take my opinions with a grain of salt :)

Yes, you should store the first search results in an array and then display against that array. It will be faster.

As for CONTAINS; not sure if you can avoid it. It is slow but it works and you are doing string compares so there is not much you can do on that front. So fix everything else so that you are not paying more computational tax than you need to.

这篇关于Core Data的UISearchBar性能问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆