简体   繁体   中英

How to improve speed loading a UITableViewController

I have a UITableViewController populated from a backend. The cell is a custom cell that has:

  • 1 UIImageView (circled with clipBounds and roundcorners) which will be loaded asynchronously in cellForRowAtIndexPath.
  • 3 UIImageViews preloaded (45px x 30px and 700 bytes each one)
  • 4 UILabels

This is my code to load the table:

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

[_activity startAnimating];
NSString *urlString = @"http://blablabla.com/getmydata";
NSLog(@"Loading URL=%@", urlString);
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
                                                       cachePolicy:BACKEND_CACHE_POLICY
                                                   timeoutInterval:BACKEND_TIMEOUT_ASINC];

[NSURLConnection sendAsynchronousRequest:request
                                   queue:[NSOperationQueue mainQueue]
                       completionHandler:^(NSURLResponse *response, NSData *receivedData, NSError *error) {
                           NSLog(@"Received data: %@", [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding]);
                           NSError *jsonParsingError = nil;
                           NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:receivedData
                                                                                    options:0
                                                                                      error:&jsonParsingError];

                           dispatch_async(dispatch_get_main_queue(), ^{
                               [_activity stopAnimating];
                               NSLog(@"Stop animating");
                               myData = [receivedData objectForKey:@"data"];
                               [mytable reloadData];
                           });

                       }];
}

and

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Just before dequeueReusableCellWithIdentifier for row %d", indexPath.row);
CustomViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
NSLog(@"Just after dequeueReusableCellWithIdentifier for row %d", indexPath.row);
NSDictionary *data = [_items objectAtIndex:indexPath.row];
NSString *photo = [data objectForKey:@"photo_url"];
if([photo isKindOfClass:[NSNull class]])
    photo = @"";
NSString *title = [data objectForKey:@"title"];
if([title isKindOfClass:[NSNull class]])
    title = @"";
NSString *subtitle = [data objectForKey:@"subtitle"];
if([subtitle isKindOfClass:[NSNull class]])
    subtitle = @"";
NSString *author = [data objectForKey:@"author"];
if([author isKindOfClass:[NSNull class]])
    author = @"";
NSString *createdAt = [data objectForKey:@"created_at"];
if([createdAt isKindOfClass:[NSNull class]])
    createdAt = @"";
MyImageDownloader *downloadingImage = [_downloadingImages objectForKey:indexPath];
if (downloadingImage == nil)
{
    if ((photo != nil) && (![photo isEqualToString:@""]))
    {
        [self startImageDownload:photo forIndexPath:indexPath];
    }
    [cell.photoIV setImage:[UIImage imageNamed:@"noimage"]];
}
else
{
    [cell.photoIV setImage:downloadingImage.image];
}
[cell.titleLb setText:title];
[cell.subtitleLb setText:subtitle];
[cell.authorLb setText:author];
[cell.createdAtLb setText:createdAt];
return cell;
}

If I run this, this is the output:

