iOS开发底层进阶视频笔记

===底层分析方法:

1,在alloc的地方下一个符号断点(symbol breakpoint);

苹果开源源码汇总:https://opensource.apple.com

这个地址用的更直接:https://opensource.apple.com/tarballs/

2,在alloc这个地方下一个断点,然后摁住options键,然后命令行上面那个就有一个往下走(step into)的箭头,点击就进入内部了,然后在加一个符号断点;

3,还是在alloc的地方放一个断点,然后点击Debug->Debug workflow->Always show dissasebily,然后就进入内部栈调用来了(用的最多)

===内存对齐:16字节对齐,之前都是8字节对齐,如果是8字节对齐,对象之间间距太近,出错的概率会增加,变成16字节的话,都有点剩余,这样的内存更安全。为什么是16呢,因为我们每个对象都是8个字节,我们研究的时候都是要最基本对象内存翻倍开始的;总的来说就是16字节对齐的话,这样更利于数据读取,然后这样读取的时候更安全,不会访问到其他对象的内存里面去。

===alloc 核心方法

1,cls->instancesize:先计算出需要的内存空间大小

2,calloc:向系统申请开辟内存,返回地址指针

3,obj->initinstanceisa:关联到相应的类

===init方法内部:return (id)self;强转了一下,为什么?在alloc的时候已经有个地址指针,这里还这样?

因为这是一个工厂设计,构造方法,你可以继承之后重写,因为一个arrar,一个objc返回的东西是不一样的啊,所以需要一个构造方法,可以去重写,实现不同的东西

===new方法内部:return [callalloc (self, false) init] == (alloc init) 其实2个没有什么本质区别

但是一般不建议用new,因为有时候alloc init出来的东西跟new出来的不一样,为什么呢,因为alloc init的时候我可以带参数,这样出来的东西跟你直接new出来的东西就不一样了,所以一般建议用alloc init,这样可以带参数,重写init方法。

===影响内存排布和大小的是属性@property(16字节对齐)

===block分类:NSGlobalBlock(全局静态), NSMallocBlock(堆block), NSStackBlock(栈block)

===block也是一个结构体,对象,匿名函数,持有代码,保存代码(把函数给它),随时随地使用

===block特性:自动捕获变量

对象--struct-->增加属性-->栈-->操作符重写-->copy-->堆

===nslog;printf;这些的本质都是io耗时操作,在做性能优化的时候尽量不要用,可以用宏定义一些调试代码,比如用lldb之类的

===解决循环引用(一层block): __weak typeof(self) weakSelf = self;

===block里面的block的时候(循环嵌套):__strong typeof(self) strongSelf = weakSelf;

===__block(当前vc对象时)会把被block捕获的对象copy一份到struct里面,然后就会相互持有,然后循环引用,只有在block方法里面把捕获的对象置为nil,才能打破这个循环引用,也可以直接在参数里传一个vc进去,这样就不需要用self.来获取属性,这样就不会循环引用了

===栈地址:0x7开头;堆区地址:0x6开头;静态区:0x1开头

===在block里面捕获外部变量a的时候,假如a=10,没加__block的时候会报错,所以问题就是加了__block和不加__block区别是什么,底层原理是什么?

没有加__block的时候:在block里面捕获的变量就是值传递,你只有readonly的权限,所以不能修改,只能读取值;

加__block的时候:在block里面捕获的变量就是指针传递,有readwrite的权限,你有了指针,就找到了内存空间,然后你就可以修改变量的值了啊;

===进程(应用程序)是没办法触碰到物理内存的,都是虚拟内存

===每个进程都有一个映射表(虚拟页表),就是虚拟内存和物理内存的映射

===虚拟内存出现原因:首先是因为物理内存的不足问题,然后就是安全问题,之前的系统直接用物理内存的时候,那些数据很容易被人修改和利用,有很大的安全隐患。

===macOS,linux的pagesize是4k,而iOS的pagesize是16k

