美文网首页
2020-09-07

2020-09-07

作者: 汪配韦 | 来源:发表于2020-09-07 19:37 被阅读0次

1. ARC帮我们做了什么?

使用LVVM + Runtime 结合帮我管理对象的生命周期

LVVM 帮我们在代码合适的地方添加release、retarn、autorelease等添加计数器或者减少计数器操作

Runtime 帮我们像__weak、copy等关键字的操作

2.initialize和load是如何调用的?它们会多次调用吗?

load方法说在应用加载的时候,Runtime直接拿到load的IMP直接去调用的,而不是像其他方式根据objc_msgSend(消息机制)来调用方法的

load方法一个类只会调用一次(除去手动调用),而调用的数序是,从superclass -> class -> category,category里面的顺序是先编译,先调用

initialize方法,一个类可能会调用多次,如果子类没有实现initialize方法,当第一次使用此类的时候,会调用superclass。而调用的顺序是,superclass -> 实现initialize的category 或者 实现了initialize方法(没有category实现initialize) 或者 superclass的initialize (没有子类和category实现initialize方法)

initialize方法的调用其实和其他方法调用一样的,objc_msgSend(消息机制)来调用的。调用的数序是:没有初始话的superclass -> 实现initialize的categort 或者 实现了initialize的class,如果class没有实现initialize 方法,则会调用superclass的initialize,因为initialize的底层是使用了objc_msgSend

看下Runtime底层调用_class_initialize的源码

load方法调用的顺序是根据类的加载的前后进行调用的,但是每个类调用的顺序是superclass->class->category顺序调用的,每个load方法只会调用一次(手动调用不算)

一下为Runtime源码的主要代码

3.category属性是存储在那里?

我们都知道可以使用Runtime的objc_setAssociatedObject、objc_getAssociatedObject两个方法给category的属性重写get、set方法,而此属性的值是存储在那里呢?

其实此属性的值保存在一个AssociationsManager里面。

4. OC 的消息机制

消息机制可以分为三个部分

如果我们没有实现动态解析方法,就会走到消息转发这里

第一步,会调用-(id)forwardingTargetForSelector:(SEL)aSelector方法,我们可以在这里,返回一个响应aSelector的对象。当返回不为nil时候,系统会继续再次走消息转发,继续查找对应的IMP

第二步,如果第一步返回nil或者self(自己),此时系统会继续走这里-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector,需要返回aSelector的一个签名

第三步,如果返回了签名,就会到这里-(void)forwardInvocation:(NSInvocation *)anInvocation,相应的我们可以根据anInvocation,可以获取到参数、target、方法名等,再次操作的空间就很多了,看你需求喽。此时我们什么都不操作也是没问题的,

注意:当我们是类方法的时候,其实我们可以将以上方法的-改为+,即可实现了类方法的转发

当消息传递,没有找到对应的IMP的时候,会进入的动态解析中

此时会根据方法是类方法,还是实例方法分别调用+(BOOL)resolveClassMethod:(SEL)sel、+(BOOL)resolveInstanceMethod:(SEL)sel

我们可以实现这两个方法,使用Runtime的class_addMethod来添加对应的IMP

如果添加后,返回true,没有添加则调用父类方法

注意:其实返回true或者false,结果都是一样的,再次掉消息传递步骤

当我么调用方法的时候,方法的调用都会转化为objc_msgSend这样来传递。

第一步会根据对象的isa指针找到所属的类(也就是类对象)

第二步,会根据类对象里面的catch里面查找。catch是个散列表,是根据@selector(方法名)来获取对应的IMP,从而开始调用

第三步,如果第二步没有找到,会继续查找到类对象里面的class_rw_t里面的methods(方法列表),从而遍历,找到方法所属的IMP,如果查找到则会添加到catch表里面

