MST

星途 面试题库

面试题:Objective-C在iOS应用的深层次性能优化策略

当iOS应用在启动、滚动列表、加载大量数据等场景下出现性能瓶颈,结合Objective-C语言特性,从底层运行机制、与系统框架交互等方面详细说明优化方法,比如如何利用Objective-C的runtime机制进行动态优化,以及在多线程环境下如何利用GCD与NSOperationQueue优化性能。
36.8万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

启动优化

  1. 减少启动时的计算任务
    • AppDelegateapplication:didFinishLaunchingWithOptions:方法中,避免执行复杂的计算任务,如大量的初始化逻辑、数据预处理等。将非必要的任务延迟到用户操作或者后台线程执行。
    • 例如,如果有一些数据需要从本地文件读取并进行复杂解析,可以使用dispatch_async将其放到后台队列中执行。
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 复杂的数据读取和解析逻辑
        NSString *filePath = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"txt"];
        NSString *dataString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
        // 解析数据
        NSArray *parsedData = [dataString componentsSeparatedByString:@","];
        // 处理完后回到主线程更新UI(如果需要)
        dispatch_async(dispatch_get_main_queue(), ^{
            // 更新UI
        });
    });
    
  2. 优化动态库加载
    • 尽量减少动态库的依赖,动态库加载会增加启动时间。如果必须使用动态库,确保其初始化过程简单高效。
    • 对于Objective - C语言,可以使用@lazy_import来延迟加载动态库。例如,对于一个非关键的第三方库:
    @lazy_import(<OptionalLibrary/OptionalLibrary.h>)
    
  3. 利用runtime机制延迟方法实现
    • 在类的实现中,可以使用runtime的关联对象(Associated Objects)来延迟一些方法的具体实现。例如,有一个复杂的初始化方法initComplex,可以先不实现,通过runtime在需要时动态添加实现。
    @interface MyClass : NSObject
    - (void)initComplex;
    @end
    
    @implementation MyClass
    + (void)load {
        Method originalMethod = class_getInstanceMethod(self, @selector(initComplex));
        IMP originalIMP = method_getImplementation(originalMethod);
        const char *originalType = method_getTypeEncoding(originalMethod);
    
        // 定义一个简单的占位实现
        IMP placeholderIMP = imp_implementationWithBlock(^(id selfObject) {
            // 这里可以记录日志或者直接返回
            NSLog(@"initComplex is called, but will be implemented later.");
            return nil;
        });
        class_replaceMethod(self, @selector(initComplex), placeholderIMP, originalType);
    }
    
    - (void)realInitComplex {
        // 真正的复杂初始化逻辑
    }
    
    - (void)whenNeeded {
        // 当需要时,动态替换为真正的实现
        Method realMethod = class_getInstanceMethod(self.class, @selector(realInitComplex));
        IMP realIMP = method_getImplementation(realMethod);
        class_replaceMethod(self.class, @selector(initComplex), realIMP, method_getTypeEncoding(realMethod));
        [self initComplex];
    }
    @end
    