===缺页异常:就是说当进程要访问一个没有通过页表映射到物理内存的数据时,会有缺页异常,阻塞当前进程,然后系统就会把它从磁盘加载到内存,然后cpu通过mmu再去使用这个数据,这个时间很短,用户基本感知不到的

===缺页异常(page fault)和页面置换,这两个有时候会一起出现的

===什么样的时刻缺页异常会同时大量的出现?app启动的那一刻,大量的数据加载,方法调用,所以会造成耗时

===通过在buildsetting中启用linkmap可以看到我们的文件编译顺序,同时可以在build phase 里的complie source里面调整文件的编译顺序,同时文件里的编译顺序是跟你写的方法顺序有关,上面的就在前面,下面的就在后面。

===启动优化之二进制重排:假如有500个缺页异常,启动时刻需要调用的方法分别分布在了这500页数据里面,这500页都用完了吗?有空闲的地方吗?全部需要加载吗?

我们可以通过启动的调用顺序来进行排列,重排它。在项目里面用一个_order文件来把自己想编译的文件,按照自己的意愿排列,这样编译的时候最终就会按照我们的意愿排列了,这样就能保证我们想要优先编译的,必须编译的文件可以被优先编译。这个order文件可以自己新建,然后在build setting 里面搜索order file然后指定好这个文件的路径就可以了,然后你重新编译,在看link map文件的时候, 你就会发现文件里的符号编译顺序就会按照你在文件里写的顺序来编译了

===我如何获取项目整个执行的流程?或者说启动时刻所调用的方法顺序?

fishhook是个工具,可以用来hook所有的系统函数,所以假如我用fishhook来hook方法底层objc-msgsend这个方法,是不是每当你调用一次方法都要走一次objc-msgsend,所以这样我是不是就可以拿到所有的方法了?但是问题是你怎么hook它的objc-msgsend这个方法,它是可变参数哦,还有就是这个是只能hook到oc的方法,万一有swift的呢,或者block,或者c方法呢,这些你都是拿不到的哦,或许通过编译器插桩可以做到哦,不过你要去学习编译器相关的东西哦,比如llvm, clang这些东西都要学的哦

===mach-O包括:mach header; load command; 二进制数据

===mach-O文件这样的格式是在编译的时候就确定好了,链接器ld开始工作之前,编译已经结束了,意思就是说编译完了链接器ld才开始链接静态库;

===我们链接一个库,不管是动态库还是静态库,都分三步:第一就是库的头文件,也就是.h文件(header search path);然后就是库文件所在的位置(search paths),也就是要链接的位置;最后就是库的名称是什么;

===ld标志:链接静态库时才有下面这些标志,仅限静态库

-all_load:所有的东西都链接进去

-objc:只链接oc的代码进去

-force_load:手动指定那些文件需要加载

-no_allload:不是全部加载,由链接器自动决定

-load_hidden:指定某个文件在链接的时候把它隐藏

===静态库和动态库哪个对app的体积影响比较大?

静态库:就是.o文件的集合,零碎的东西多;但是集成到app的时候,可以通过指定参数设置,使得app的体积比用动态库的时候还小,因为动态库虽然本身体积小,但是需要全部加载才能使用。

动态库:.o文件的代码的集合,所以本身体积比较小,跟静态库里面的代码是一样的,只是少了除代码之外的东西;

===如果文件没做其他的处理,.a的肯定是静态库;如果是.framework后缀的,其实它有可能是静态库(里面会有.a的文件);也有可能是动态库(里面会有.dylib文件);所以需要从里面的文件才能区分是静态库还是动态库;

===@rpath就是run path searchpaths,dyld搜索路径,运行时dyld按顺序搜索路径列表,以找到动态库,@rpath可以保存一个或者多个路径的变量

===方法的本质,sel是什么,imp是什么,两者之间关系又是什么?

objc_msgsend(消息接收者,消息主体)

汇编快速查找(cache_t ->bucket(sel imp))

慢速查找-遍历: methodlist 自己->父类->nsobject 继承

多态-机会