第四步,如果第三部也没有找到,会根据类对象里面的superclass指针,查找super的catch,如果也是没有查找,会继续查找到superclass里面的class_rw_t里面的methods(方法列表),从而遍历,找到方法所属的IMP,如果查找到则会添加到catch表里面

第五步,如果第四部还是没有查找到,此时会根据类的superclass,继续第四部操作

…………

第六步。如果一直查找到基类都没有找到响应的方法,则会进入动态解析里面

5.weak表是如何存储__weak指针的

weak关键字,我们都知道,当对象销毁的时候,也会将指针赋值为nil,而weak的底层也是将指针和对象以键值对的形式存储在哈希表里面

当使用__weak修饰的时候,底层会调用id objc_storeWeak(id *location, id newObj)传递两个参数

第一个参数为指针,第二个参数为所指向的对象

第二步,继续调用storeWeak(location, (objc_object *)newObj)

第一个参数是指针,第二个参数是对象的地址

再次方法里面会根据对象地址生成一个SideTables对象

第三步,调用id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating)

weak_table则为SideTables的一个属性,referent_id为对象,referrer_id则为那个弱引用的指针

在此里面会根据对象地址和指针生成一个weak_entry_t

第四步,会继续调用static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)

重点:在此方法里面会根据对象&weak_table->mask(表示weak表里面可以存储的大小减一,例如:表可以存储10个对象,那么mask就是9), 生成对应的index,如果index对应已经存储上对象,则会index++的方式找到未存储的对应,并将new_entry存储进去,储存在weak_table里的weak_entries属性里面

注意:当一个对象多个weak指针指向的时候,生成的也是一个entry,多个指针时保存在entry里面referrers属性里面

6.优化后isa指针是什么样的?存储都有哪些内容?

最新的Objective-C的对象里面的isa指针已经不是单单的指向所属类的地址了的指针了,而时变成了一个共用体,并且使用位域来存储更多的信息

7.App启动流程,以及如何优化?

启动顺序

所有初始化工作结束后,dyld就会调用main函数

截下来就是UIApplicationMan函数,AppDelegate的application:didFinishLaunchingWithOptions:的

调用map_images进行可执行文件内容的解析和处理

在load_images里面调用call_load_methods,调用所有class和category的+load方法

进行各种objc结构的初始化(注册Objc类,初始化类对象等等)

到目前未知,可执行文件和动态库中所有的符号(Class,Protocol,Selector,IMP…)都已经按照格式成功加载到内存中,被runtime管理

装载App的可执行文件,同事递归加载所有依赖的动态库

当dyld把可执行文件、动态库装载完毕后,会通知Runtime进行下一步的处理

dyld,Apple的动态连接器,可以用来装载Mach-O文件(可执行文件、动态库)

Runtime

main函数调用

App启动速度优化

DYLD_PRINT_STATISTICS设置为1,可以打印出来每个阶段的时间

如果需要更详细的信息,那就设置DYLD_PRINT_STATISTICS_DETAILS为1

再不印象用户体验的情况下面,尽可能的将一些操作延迟,不要全部放到finishLaunching

以及window的rootViewController 的viewDidload方法,也别做耗时操作

一些网络请求

一些第三方的注册

使用+initialize和dispatch_once取代Objc的+load方法、C++的静态构造器

减少动态库,合并一些自定义的动态库,以及定期清理一些不需要的动态库

较少Objc类、category的数量、以及定期清理一些不必要的类和分类

Swift尽量使用struct

dyld

Runtime

main

注意:我们可以添加环境变量可以打印出App的启动时间分析(Edit scheme -> Run -> Arguments)

8.App瘦身

资源(图片、音频、视频等)

可以采取无损压缩

使用LSUnusedResources去除没有用的资源 LSUnusedResources

可执行文件瘦身

Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为true

去掉一些异常支持 Enable C++ Exceptions、Enable Objective-C Exceptions设置为false

使用AppCode检测未使用的代码:菜单栏 -> Code -> Inspect Code,等编译完成后,会看到未使用的类

