美文网首页
iOS IM启动逻辑梳理及优化

iOS IM启动逻辑梳理及优化

作者: 某非著名程序员 | 来源:发表于2021-03-22 20:19 被阅读0次

    一、app启动原理

    1.app启动分为冷启动和热启动。

    App 的启动主要包括三个阶段:main() 函数执行前;main() 函数执行后;首屏渲染完成后。

    1. main() 函数执行前;

    加载Mach-o文件;加载动态库;Objc类、分类、方法初始化;+load()方法初始化。

    可以优化的点:减少动态库加载,即合并动态库;采用懒加载的方式调用类或方法;+load()使用+initialize()方法替换;控制C++全局变量数量。

    2. main() 函数执行后

    从main()函数到didFinishLaunchingWithOptions方法里首屏渲染完成。
    一般app的配置信息,初始化,信息上报等都在这里。放在了首屏渲染之前。

    可以优化的点:按功能梳理出首屏必要的初始化功能。

    3. 首屏渲染完成后

    主要完成的是,非首屏其他业务服务模块的初始化、监听的注册、配置文件的读取等
    问题:什么样的功能适合放在首屏渲染后呢?应该先检测方法耗时,按耗时严重的去重点关注。

    app启动速度的监控
    第一种:定时抓取主线程的方法调用栈,计算一段时间里的各个方法耗时。
    第二种:对objc_msgSend方法进行hook来掌握所有的方法执行耗时。

    自定义方法耗时工具
    fishhook:在 iOS 上运行的 Mach-O 二进制文件中动态地重新绑定符号

    二、启动时加载逻辑梳理

    1. 通讯之前有哪些逻辑?

    1. 请求LBS接口(获取socket连接地址)
    2. 拿到IP、端口后socket连接

    2. 一款IM软件,启动时需要哪些逻辑。

    1. 获取服务端时间
    2. 同步拉取个人信息、名片信息、同步个人设置(3个接口)
    3. 同步好友信息、同步最近联系人(2个接口)
    4. 同步组织信息
    5. 同步群组资料信息
    6. 同步群成员列表及禁言信息(2个接口)

    思考及优化

    1. LBS接口的作用
      LBS仅返回一个地址,首先必须返回是IP地址,避免DNS解析耗时。
      LBS接口还有一个作用,就是负载均衡,服务端有多台服务器,但那台服务器处于空闲,由服务端返回最优的IP.

    2. LBS的接口是HTTP的,在网络不稳定时,也是非常耗时的
      对LBS返回的ip做缓存,当ip连接失败时,再请求LBS接口

    3. 第二步的逻辑有些多
      接口能否合并,如个人信息与个人设置:对于功能不同的接口更愿意遵循单一职责原则,合并到一起反而显得臃肿。可以采用第4步的方式来解决这个问题。
      是否所有消息都是必须拉取,如群成员列表是否进入会话才关注。
      已经加载过的,考虑增量拉取。

    4. 不要把所有接口都串行,屡清几条线并发。之前的逻辑:

    dispatch_group_async(syncGroup, syncQueue, ^{
        BLLogDebug(@"sync");
        [self getNetworkTimestamp:^{
    //            BLLogDebug(@"【INFO】1、获取服务器时间成功");
            dispatch_semaphore_signal(syncSemaphore);
        }];
        dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
    
        [self syncLoginUserProfileWithCallback:^{
    //            BLLogDebug(@"【INFO】2、同步账户信息成功");
            dispatch_semaphore_signal(syncSemaphore);
        }];
        dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
    
        [self syncFriendsWithCallback:^{
    //            BLLogDebug(@"【INFO】3、同步好友信息成功");
            dispatch_semaphore_signal(syncSemaphore);
        }];
        dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
    
        [self syncEnterprisesWithCallback:^{
    //            BLLogDebug(@"【INFO】4、同步组织信息成功");
            dispatch_semaphore_signal(syncSemaphore);
        }];
        dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
    
        [self syncImGroupWithCallback:^{
            BLLogInfo(@"【INFO】5、同步群组信息成功");
            dispatch_semaphore_signal(syncSemaphore);
        }];
        dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
    
        [self syncImgroupMemberWithCallback:^{
            BLLogInfo(@"【INFO】6、同步群组成员信息成功");
            dispatch_semaphore_signal(syncSemaphore);
        }];
        dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
    });
    
    dispatch_group_notify(syncGroup, syncQueue, ^{
    //        BLLogDebug(@"【INFO】7、同步完毕");
        [self syncComplete];
    });
    

    修改完后:

    1. 从所有串行到并发三条线:同步账户信息;同步好友信息;群组及群成员(必须串行)
    2. 同步账户信息:同步拉取个人信息、名片信息、同步个人设置,在个人信息返回后就算完成,名片信息和同步个人设置慢慢拉取。
    3. 获取服务器时间,放在最后。前面的接口都有返回时间。
    dispatch_queue_t syncConcurrentQueue = dispatch_queue_create("sync", DISPATCH_QUEUE_CONCURRENT);
    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
    
    BLGCDGroupManager * gcdGroup = [[BLGCDGroupManager alloc] initWithGroup:syncGroup queue:syncConcurrentQueue];
    
    [gcdGroup enter];
    dispatch_group_async(syncGroup, syncConcurrentQueue, ^{
        [self syncLoginUserProfileWithCallback:^{
            BLLogWarn(@"<<< t3_1_0 同步账户信息 %f",CFAbsoluteTimeGetCurrent()-startTime);
            [gcdGroup leave];
        }];
    });
    
    [gcdGroup enter];
    dispatch_group_async(syncGroup, syncConcurrentQueue, ^{
        [self syncFriendsWithCallback:^{
            BLLogWarn(@"<<< t3_1_1 同步好友信息 %f",CFAbsoluteTimeGetCurrent()-startTime);
            [gcdGroup leave];
        }];
    });
    
    [gcdGroup enter];
    dispatch_group_async(syncGroup, syncConcurrentQueue, ^{
        [self syncImGroupWithCallback:^{
            [self syncImgroupMemberWithCallback:^{
                BLLogWarn(@"<<< t3_1_2 同步群组、群组成员信息 %f",CFAbsoluteTimeGetCurrent()-startTime);
                [gcdGroup leave];
            }];
        }];
    });
    
    [gcdGroup enter];
    dispatch_group_async(syncGroup, syncConcurrentQueue, ^{
        [self syncEnterprisesWithCallback:^{
            BLLogWarn(@"<<< t3_1_3 同步组织信息 %f",CFAbsoluteTimeGetCurrent()-startTime);
            [gcdGroup leave];
        }];
    });
    
    dispatch_group_notify(syncGroup, syncConcurrentQueue, ^{//7、同步完毕
        BLLogWarn(@"<<< t3_1 同步登录信息、好友、群组、群成员耗时: %f",CFAbsoluteTimeGetCurrent()-startTime);
        [self syncComplete];
        [self getNetworkTimestamp:nil];
    });    
    

    三、启动时消息加载慢问题排查

    收到反馈:一条信息从启动或退到后台后再进入app,新消息展示大概6、7s。
    一个IM软件,这肯定是不能容忍的。

    新消息展示流程

    启动几个时间段:如从didFinishLaunchingWithOptions->首页消息展示经过了哪些过程。

    1. 请求lbs
    2. 链接socket
    3. socket链接后接收服务端推送的消息
    4. 收到消息后进行首屏会话消息渲染与红点展示

    思考

    偶现的问题。听到反馈,先确定原因。
    排查原因:首先希望测试能复现;其次从代码角度排查。

    1. 打印出各时间段耗时与总耗时,总时间超过5s进行上报。
    2. 服务端从socket连接上、到消息推送耗时加日志。

    解决

    1. 发现最耗时的时间段:在socket连接成功后到消息推到客户端占了5s多。
    2. 服务端逻辑问题:先查询所有会话,再去过滤未读的会话推送给客户端。当会话量大时,耗时严重。
    3. 服务端直接查询未读的会话,再做其他处理。限制在400ms。

    四、启动拉取个人信息偶现闪退

    1.dispatch_group_t

    场景:一般在并发多个网络请求都返回时,处理逻辑会用到。

    dispatch_group_enter与dispatch_group_leave需要成对出现。但dispatch_group_leave出现比dispatch_group_enter多时会崩溃。
    例如在网络异常时,出现超时重试多次回调了block。这种场景不是必现,也不好复现,bugly监控只能看到在某个函数崩溃。我认为直接使用系统提供的是不安全的。


    group使用崩溃.png

    我的解决方案:

    1. 封装dispatch_group_t使用,当leave出现异常时使用NSAssert处理
    @interface BLGCDGroupManager()
    {
        dispatch_group_t _group;
        dispatch_queue_t _queue;
        NSInteger _count;
        NSLock * _lock;
    }
    @end
    
    @implementation BLGCDGroupManager
    
    - (instancetype)initWithGroup:(dispatch_group_t)group
                            queue:(dispatch_queue_t)queue{
        self = [super init];
        if (self) {
            _group = group;
            _queue = queue;
            _lock = [[NSLock alloc] init];
        }
        return self;
    }
    
    - (void)enter{
        if (_group) {
            [self add];
            dispatch_group_enter(_group);
        }
    }
    
    - (void)leave{
        if (_group && _count > 0) {
            [self sub];
            dispatch_group_leave(_group);
        }else{
            NSAssert(@"leave使用出现异常", nil);
        }
    }
    
    - (void)add{
        [_lock lock];
        _count ++;
        [_lock unlock];
    }
    
    - (void)sub{
        [_lock lock];
        _count --;
        [_lock unlock];
    }
    
    @end
    
    1. 业务上的处理
      找出dispatch_group_leave为什么会出现两次。

    相关文章

      网友评论

          本文标题:iOS IM启动逻辑梳理及优化

          本文链接:https://www.haomeiwen.com/subject/ocjthltx.html