动态方法决议 对象 + 类 (第一次的时候)

转发:快速转发

慢速转发 selector target 签名:参数和返回值

sel是方法编号,imp函数指针地址,objc_msg:消息的接收者+消息的主体;

sel相当于一本书的目录内容,imp相当于一本书的页码,通过这2个就可以找到具体的函数内容

通过sel找到imp,再通过imp找到具体的函数内容,一一对应,绑定;

===离屏渲染底层原理

1,内存-帧缓冲区-显示控制器-显示在物理屏幕上

2,图片撕裂:显示的时候是一行一行扫描显示的,cpu+gpu协作,上一个图片数据还没显示完,下一帧数据又填满,替换了,所以图片撕裂了(单缓冲区的时候)--解决方案:垂直同步+双缓冲区

3,掉帧:上一次的显示结果重复出现,因为下一帧的数据没准备好--解决方案:垂直同步+三缓冲区

4,什么时候去开辟离屏渲染缓冲区呢?图层合并的时候就会触发离屏渲染,因为要开辟多个离屏渲染缓冲区,然后最后呈现图片的时候把结果合并,就是图层数据合并,也就是多个离屏渲染缓冲区的数据合并。

5,离屏渲染产生原因:就是因为要显示像遮罩,圆角,毛玻璃效果啊这种东西的时候,苹果没办法一次性拿到数据直接显示,所以需要开辟两个离屏渲染缓冲区,分别处理数据,最后再把两个离屏渲染缓冲区的数据合并,这样就造成了离屏渲染。

6,圆角有离屏渲染是因为图片是有几层显示的,我做圆角的时候要分别对几个图层进行切圆角,然后把数据分别放在离屏渲染缓冲区,最后把几个层的数据都切完了,再做合并,这样就会造成离屏渲染,因为要分步处理,再合并

7,为什么要离屏渲染:因为要多图层组合,要存储中间数据,最后合并。所以导致离屏渲染。

===block()这样的调用就是调用它保存的底层的函数指针(funptr),也是一个静态函数,存在mach-o中的;当用[block copy]这样调用的时候,它就是一个对象来使用;当然,判断是不是对象,只要看它有没有isa就行;

===globalblock:位于全局区,在block内部不使用外部变量,或者只使用静态变量和全局变量

===mallocblock:位于堆区,在block内部使用局部变量或者oc属性,并且赋值给强引用或者copy修饰的变量

===stackblock:位于栈区,与mallocblock一样,可以在内部使用局部变量或者oc属性,但是不能赋值给强引用或者copy修饰的变量

===block,平常我们写block的时候,block前面没有修饰符,其实它默认的修饰符就是__strong,也就是说,如果它没使用局部变量或者oc属性,那是全局block,如果使用了局部变量或者oc属性,那就是堆区block;

===什么情况下会触发block的拷贝?也就是说从栈区拷贝到堆区;触发拷贝的时候具体它又是怎么做的呢?首先我们明白如果block是在堆区的话,会把block当作一个对象,引用计数+1,然后返回;如果是在全局区的话,不加引用计数,不拷贝,直接返回;只有在栈区的时候,才会触发到block的拷贝,那具体的拷贝步骤是怎么样的呢?

1,首先在堆上重新开辟一块和ablock相同大小的内存,调用malloc方法

2,将ablock内存上的数据全部拷贝到新开辟的内存上

3,将ablock的isa指针由原来的栈区,指向新的堆区block

===block的release方法怎么做的呢?其实具体跟上面拷贝的时候差不多,首先要判断block是什么类型的,如果是全局区的,直接返回,因为内存不归它管,是栈区的也不用管,因为栈区的内存是系统自己管,只有判断block是在堆区的时候才需要自己处理,处理的步骤也跟具体的oc对象一样啊,引用计数-1,当引用计数为0的时候,就调用free方法,同时还要处理一下它之前捕获的变量,也要释放掉;

===block捕获变量分2种情况:

1,如果block是被__strong修饰的,那捕获的变量也是strong类型的强引用,这个时候变量的引用计数+1,这就是为什么会造成循环引用的原因