生成LinkMap文件,可以查看可执行文件的具体组成

可借助第三方工具解析LinkMap文件LinkMap

9、说一下OC的反射机制

在动态运行下我们可以构建任何一个类,然后我们通过这个类知道这个类的所有的属性和方法,并且如果我们创建一个对象,我们也可以通过对象找到这个类的任意一个方法,这就是反射机制。

比如NSClassFormString,NSStringFormSelector,NSSelectorFormString

参考链接

10、block的本质是什么?有几种block?分别是怎样产生的?

参考链接

block与函数类似,只不过是直接定义在另一个函数里,和定义它的那个函数共享同一个范围内的东西,

block的强大之处是:在声明它的范围里,所有变量都可以为其捕获,这也就是说,那个范围内的全部变量,在block依然可以用,默认情况下,为block捕获的变量,是不可以在block里修改的,不过声明的时候可以加上__block修饰符,这样就可以再block内修改了。

block本身和其他对象一样,有引用计数,当最后一个指向block的引用移走之后,block就回收了,回收时也释放block所捕获的变量。

Block的实现是通过结构体的方式实现,在编译的过程中,将Block生成对应的结构体,在结构体中记录Block的匿名函数,以及使用到的自动变量,在最后的使用中,通过Block结构体实例访问成员中存放的匿名函数地址调用匿名函数,并将自身作为参数传递。

block其实就是C语言的扩充功能,实现了对C的闭包实现,一个带有局部变量的匿名函数,

block的本质也是一个OC对象,它内部也有一个isa指针,block是封装了函数调用以及函数调用环境的OC对象,为了保证block内部能够正常访问外部的变量,block有一个变量捕获机制。static 修饰的变量为指针传递,同样会被block捕获。局部变量因为跨函数访问所以需要捕获,全局变量在哪里都可以访问 ,所以不用捕获。

当block内部访问了对象类型的auto变量时,如果block在栈上,block内部不会对变量产生强应用,不论block的结构体内部的变量时__strong修饰还是__weak修饰,都不会对变量产生强引用

默认情况下block不能修改外部的局部变量

1.static修饰

static修饰的age变量传递到block内部的是指针,在__main_block_func_0函数内部就可以拿到age变量的内存地址,因此就可以在block内部修改age的值。

有三种类型

__NSGlobalBlock__ ( _NSConcreteGlobalBlock )__NSStackBlock__ ( _NSConcreteStackBlock )__NSMallocBlock__ ( _NSConcreteMallocBlock )

__block内存管理

当block内存在栈上时,并不会对__block变量产生内存管理,当block被copy到堆上时会调用block内部的copy函数,copy函数内部会滴啊用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(相当于retain)。

当block被copy到堆上时,block内部引用的__block变量也会被复制到堆上,并且持有变量,如果block复制到堆上的同时,__block变量已经存在堆上了,则不会复制。

当block从堆中移除的话,就会调用dispose函数,也就是__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose函数,会自动释放引用的__block变量。

解决循环引用问题

使用__weak和__unsafe_unretained修饰符合一解决循环引用的问题,__weak会使block内部将指针变为弱指针。

__weak 和 __unsafe_unretained的区别。

__weak不会产生强引用,指向的对象销毁时,会自动将指针置为nil

__unsafe_unretained不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变

__strong 和 __weak

在block内部重新使用__strong修饰self变量是为了在block内部有一个强指针指向weakSelf避免在block调用的时候weakSelf已经被销毁。

2.__block修饰的变量为什么能在block里面能改变其值?

__block用于解决block内部不能修改auto变量值的问题,__block不能修饰静态变量和全局变量

_block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。

11.APP生命周期

要知道APP的生命周期,首先要了解一下生命周期的5种状态,结合状态理解生命周期的使用。

应用的状态包括:

未运行(Not running)

程序没启动