2016-05-20 02:14:22.571 MyApp[7913:2037477] Loading URL=http://blablabla.com/getmydata
2016-05-20 02:14:23.233 MyApp[7913:2037477] Received data: data for 3 items.
2016-05-20 02:14:23.240 MyApp[7913:2037477] Stop animating
2016-05-20 02:14:23.242 MyApp[7913:2037477] Just before dequeueReusableCellWithIdentifier for row 0
2016-05-20 02:14:28.784 MyApp[7913:2037477] Just after dequeueReusableCellWithIdentifier for row 0
2016-05-20 02:14:28.791 MyApp[7913:2037477] Just before dequeueReusableCellWithIdentifier for row 1
2016-05-20 02:14:28.818 MyApp[7913:2037477] Just after dequeueReusableCellWithIdentifier for row 1
2016-05-20 02:14:28.821 MyApp[7913:2037477] Just before dequeueReusableCellWithIdentifier for row 2
2016-05-20 02:14:28.842 MyApp[7913:2037477] Just after dequeueReusableCellWithIdentifier for row 2
2016-05-20 02:14:28.858 MyApp[7913:2037477] Unable to simultaneously satisfy constraints.(some problems with the labels, but in the Interface Builder there are no warnings)
2016-05-20 02:14:28.873 MyApp[7913:2037477] Unable to simultaneously satisfy constraints.(some problems with the labels, but in the Interface Builder there are no warnings)
2016-05-20 02:14:28.887 MyApp[7913:2037477] Unable to simultaneously satisfy constraints.(some problems with the labels, but in the Interface Builder there are no warnings)
2016-05-20 02:14:29.645 MyApp[7913:2037477] Just before dequeueReusableCellWithIdentifier for row 0
2016-05-20 02:14:29.649 MyApp[7913:2037477] Just after dequeueReusableCellWithIdentifier for row 0
2016-05-20 02:14:29.650 MyApp[7913:2037477] Just before dequeueReusableCellWithIdentifier for row 1
2016-05-20 02:14:29.651 MyApp[7913:2037477] Just after dequeueReusableCellWithIdentifier for row 1
2016-05-20 02:14:29.652 MyApp[7913:2037477] Just before dequeueReusableCellWithIdentifier for row 2
2016-05-20 02:14:29.653 MyApp[7913:2037477] Just after dequeueReusableCellWithIdentifier for row 2
2016-05-20 02:14:29.656 MyApp[7913:2037477] Just before dequeueReusableCellWithIdentifier for row 0
2016-05-20 02:14:29.657 MyApp[7913:2037477] Just after dequeueReusableCellWithIdentifier for row 0
2016-05-20 02:14:29.658 MyApp[7913:2037477] Just before dequeueReusableCellWithIdentifier for row 1
2016-05-20 02:14:29.659 MyApp[7913:2037477] Just after dequeueReusableCellWithIdentifier for row 1
2016-05-20 02:14:29.661 MyApp[7913:2037477] Just before dequeueReusableCellWithIdentifier for row 2
2016-05-20 02:14:29.662 MyApp[7913:2037477] Just after dequeueReusableCellWithIdentifier for row 2

As you can see, the first dequeueReusableCellWithIdentifier is taking 5 seconds!!

I tried to encapsulate the NSURLConnection sendAsynchronousRequest inside a dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) but the results are the same.

Why is this taking so long? What am I missing?

EDIT 1:

  • Regarding to the cellForRow, I've just edited the method above so it includes the complete code.

  • Regarding to the JSON parsing I will take into account the suggestions, but as some of you have noted, the problem is not with this process but with the first loading of the cell.

  • To load remote images async I'm using a class made by myself but, as I can see, it's very similar to SDWebImage.

  • I've tried to make a first loading in viewDidLoad but with no success, the table still takes 5 seconds to load.

  • Regarding to the cellId, I'm doing this inside viewDidLoad:

    UINib *nib = [UINib nibWithNibName:@"CustomViewCell" bundle:nil];

    [mytable registerNib:nib forCellReuseIdentifier:@"Cell"];

    [mytable setRowHeight: UITableViewAutomaticDimension];

  • And the main thing, inspired by Andrew Romanov answer "can you move out strings with long time to show it for all?" I've decided to remove completely from the cell XIB file the 4 UILabels and the table load superfast (both the 2 preloaded images and the async image from backend), so the problem is with the UILabels.

  • Knowing this I've tried to remove some of them but the results are the same, with only one UILabel (it doesn't matter which one) the table still takes 5 seconds to load the first cell.

  • I've tried to add a width and height constraint to the 4 UIlabels, just in case it's a problem of calculate the cell dimensions, but the table still takes 5 seconds to load the first cell. If you think it could be a problem with Autolayout of that cell I will try to post the constraints.