2,如果block是被__weak修饰的,那捕获的变量也是__weak类型的弱引用,block里面也是用一个__weak的变量来接收捕获的变量,然后变量会被加入到弱引用表中,不持有,引用计数不加1,用完了就置为nil,这就是为什么__weak可以解决循环引用的原因

===用__weak修饰的变量,就是把变量放入了弱引用表,你使用变量的时候它的引用计数不会发生改变,不加1,不持有;

===runloop可以做什么?

1,处理crash,当我捕获异常后,我就强制程序进行runloop,这样程序就不会闪退了

2,保持线程存活,通常的子线程都是处理完事情就销毁了,我们可以通过runloop让子线程不销毁

3,检测,优化app的卡顿问题

===线程和runloop是键值对存在的,key就是线程,value就是runloop,一一对应

===线程保活:当有大量的耗时任务要在后台执行时,通常后台子线程都是处理完就销毁了,但是为了后台子线程处理完不销毁,我们就可以用runloop处理。就是添加source,timer,observer;像主线程就不需要保活,它就是会一直存在的。

===runloop的mode的observer专门用来做卡顿监测的,卡顿主要跟硬件有关,也就是cpu,gpu;

1,影响cpu性能:io任务,过多的线程抢占cpu资源,温度过高降频

2,影响gpu性能:显存频率,渲染算法,大计算量,不过这些基本不能通过代码改变,所以卡顿的处理只能从cpu那里考虑;

===那我们如何监测卡顿呢?

1,fps=59.94 hz/s

2,ping:测试网络的,卡顿的时候主线程短时间内无响应,尝试从子线程和主线程通信,获取卡顿延时

3,runloop:用observer来监测,线程消息的处理都是依赖于runloop的驱动,

===除了线程之外,还有什么方式来保活呢?那就是nscondition-条件锁,但是这个不涉及runloop;

===React Native:就是一套代码放在服务器端,可以在iOS,安卓上下载下来使用,底层渲染就是用的原生来渲染的;缺点:1,太依赖原生了,在更新迭代的时候 被原生牵着鼻子走,还有就是同一套界面在iOS和安卓有可能呈现出来的效果不一样,比较底层都是iOS和安卓的原生控件,呈现有时候有所区别

===Flutter:跨平台技术,有自己的渲染引擎,这个是跟React Native最大的区别,它在iOS和安卓机器里分别安装自己的引擎,这个代码就分别由它自己的引擎解析。

所以优点:1,跨平台,界面不依赖原生,所以会长的一样 2,系统的更新不依赖iOS,安卓系统,因为它有自己的渲染引擎,更新迭代不像React Native那样受限于原生系统的控件更新,这点就很流弊;

===swift的闭包是一个捕获了上下文常量或者变量的函数;闭包是一个引用类型,函数的地址+堆的内存地址;堆的内存地址就是用来存储捕获的常量或者变量的。

===方法调度

oc:objc-msgsend方法列表中查找,这个是可变的

swift:1,函数表(v-table) 查表过程 2,静态派发(extension直接地址调用),不在表中

===swift -->swiftc --> sil (这个就是swift通过编译器变成的中间语言)

===oc --> clang --> c++

===编译源码就是为了了解底层调用情况

===方法保存在哪里?

1,如果是对象方法,那就存在类的方法列表里,对象里的isa指针就指向了类,找到类

2,如果是类方法,那就存在元类的方法列表里

===内存(指针)平移:这个的关键是要懂的对象的内存结构是怎么样排布的,访问的那个属性是什么类型的很重要,看它占几个字节,最后就会根据这个平移几个字节。

对象的内存结构:1,isa(8个字节) 2,属性依次排列

===聊聊你对block的理解,拷贝和捕获外部变量以及释放流程

block是一个对象,我们怎么探索呢,那肯定是通过clang,然后把它编译成c++代码,这样我们就可以看到它的底层数据结构了;oc的底层就是c或者c++;