滚动列表优化

  1. 复用单元格
    • UITableViewUICollectionView中,使用单元格复用机制。在UITableViewDataSourcetableView:cellForRowAtIndexPath:方法中,通过dequeueReusableCellWithIdentifier:forIndexPath:获取复用单元格。
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellIdentifier" forIndexPath:indexPath];
        // 配置单元格数据
        cell.textLabel.text = [NSString stringWithFormat:@"Row %ld", (long)indexPath.row];
        return cell;
    }
    
  2. 异步加载单元格数据
    • 当单元格的数据需要从网络或者数据库加载时,使用GCD或者NSOperationQueue进行异步加载。
    • 使用GCD示例:
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellIdentifier" forIndexPath:indexPath];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 异步加载数据
            NSString *data = [self loadDataForIndexPath:indexPath];
            dispatch_async(dispatch_get_main_queue(), ^{
                // 回到主线程更新单元格
                cell.textLabel.text = data;
            });
        });
        return cell;
    }
    
    • 使用NSOperationQueue示例:
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellIdentifier" forIndexPath:indexPath];
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            // 异步加载数据
            NSString *data = [self loadDataForIndexPath:indexPath];
            dispatch_async(dispatch_get_main_queue(), ^{
                // 回到主线程更新单元格
                cell.textLabel.text = data;
            });
        }];
        [[NSOperationQueue mainQueue] addOperation:operation];
        return cell;
    }
    
  3. 优化单元格绘制
    • 避免在单元格的drawRect:方法中执行复杂的绘制操作。如果需要绘制复杂图形,可以使用Core Animation提前渲染到图片,然后在单元格中显示图片。
    • 例如,使用UIBezierPathCAShapeLayer提前绘制一个复杂图形到图片:
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 50, 50)];
    CAShapeLayer *layer = [CAShapeLayer layer];
    layer.path = path.CGPath;
    layer.fillColor = [UIColor redColor].CGColor;
    UIGraphicsBeginImageContext(CGSizeMake(50, 50));
    [layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    cell.imageView.image = image;
    

加载大量数据优化

  1. 分批加载
    • 从数据库或者网络加载大量数据时,采用分批加载的方式。例如,在从数据库查询数据时,使用LIMITOFFSET关键字(对于SQLite数据库)。
    • 在Objective - C中,结合FMDB库进行分批查询:
    FMDatabase *db = [FMDatabase databaseWithPath:databasePath];
    if ([db open]) {
        int pageSize = 100;
        int offset = 0;
        while (true) {
            FMResultSet *rs = [db executeQuery:@"SELECT * FROM YourTable LIMIT ? OFFSET ?", @(pageSize), @(offset)];
            while ([rs next]) {
                // 处理数据
                NSString *data = [rs stringForColumn:@"ColumnName"];
            }
            [rs close];
            if (rs.columnCount == 0) {
                break;
            }
            offset += pageSize;
        }
        [db close];
    }
    
  2. 使用缓存
    • 对于频繁访问的数据,使用缓存机制。可以使用NSCache(类似于NSDictionary,但具有自动释放内存的特性)或者SDWebImage等第三方缓存库(用于图片缓存)。
    • 使用NSCache示例:
    static NSCache *dataCache;
    + (NSCache *)dataCache {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            dataCache = [[NSCache alloc] init];
        });
        return dataCache;
    }
    
    - (NSString *)loadDataForIndex:(NSUInteger)index {
        NSString *cachedData = [[self class] dataCache][@(index)];
        if (cachedData) {
            return cachedData;
        }
        // 从其他地方加载数据
        NSString *newData = [self loadDataFromSource:index];
        [[self class] dataCache][@(index)] = newData;
        return newData;
    }
    
  3. 多线程加载与处理
    • 使用GCD或NSOperationQueue进行多线程加载和处理数据。
    • 使用GCD进行并行加载多个数据块:
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSArray *dataIndices = @[@0, @1, @2, @3];
    dispatch_group_t group = dispatch_group_create();
    for (NSNumber *index in dataIndices) {
        dispatch_group_async(group, concurrentQueue, ^{
            // 加载和处理数据
            NSString *data = [self loadDataForIndex:index.unsignedIntegerValue];
            // 处理数据
            NSString *processedData = [self processData:data];
            // 回到主线程更新UI(如果需要)
            dispatch_async(dispatch_get_main_queue(), ^{
                // 更新UI
            });
        });
    }
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 所有任务完成后的操作
    });
    
    • 使用NSOperationQueue实现类似功能:
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    operationQueue.maxConcurrentOperationCount = 4;
    NSArray *dataIndices = @[@0, @1, @2, @3];
    NSMutableArray<NSBlockOperation *> *operations = [NSMutableArray array];
    for (NSNumber *index in dataIndices) {
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            // 加载和处理数据
            NSString *data = [self loadDataForIndex:index.unsignedIntegerValue];
            NSString *processedData = [self processData:data];
            // 回到主线程更新UI(如果需要)
            dispatch_async(dispatch_get_main_queue(), ^{
                // 更新UI
            });
        }];
        [operations addObject:operation];
    }
    for (NSBlockOperation *operation in operations) {
        [operationQueue addOperation:operation];
    }
    [operationQueue addOperationWithBlock:^{
        // 所有任务完成后的操作
    }];
    