未激活(Inactive)

程序在前台运行,不过没有接收到事件。

一般每当应用要从一个状态切换到另一个不同的状态时,中途过渡会短暂停留在此状态。唯一在此状态停留时间比较长的情况是:当用户锁屏时,或者系统提示用户去响应某些(诸如电话来电、有未读短信等)事件的时候。

激活(Active)

程序在前台运行而且接收到了事件。这也是前台的一个正常的模式

后台(Backgroud)

程序在后台而且能执行代码,大多数程序进入这个状态后会在在这个状态上停留一会。时间到之后会进入挂起状态(Suspended)。有的程序经过特殊的请求后可以长期处于Backgroud状态

挂起(Suspended)

程序在后台不能执行代码。系统会自动把程序变成这个状态而且不会发出通知。当挂起时,程序还是停留在内存中的,当系统内存低时,系统就把挂起的程序清除掉,为前台程序提供更多的内存。

APP的生命周期就是UIApplicationDelegate中的回调方法,这些方法是根据状态变化进行响应的地方,其中最常用的就是以下7个方法:

application:willFinishLaunchingWithOptions:

在App启动时调用表示应用加载进程已经开始,常用来处理应用状态的存储和恢复

application:didFinishLaunchingWithOptions:

表示App将从未运行状态进入运行状态,用于对App的初始化操作

applicationDidBecomeActive:

当应用即将进入前台运行时调用

applicationWillResignActive:

当应用即将进从前台退出时调用

applicationDidEnterBackground:

当应用开始在后台运行的时候调用

applicationWillEnterForeground:

当程序从后台将要重新回到前台(但是还没变成Active状态)时候调用

applicationWillTerminate:

当前应用即将被终止,在终止前调用的函数。通常是用来保存数据和一些退出前的清理工作。如果应用当前处在suspended,此方法不会被调用。 该方法最长运行时限为5秒,过期应用即被kill掉并且移除内存。

UIViewController的生命周期

当一个视图控制器被创建,并在屏幕上显示的时候。 代码的执行顺序

1、 alloc 

创建对象,分配空间

2、init (initWithNibName|initWithCoder)

初始化对象,初始化数据

3、awakeFromNib

所有视图的outlet和action已经连接,但还没有被确定。

4、loadView 

完成一些关键view的初始化工作,加载view。

5、viewDidLoad 

载入完成,可以进行自定义数据以及动态创建其他控件

6、viewWillAppear 

视图将出现在屏幕之前

7、viewWillLayoutSubviews

将要对子视图进行调整

8、viewDidLayoutSubviews

对子视图进行调整完毕

9、viewDidAppear 

视图已在屏幕上渲染完成

10、viewWillDisappear 

视图将被从屏幕上移除

11、viewDidDisappear 

视图已经被从屏幕上移除

12、dealloc 

视图被销毁,此处需要对你在init和viewDidLoad中创建的对象进行释放

13、didReceiveMemoryWarning

12、谈下iOS开发中知道的哪些锁? 哪个性能最差?SD和AFN使用的哪个? 一般开发中你最常用哪个? 哪个锁apple存在问题又是什么问题?

我们在使用多线程的时候多个线程可能会访问同一块资源,这样就很容易引发数据错乱和数据安全等问题。这时候我们需要保证每次只有一个线程访问这一块资源,锁应运而生。

@synchronized

NSLock对象锁

NSRecursiveLock 递归锁

NSCondition

NSConditionLock 条件锁

pthread_mutex 互斥锁(C语言)

dispatch_semaphore 信号量实现加锁(GCD)

OSSpinLock

1)、@synchronized关键字加锁 互斥锁,性能较差不推荐使用

2)、NSLock互斥锁 不能多次调用lock方法,会造成死锁

3)、NSRecursiveLock 递归锁

4)、NSConditionLock条件锁

5)、NSCondition

6)、pthread_mutex互斥锁

7)、dispatch_semaphore信号量实现加锁

8)、OSSpinLock(不建议使用)

AFN 3.1 .0 使用NSlock 、@synchronized和 dispatch_semaphore_t信号量实现加锁(老版本有使用过NSRecursiveLock) SDWebImage 使用@synchronized

13. Runtime

Runtime 实际上是一个库,这个库使我们可以在程序运行时动态的创建对象、检查对象,修改类和对象的方法。

2. 消息机制的基本原理

Objective-C 语言 中,对象方法调用都是类似 [receiver selector]; 的形式,其本质就是让对象在运行时发送消息的过程。

我们来看看方法调用 [receiver selector]; 在『编译阶段』和『运行阶段』分别做了什么?

编译阶段:[receiver selector]; 方法被编译器转换为:

objc_msgSend(receiver,selector) (不带参数)

objc_msgSend(recevier,selector,org1,org2,…)(带参数)

运行时阶段:消息接受者 recevier 寻找对应的 selector。

通过 recevier 的 isa 指针 找到 recevier 的 Class(类);

在 Class(类) 的 cache(方法缓存) 的散列表中寻找对应的 IMP(方法实现);

如果在 cache(方法缓存) 中没有找到对应的 IMP(方法实现) 的话,就继续在 Class(类) 的 method list(方法列表) 中找对应的 selector,如果找到,填充到 cache(方法缓存) 中,并返回 selector;

如果在 Class(类) 中没有找到这个 selector,就继续在它的 superClass(父类)中寻找;

一旦找到对应的 selector,直接执行 recevier 对应 selector 方法实现的 IMP(方法实现)。

若找不到对应的 selector,消息被转发或者临时向 recevier 添加这个 selector 对应的实现方法,否则就会发生崩溃。

在上述过程中涉及了好几个新的概念:objc_msgSend、isa 指针、Class(类)、IMP(方法实现) 等,下面我们来具体讲解一下各个概念的含义。

消息发送以及转发机制总结

调用 [receiver selector]; 后,进行的流程:

编译阶段:[receiver selector]; 方法被编译器转换为:

objc_msgSend(receiver,selector) (不带参数)

objc_msgSend(recevier,selector,org1,org2,…)(带参数)

运行时阶段:消息接受者 recevier 寻找对应的 selector。

通过 recevier 的 isa 指针 找到 recevier 的 class(类);

在 Class(类) 的 cache(方法缓存) 的散列表中寻找对应的 IMP(方法实现);

如果在 cache(方法缓存) 中没有找到对应的 IMP(方法实现) 的话,就继续在 Class(类) 的 method list(方法列表) 中找对应的 selector,如果找到,填充到 cache(方法缓存) 中,并返回 selector;

如果在 class(类) 中没有找到这个 selector,就继续在它的 superclass(父类)中寻找;

一旦找到对应的 selector,直接执行 recevier 对应 selector 方法实现的 IMP(方法实现)。

若找不到对应的 selector,Runtime 系统进入消息转发机制。

运行时消息转发阶段:

动态解析:通过重写 +resolveInstanceMethod: 或者 +resolveClassMethod:方法,利用 class_addMethod方法添加其他函数实现;

消息接受者重定向:如果上一步添加其他函数实现,可在当前对象中利用 forwardingTargetForSelector: 方法将消息的接受者转发给其他对象;

消息重定向:如果上一步没有返回值为 nil,则利用 methodSignatureForSelector:方法获取函数的参数和返回值类型。

如果 methodSignatureForSelector: 返回了一个 NSMethodSignature 对象(函数签名),Runtime 系统就会创建一个 NSInvocation 对象,并通过 forwardInvocation: 消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。

如果 methodSignatureForSelector: 返回 nil。则 Runtime 系统会发出 doesNotRecognizeSelector: 消息,程序也就崩溃了。

相关文章

网友评论

      本文标题:2020-09-07

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