===set方法的底层就是对新值的retain,对旧值的release;

===对象的引用计数存在isa中,通过判断是否是nonpointerisa,来决定是放在sidetable中还是在extra_rc这个字段中,当在extra_rc中存满的时候,会放一半到sidetable中,剩下的一半还是在extra_rc这里,extra_rc这个字段在从第45位开始,有19位;不是全部放在sidetable是因为操作表也是耗性能的哦

===启动优化:

1,整理自己放荡不羁的代码,发挥cpu的优点,多开线程,这个是业务层面的考虑

2,启动时间在项目中我们可以分为main之前(premain阶段)和main之后,项目大之后,其实在main之前对启动时间影响比较大,抛开业务层面来说;这个我们可以通过dyld就可以得到,加载了动态库,苹果建议动态库最多是4个,大于4个了我们可以通过合并动态库进行优化。

===aslr,这个是一个随机值,它是为了解决安全问题,它在每个虚拟地址前面加这个aslr,保证每次虚拟地址就不一样了,安全性提高了。虚拟地址是一个假地址,它是为了解决地址不够用和进程数据安全的问题,也就是物理内存不足的问题。

===代码存放的顺序和写代码的顺序有关,和文件排列顺序有关,和执行顺序没有半毛钱关系。

===图片渲染流程:在iOS平台上,图片的解码是cpu来进行的,当你有多个png的图片需要显示的时候,cpu不会马上全部解码,是等到你确定要显示图片之前,cpu才进行解码,不是一下子全部解码的哦;

===我们显示器显示的东西不是直接从内存拿到的,是从帧缓冲区拿到的,内存->帧缓冲区->显示控制器->显示器,之前帧缓冲区是在主存里的,后来主存就把帧缓冲区独立出来,就变成了独立显卡,就是有独立的显示处理器存储器和帧缓冲区。(这个是计算机的)

===屏幕成像和卡顿:成像是光栅扫描,逐行扫描显示的,图片卡顿和撕裂是因为cpu和gpu的不同步,上一帧还没显示完全,帧缓冲区数据满了,被替换了;因为这个是单缓冲区。

===垂直同步:就是从第一行扫描到最后一行,把整个图片扫描完,显示完了,然后发送一个信号回去,再开始从帧缓冲区读取下一帧的数据,意思就是把一张图片显示完再去拿下一张的数据,这就叫垂直同步;

===双缓冲区:就是有一个前缓冲区,一个后缓冲区,开始先从前缓冲区拿数据,这个时候数据来了放在后缓冲区,什么时候改变指针呢,就是上面垂直同步信号来的时候,我再把指针指向后缓冲区,显示下一个图片,新来的数据就放在前缓冲区,就是这样根据垂直同步信号依次交替;

===掉帧:就是出现了重复的帧,假如我有a,b两帧,我在显示a的时候,你在准备b,当我显示完a帧了,但是你的b帧还没准备好,这个时候不能不显示东西啊,所以我还是要继续显示a帧,我等你b帧完全准备好了,我才显示你的b帧,像这样重复出现上一帧的就叫掉帧。解决方案就是:垂直同步+三缓冲区

===光栅化,就是用顶点着色器,把需要显示的像素点找到,确定好;然后才用片元着色器,也叫像素着色器,给刚才确定好的像素点着色(填充颜色),这样图片就显示出来了。

===普通渲染:app从帧缓冲区拿数据的频率是60fps,也就是1秒拿60次;

===离屏渲染:当数据是从离屏渲染缓冲区拿的时候,什么时候出现离屏渲染缓冲区呢?就是你需要做图层合并的时候;

===离屏渲染造成的效率问题:1,随意开辟离屏渲染缓冲区是需要消耗内存的 2,你需要有2个(或者多个)离屏渲染缓冲区,然后把结果合并,这些操作都是比较耗时的,这就是平常说的切换上下文,这个就比较耗时啊,基于这2点,所以离屏渲染会造成效率问题

===不是说显示图片的时候有多层就会离屏渲染,关键看看它的多层是否需要合并,如果不需要合并,就不会触发离屏渲染,就是普通渲染,如果需要合并的才会触发离屏渲染;像切圆角造成离屏渲染就是因为你需要对多个图层分别切圆角,放在离屏渲染缓冲区,最后再合并,所以会离屏渲染,正常显示一个图片+背景色是不会造成离屏渲染的,虽然它有多层;如果单层切圆角是不会触发离屏渲染的,因为一次完成,不需要分别存储再合并;

===底层原理+性能优化+项目体现技术

===GCD = 函数 + 队列; 底层libdispatch这个库

===队列是一种数据结构,fifo;是对任务进行调度的,不参与执行,只负责调度;

===同步队列,一个一个顺序执行;异步队列,同时执行,谁先执行完不确定;dispatch_async这个是异步函数,不堵塞,直接可以往下面继续执行;dispatch_sync这个是同步函数,堵塞,必须等到它的代码执行完了在它下面的代码才能执行;

===main()函数之前,其实有很多东西的,像主线程的创建,dyld链接器连接库等等

===runloop底层就是一个循环,do while循环,可以通过打断点,或者clang编译看到源码

===mach port是用来做什么的?是用来进行跨线程通讯的(如2个子线程之间),它也是跨进程(app)通讯的基石。

===mach port怎么进行线程间通讯?跟runloop有什么关系?

其实假如a线程有一个接受消息的权限,它就发出这个请求,然后block当前线程,一直在等待消息的到来;runloopwakeup这个方法的底层就是通过mach port然后去发送消息,唤醒线程,它有send msg权限,当它把msg发送到刚才的a线程后,线程就被唤醒了,然后a线程解除刚才的block,然后继续往下执行;

===runloop是不是一直在循环?其实它是通过底层的mach port set,这个端口集来管理的

===runloop的timer为什么不准?1,底层的dispatch_source_timer和mk_timer的精度设置问题,精度不够 2,就是runloop的周期问题,本来是固定按一个时间周期运行的,如果当前你的runloop占用周期过长,就会导致后面的不准了;如果要高精度的timer,那就用dispatch_source_timer,然后设置它的高精度就行;

===子线程的runloop默认不会创建,获取的时候才会创建,获取的时候它会先判断,有了直接返回,没有就创建;我们不能通过runloop new这样去新建一个runloop;[cfrunloopgetcurrent] 这样去获取runloop;

===runloop也是会退出的哦,如果是主线程,它是会自动保活,不用判断sources,不退出的;如果是其他的线程,它就会判断你的source和timer是否为空,如果是空,就退出,如果非空,就保活,所以想对线程保活就要提供source或者timer;

===runloop创建的时候会初始化2个东西,一个是wake up port,就是上面我们说的用runloopwakeup来唤醒runloop的;另一个是timer port就是mk_timer,我们平常的那个timer来唤醒runloop;

===MVP:面向协议编程

===探究oc的底层就是clang或者xcrun成c++代码,就可以看到oc的底层了,把文件编译成c++文件就可以了,同时也要去开源文档那里看相应的底层实现;

===weak-strong-dance:block外面是weakself,里面要再用一个strongself再来使用

===@autoreleasepool是什么?底层原理是什么?

1,通过clang来玩,编译成c++文件,我们可以看到autoreleasepool的底层也是一个结构体,然后它会调用push,pop这两个方法,也就是压栈和出栈;

2,通过汇编,就是在main函数的autoreleasepool这里打断点,然后看它的调用栈,你会发现在callq这里也是调了两个方法,push和pop,也就是压栈和出栈;

===objc_autoreleasepoolpush这个方法底层具体实现,这个时候我们就必须要通过源码来查看它的实质,那我们怎么看它的源码呢,怎么知道它在哪个库呢?我们可以通过汇编调用,像上面那样,在autoreleasepool这里打断点,然后继续点进去,发现进不去,这个时候我们应该打一个符号断点,那这个符号断点打在哪里呢,就是打在objc_autoreleasepoolpush这个方法这里,然后再运行,这个时候你就会发现它断在这个方法这里,然后你看它前面,就能看到它的源码时在哪个库文件中了,比如这个我们就看到是在libobjc.a.dylib这个动态库里哦,所以这个时候你就应该去Apple的opensource网站那里下载一份源码,就是下载objc的底层源码。