利用runtime机制进行动态优化

  1. 方法交换
    • 可以使用runtime的method_exchangeImplementations函数进行方法交换,实现对系统方法或者自定义方法的动态替换。例如,要在所有UIViewControllerviewDidLoad方法中添加日志记录:
    @interface UIViewController (Logging)
    @end
    
    @implementation UIViewController (Logging)
    + (void)load {
        Method originalMethod = class_getInstanceMethod(self, @selector(viewDidLoad));
        Method swizzledMethod = class_getInstanceMethod(self, @selector(swizzled_viewDidLoad));
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
    
    - (void)swizzled_viewDidLoad {
        NSLog(@"%@'s viewDidLoad is called.", self.class);
        [self swizzled_viewDidLoad];
    }
    @end
    
  2. 动态添加属性
    • 通过runtime的关联对象可以在运行时为类动态添加属性。例如,为UIButton添加一个自定义的identifier属性:
    @interface UIButton (CustomIdentifier)
    @property (nonatomic, copy) NSString *customIdentifier;
    @end
    
    @implementation UIButton (CustomIdentifier)
    static const char *customIdentifierKey = "customIdentifierKey";
    - (NSString *)customIdentifier {
        return objc_getAssociatedObject(self, customIdentifierKey);
    }
    
    - (void)setCustomIdentifier:(NSString *)customIdentifier {
        objc_setAssociatedObject(self, customIdentifierKey, customIdentifier, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    @end
    

多线程环境下利用GCD与NSOperationQueue优化性能

  1. GCD
    • 并行执行任务:使用dispatch_apply可以方便地并行执行一系列任务。例如,计算数组中每个元素的平方:
    NSArray *numbers = @[@1, @2, @3, @4];
    NSMutableArray *squaredNumbers = [NSMutableArray arrayWithCapacity:numbers.count];
    dispatch_apply(numbers.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
        NSNumber *number = numbers[index];
        NSNumber *squared = @(number.integerValue * number.integerValue);
        dispatch_sync(dispatch_get_main_queue(), ^{
            [squaredNumbers addObject:squared];
        });
    });
    
    • 控制任务执行顺序:可以使用dispatch_group来控制任务的执行顺序。例如,先执行两个异步任务,然后再执行一个汇总任务:
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_async(group, queue, ^{
        // 任务1
        NSLog(@"Task 1 started.");
        sleep(2);
        NSLog(@"Task 1 finished.");
    });
    dispatch_group_async(group, queue, ^{
        // 任务2
        NSLog(@"Task 2 started.");
        sleep(1);
        NSLog(@"Task 2 finished.");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 汇总任务
        NSLog(@"All tasks finished, starting summary task.");
    });
    
  2. NSOperationQueue
    • 创建和添加操作:可以创建NSOperation的子类或者使用NSBlockOperation来定义任务,然后添加到NSOperationQueue中。例如:
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"Operation 1 started.");
        sleep(2);
        NSLog(@"Operation 1 finished.");
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"Operation 2 started.");
        sleep(1);
        NSLog(@"Operation 2 finished.");
    }];
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    [operationQueue addOperation:operation1];
    [operationQueue addOperation:operation2];
    
    • 设置操作依赖:可以通过addDependency:方法设置操作之间的依赖关系。例如,让operation2依赖于operation1
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"Operation 1 started.");
        sleep(2);
        NSLog(@"Operation 1 finished.");
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"Operation 2 started.");
        sleep(1);
        NSLog(@"Operation 2 finished.");
    }];
    [operation2 addDependency:operation1];
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    [operationQueue addOperation:operation1];
    [operationQueue addOperation:operation2];