美文网首页iOS
简单记录下理解的iOS知识点

简单记录下理解的iOS知识点

作者: 龙子陵 | 来源:发表于2022-05-11 14:32 被阅读0次

    一.在查看优化经验文章的时候,经常会看到关于+loadinitialize两个方法,其中+load方法,是只要程序启动了,程序就会将所有代码加载到内存的代码区,此时,这个+load方法就会执行,同时,此方法会在main函数执行前就调用,而 initialize是该类初始化时候才调用。所以推荐在做启动优化的时候,要检查系统中的无用类、过期类,合并多余类、中间类,因为每次启动app,这些都会加载一遍。

    二.启动时间大体可分为两步(冷启动热启动就算了,不区分了):
    第一步:main函数加载之前,系统会加载动态库、引入类的+load方法等,所以需要:删减不必要的动态库,定期检查系统中无用类,过期类,合并多余中间类(多说一嘴,中小项目没必要用MVVM),除了系统动态库,个人创建的动态库多常见于一些自己封装的UIKit,可以考虑封装成静态库,或者合并。
    第二步:main函数加载之后,此时,主要是针对业务模块进行优化,
    1.日志、统计、广告业等必须一开始加载的功能,保留在didFinishLaunchingWithOptions中。
    2.推送、项目配置、环境配置、信息初始化等必须在主页面进入前完成的工作,放到广告页的viewDidAppear里进行。
    3.其他SDK和配置事件,由于启动时间不是必须的,所以可以放到首页的viewDidAppear里进行。
    4.初始化tabbar的时候,除了首页外,延迟其他几个控制器的创建时间,比如“个人中心”,“订单”等模块,用空的viewController占位,第一次点击的时候再初始化加载。

    其他更厉害的优化,可以参考抖音团队分享的:基于二进制文件重排的解决方案。http://www.zyiz.net/tech/detail-127196.html

    三:KVO是如何实现监听的
    KVO底层使用了 isa-swizling的技术.
    OC中每个对象/类都有isa指针, isa 表示这个对象是哪个类的对象.
    当使用KVO给对象的某个属性注册了一个 observer,系统会创建一个新的中间类(intermediate class)继承原来的class,把该对象的isa指针指向中间类(这样,这个对象的类其实就改变为这个新创建的中间类)。
    然后中间类会重写该属性的setter方法,当这个属性的值更改的时候,会在调用setter之前调用willChangeValueForKey, 调用setter之后调用didChangeValueForKey,以此通知所有观察者值发生更改。
    之后重写了 -class 方法,企图欺骗我们这个类没有变,就是原本那个类。

    四:NSTimer的循环引用和Block循环引用有什么区别?
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(timerAction) userInfo:nil repeats:YES];
    同样是用__weak修饰self,为什么NSTimer会导致循环引用,而block不会呢?
    因为block中,引用的是weakSelf这个指针,而NSTimer引用的是weakSelf指针指向的内存地址,所以只要NSTimer不释放,就会一直持有weakSelf指向的self,而因为timer被runloop强持有,runloop又是常驻的,所以timer一直没有被释放。
    NSTimer解决循环引用的几种方式:
    1.离开页面的时候,置空timer
    2.ios 10之后,系统帮我们对NSTimer进行了block的封装,防止block循环引用即可
    3.做个中间类,帮助控制器持有NSTimer
    4.用GCD的定时器

    五:GCD什么情况下会开启多线程?
    1.同步函数串行队列:
    不会开启新线程,在当前线程执行任务
    任务串行执行,一个接着一个
    会产生死锁
    2.同步函数并行队列:
    不会开启新线程,在当前线程执行任务
    任务依然一个接一个
    3.异步函数串行队列:
    开启一个新线程,任务一个接一个执行
    4.异步函数并行队列:
    开启多个线程,任务并发执行
    5.创建队列的方式:

    - (void)test {
        //串行队列
        dispatch_queue_t serial = dispatch_queue_create("com", DISPATCH_QUEUE_SERIAL);
        //并发队列
        dispatch_queue_t concurrent = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
        //主队列
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        //全局队列
        dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    } 
    

    根据官网介绍,多线程创建需要耗费一定的内核空间,主线程一般是1MB,非主线程一般是16Kb到512KB,创建线程的时间大概是90微秒,切换线程,也会消耗一定的资源时间

    六:栅栏函数
    1.dispatch_barrier_async: 当前任务队列中,前面的任务执行完毕才会执行barrier中的逻辑,以及barrier后加入该队列的任务,但是不影响该栅栏函数所在队列后面函数的执行。
    2.dispatch_barrier_sync:作用相同,区别是会堵塞队列后面的函数执行。
    举例:

        dispatch_queue_t concurrentQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(concurrentQueue, ^{
            NSLog(@"1");
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"2");
        });
        dispatch_barrier_async(concurrentQueue, ^{
            NSLog(@"3:%@",[NSThread currentThread]);
        });
        dispatch_async(concurrentQueue, ^{
            NSLog(@"4");
        });
        NSLog(@"5");
    } 
    

    上面代码打印出来的数据,1,2,5的排序是不固定的,,但是3、4一定是最后两个,3一定在4前面。
    3.栅栏函数在全局队列中是不生效的,因为全局队列中,不仅有你的任务,还有其他系统的任务,如果加barrier,不仅会影响你自己的任务,还会影响系统的任务,对于全局队列来说,barrier相当于普通的异步函数。
    4.栅栏函数可以当成锁来用:

    NSMutableArray *array = [NSMutableArray array];
    dispatch_queue_t concurrentQueue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 1000; i++) {
        dispatch_async(concurrentQueue, ^{
            [array addObject:@(i)];
        });
    }
    

    上面代码中,多线程操作一个数组array,在addObject的时候有可能存在同一时间对同一块内存空间写入数据(因为多线程是无序的,i=4的时候和i=100的时候,有可能i=100先运行,那么这时候往数组里插数据,就有可能数据错误,此时就会崩溃报错)。
    比如写第3个数据的时候,当前数组中数据是(1,2)这个时候有2个线程同时写入数据就存在了(1,2,3)(1,2,4)这个时候数据就发生了混乱造成了错误。
    将数组添加元素的操作放入dispatch_barrier_async中:

    dispatch_queue_t concurrentQueue = dispatch_queue_create("Hotpot", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 1000; i++) {
        dispatch_async(concurrentQueue, ^{
            dispatch_barrier_async(concurrentQueue , ^{
                [array addObject:@(i)];
            });
        });
    }
    

    七.信号量
    dispatch_semaphore_create(long value);
    初始化一个值为value的信号量,value等于几,就支持最多几个线程并发访问。

    dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
    此函数会让信号量-1(通常来讲,我们会将信号量初始化为1),信号量小于0,则线程进入堵塞、等待状态。

    dispatch_semaphore_signal(dispatch_semaphore_t dsema);
    此函数会释放信号量,让信号量+1,信号量不为1,则该线程继续执行

    信号量的作用:
    1.可以限制并发队列里最大并发数
    2.配合线程组,解决多个网络请求后,统一刷新页面、处理数据的问题
    3.保护数据上锁

     dispatch_queue_t queue = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
     NSMutableArray *array = [NSMutableArray array];
     dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
     for (int i = 0; i < 10000; i++) {
       dispatch_async(queue, ^{
           dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//此时当前并发队列,只有一个线程能访问
           NSLog(@"添加的值:%d  当前线程%@",i,[NSThread currentThread]);
           [array addObject:@(i)];
           dispatch_semaphore_signal(semaphore);
       });
     }
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"1 start");
        NSLog(@"1 end");
    });//由于初始值为0,wait后,信号量-1,小于0,所以后面的打印方法不执行
    
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"2 start");
        NSLog(@"2 end");
        dispatch_semaphore_signal(sem);
    });//此异步任务先执行
    

    打印出来的值:

    2 start
    2 end
    1 start
    1 end
    

    八:线程组:
    控制线程任务执行顺序,类似于栅栏函数的作用

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue1 = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_async(group, queue, ^{
        sleep(3);
        NSLog(@"1");
    });
    
    dispatch_group_async(group, queue1, ^{
        sleep(2);
        NSLog(@"2");
    });
    
    dispatch_group_async(group, queue1, ^{
        sleep(1);
        NSLog(@"3");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"4");
    });
    
    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"5");
    });
    

    任务5永远在任务1、2、3、4之后执行。

    相关文章

      网友评论

        本文标题:简单记录下理解的iOS知识点

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