===autoreleasepool通过源码我们可以看到它的底层是一个栈帧结构,先进后出,然后绑定在一个线程上,然后那个栈还分为了双向链表,因为内存分页啊,然后上一页和下一页就是有互相指向的指针,就是双向链表。反正就是内存分页,双向链表,栈结构;在autoreleasepool的结构中,它自己的属性占56字节,然后剩下的就是添加进来待释放的对象,然后这些待释放的对象是加到分页的内存里的,一页满了再开辟新的一页,然后就是页与页之间就是双链表的形式,每一页就是一个栈结构的方式,压栈,出栈这样的。每一页的大小=56+一个哨兵对象8+504个对象*8=4096个字节,所以就是4k,哈哈;

===oc的类中,因为类是结构体,所以我们要知道,在类中只有属性这种东西占内存,方法和其他静态变量不占结构体的内存,它们存储在其他的地方,不在类的内存结构中

===KVO 响应式编程的方案,其实像代理,通知,target-action这些都属于响应式编程,block本身只是响应式编程的一种表现形式,你只有把block拷贝起来,用的时候再触发,这样才属于响应式编程

===RAC--更优的响应式编程的方案,1,创建信号 2,订阅信号 3,发出信号

===crash分析:数组越界,野指针,坏内存,找不到方法,线上的时候就要三方库监控,比如友盟,buggly等的;

===runtime方法交换的时候是放在load方法里的,为什么呢?1,load方法使系统自动调用的,不用我们自己手动调用 2,load方法的调用时机是比较早的

===crash的时候会崩溃,我们不想让程序崩溃,怎么办呢?用runtime啊,写一个分类方法,通过交换方法实现,然后让程序再遇到崩溃的时候调用自己定义的方法,然后我们在自己定义的方法里面去做相应的处理就好了啊,比如数组越界,我们在数组越界的那里崩溃方法换个实现,然后交换方法实现后,我们在自己定义的方法给出提示,不要崩溃就好了啊

===异常捕捉:保存上传服务器,这个系统自己就有了关于异常捕捉的,自己去研究一下

===异常捕捉后的起死回生:其实之所以有异常了会崩溃是因为runloop里面事务的异常,事务的崩溃,所以我们可以通过拿到runloop,然后拿到里面所有的modes,然后我们可以通过遍历,强制runloop进行循环,不崩溃,就能让程序遇到bug要崩溃的时候不崩溃。

===怎么合理的退出我们创建的常驻子线程?1,创建一个标识,开始时yes,然后线程运行完了变成false;2,并且runloop run这个方法不要用,要用runloop for mode这样的方法去启动runloop,然后在线程运行结束后,再调用runloop stop这样的方法,因为标识变成false后,就只剩当前runloop了,如果调用了当前runloop stop方法,就可以合理的退出常驻的子线程runloop了

===重签名:就是签名我们的可执行文件(主程序,动态库),我们可以新建一个空工程,然后通过重签名,让工程执行微信的ipa包,然后通过代码注入,运行时方法交换,然后在微信的程序可以调用我们自己的代码,获取微信登录的密码。

===注入代码:添加framework,修改mach-o写入字段

===应用签名:为了防止恶意程序或者来源不明的程序运行在iOS设备当中

===codesign是一个给应用签名的工具

===SRCROOT就是当前项目的根目录,跟./一样的,都是根目录

===重签后,bundle id 肯定是改了的,所以可以通过判断这个看应用是否重签了

===我们在block里面写的代码在底层是保存在一个函数里面,这个函数在结构体里就叫funcptr(函数指针)

你可能感兴趣的:(iOS开发底层进阶视频笔记)