So, finally, the problem is with the UILabels inside the cell XIB, I've implemented houndreds of times custom cells with more complicated layouts with no problems so this time I'm sure I'm missing something.

EDIT 2:

Following Andrew Romanov suggestion, I've set a breakpoint in dequeueReusableCellWithIdentifier, I step over and pause the debugger. With so low level detail I don't understand too much:

UIKit`-[UITableView dequeueReusableCellWithIdentifier:forIndexPath:]:
0x29ee6d84 <+0>:   push   {r4, r5, r6, r7, lr}
0x29ee6d86 <+2>:   add    r7, sp, #0xc
0x29ee6d88 <+4>:   push.w {r8, r10, r11}
0x29ee6d8c <+8>:   sub    sp, #0x10
0x29ee6d8e <+10>:  mov    r5, r0
0x29ee6d90 <+12>:  movw   r0, #0xbc52
0x29ee6d94 <+16>:  movt   r0, #0xbb8
0x29ee6d98 <+20>:  mov    r10, r1
0x29ee6d9a <+22>:  add    r0, pc
0x29ee6d9c <+24>:  mov    r4, r2
0x29ee6d9e <+26>:  mov    r8, r3
0x29ee6da0 <+28>:  ldr    r1, [r0]
0x29ee6da2 <+30>:  mov    r0, r5
0x29ee6da4 <+32>:  blx    0x2a4a2240                ; symbol stub for: CFDictionaryRemoveAllValues$shim
->  0x29ee6da8 <+36>:  mov    r6, r0
0x29ee6daa <+38>:  cbnz   r6, 0x29ee6e1c            ; <+152>
0x29ee6dac <+40>:  movw   r0, #0xb99c
0x29ee6db0 <+44>:  movt   r0, #0xbb8
0x29ee6db4 <+48>:  movw   r2, #0x1c36
0x29ee6db8 <+52>:  movt   r2, #0xbba

That instruction "0x29ee6da8 <+36>: mov r6, r0" takes 5 seconds the first time and is superfast the rest of the executions.

Besides this, what Instrument could be the best to debug this problem?

FIXED!!

Following Andrew Romanov suggestion I copied&pasted the stack trace and saw that the long time was due to the loading of custom fonts (the 4 labels used a custom font properly defined in the Info.plist). What I've done is set systemFont for the UILabels and set the custom font inside the willDisplayCell method. This way the table is loading very fast.

Sorry for the offtopic, how could I set this question as answered? should I answer myself and check it? And thank you very much Andrew!

Do you implement UI of your Cell in the code or in the xib file? If in the xib may be you does not configure cellId in the xib?
Also I see that only first cell is created long time, I think that something is cached after the first initialisation.
If you configure cell in a soryboard file try to move UI of the Cell to Xib file and use method of UITableView to register a nib for the cell:

- (void)registerNib:(UINib *)nib  forCellReuseIdentifier:(NSString *)identifier

Also you can try to initiate the cell from nib somewhere before this screen (for example while data is loading), after that system can save data in the cash to avoiding read file from the disk.
Also you can load images to cache in background while data is loading with method :

+ (UIImage *)imageNamed:(NSString *)name
               inBundle:(NSBundle *)bundle
compatibleWithTraitCollection:(UITraitCollection *)traitCollection

or

+ (UIImage *)imageNamed:(NSString *)name

From the documentation:

If a matching image object is not already in the cache, this method locates and loads the image data from disk or from an available asset catalog, and then returns the resulting object.

The problem was caused by the UILabels inside the cell. They used a custom font and I set that in the cell XIB file, so the first time the table loads a cell it loads the custom font and this take a lot of time.

What I've done to fix this is:

  1. inside the XIB file, set the font of the UILabels as the system font, so the first time it loads the cell, there is no loading time of the custom fonts.

  2. then, set the UILabels custom font inside the UITableView Delegate method:

    • (void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath

This way the time to load the first cell is very short.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM