Runtime Runtime Runtime
objc在向一个对象发送消息时,发生了什么?
根据对象的isa
指针找到类对象id
,在查询类对象里面的methodLists
方法函数列表,如果没有在找到,在沿着superClass
,寻找父类,再在父类methodLists
方法列表里面查询,最终找到SEL
,根据id
和SEL
确认IMP
(指针函数),在发送消息。
什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?
当发送消息的时候,我们会根据类里面的methodLists
列表去查询我们要动用的SEL
,当查询不到的时候,我们会一直沿着父类查询,当最终查询不到的时候我们会报unrecognized selector
错误
当系统查询不到方法的时候,会调用+(BOOL)resolveInstanceMethod:(SEL)sel
动态解释的方法来给我一次机会来添加,调用不到的方法。或者我们可以再次使用-(id)forwardingTargetForSelector:(SEL)aSelector
重定向的方法来告诉系统,该调用什么方法,一来保证不会崩溃。
能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
1.不能向编译后得到的类增加实例变量
2.能向运行时创建的类中添加实例变量
解释:
1.编译后的类已经注册在runtime
中,类结构体中的objc_ivar_list
实例变量的链表和instance_size
实例变量的内存大小已经确定,
runtime
会调用class_setvarlayout
或class_setWeaklvarLayout
来处理strong weak引用.所以不能向存在的类中添加实例变量
2.运行时创建的类是可以添加实例变量,调用class_addIvar
函数.但是的在调用objc_allocateClassPair
之后,objc_registerClassPair
之前,原因同上。
runtime如何实现weak变量的自动置nil?
runtime
对注册的类, 会进行布局,对于 weak
对象会放入一个 hash
表中。 用 weak
指向的对象内存地址作为 key
,当此对象的引用计数为0的时候会 dealloc
,假如 weak
指向的对象内存地址是a
,那么就会以a
为键, 在这个 weak
表中搜索,找到所有以a
为键的 weak
对象,从而设置为 nil
。
给类添加一个属性后,在类结构体里哪些元素会发生变化?
instance_size
:实例的内存大小
objc_ivar_list *ivars
:属性列表
RunLoop RunLoop RunLoop
runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?
runloop
:字面意思就是跑圈,其实也就是一个循环跑圈,用来处理线程里面的事件和消息。
runloop
和线程的关系:每个线程如果想继续运行,不被释放,就必须有一个runloop来不停的跑圈,以来处理线程里面的各个事件和消息。
主线程默认是开启一个runloop
。也就是这个runloop
才能保证我们程序正常的运行。子线程是默认没有开始runloop
的。
runloop的mode是用来做什么的?有几种mode?
model
:是runloop
里面的模式,不同的模式下的runloop
处理的事件和消息有一定的差别。
系统默认注册了5个Mode
:
(1)kCFRunLoopDefaultMode
: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
(2)UITrackingRunLoopMode
: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
(3)UIInitializationRunLoopMode
: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
(4)GSEventReceiveRunLoopMode
: 接受系统事件的内部 Mode,通常用不到。
(5)kCFRunLoopCommonModes
: 这是一个占位的 Mode,没有实际作用。
注意iOS 对以上5中model进行了封装
NSDefaultRunLoopMode;
NSRunLoopCommonModes;
为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?
nstime
对象是在 NSDefaultRunLoopMode
下面调用消息的,但是当我们滑动scrollview
的时候,NSDefaultRunLoopMode
模式就自动切换到UITrackingRunLoopMode
模式下面,却不可以继续响应nstime
发送的消息。所以如果想在滑动scrollview
的情况下面还调用nstime
的消息,我们可以把nsrunloop
的模式更改为NSRunLoopCommonModes
。
进程和线程的区别?同步异步的区别?并行和并发的区别?
进程:
是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程:
是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
同步:
阻塞当前线程操作,不能开辟线程。
异步:
不阻碍线程继续操作,可以开辟线程来执行任务。
并发:
当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。
并行:
当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
区别:
并发和并行是即相似又有区别的两个概念,
并行是指两个或者多个事件在同一时刻发生;
而并发是指两个或多个事件在同一时间间隔内发生。
线程间通信?
当使用dispath-async
函数开辟线程执行任务的完成时,
我们需要使用dispatch_async(dispatch_get_main_queue(), ^{ });
函数会到主线程内刷新UI。并完成通信
GCD的一些常用的函数?(group,barrier,信号量,线程同步)
我们使用队列组来开辟线程时,队列组中的队列任务是并发,当所有的队列组中的所有任务完成时候,才可以调用队列组完成任务。
/**创建自己的队列*/
dispatch_queue_t dispatchQueue = dispatch_queue_create("ted.queue.next", DISPATCH_QUEUE_CONCURRENT);
/**创建一个队列组*/
dispatch_group_t dispatchGroup = dispatch_group_create();
/**将队列任务添加到队列组中*/
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSLog(@"dispatch-1");
});
/**将队列任务添加到队列组中*/
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSLog(@"dspatch-2");
});
/**队列组完成调用函数*/
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
NSLog(@"end");
})
SDWebImage的缓存策略
sd加载一张图片的时候,会先在内存里面查找是否有这张图片,如果没有会根据图片的md5(url)后的名称去沙盒里面去寻找,是否有这张图片,如果没有会开辟线程去下载,下载完毕后加载到imageview上面,并md(url)为名称缓存到沙盒里面。
AFN为什么添加一条常驻线程?
AFN 目的:就是开辟线程请求网络数据。如果没有常住线程的话,就会每次请求网络就去开辟线程,完成之后销毁开辟线程,这样就造成资源的浪费,开辟一条常住线程,就可以避免这种浪费,我们可以在每次的网络请求都添加到这条线程。
KVO的使用?实现原理?(为什么要创建子类来实现)
kvo:键值观察,根据键对应的值的变化,来调用方法。
注册观察者:addObserver:forKeyPath:options:context:
实现观察者:observeValueForKeyPath:ofObject:change:context:
移除观察者:removeObserver:forKeyPath:
(对象销毁,必须移除观察者)
注意
使用kvo监听A对象的时候,监听的本质不是这个A对象,而是系统创建的一个中间对象NSKVONotifying_A并继承A对象,并且A对象的isa指针指向的也不是A的类,而是这个NSKVONotifying_A对象。
KVC的使用?实现原理?(KVC拿到key以后,是如何赋值的?知不知道集合操作符,能不能访问私有属性,能不能直接访问_ivar)
kvc:键值赋值,使用最多的即使字典转模型。利用runtime
获取对象的所有成员变量, 在根据kvc键值赋值,进行字典转模型
setValue: forKey:
只查找本类里面的属性
setValue: forKeyPath:
会查找本类里面属性,没有会继续查找父类里面属性。
讲一下MVC和MVVM,MVP?
MVC:简单来说就是,逻辑、试图、数据进行分层,实现解耦。
MVVM:是Model-View-ViewMode
模式的简称。由视图(View)、视图模型(ViewModel)、模型(Model)三部分组成.比MVC更加释放控制器臃肿,将一部分逻辑(耗时,公共方法,网络请求等)和数据的处理等操作从控制器里面搬运到ViewModel
中。
MVVM的特点:
- 低耦合。View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
- 可重用性。可以把一些视图的逻辑放在ViewModel里面,让很多View重用这段视图逻辑。
- 独立开发。开发人员可以专注与业务逻辑和数据的开发(ViewModel)。设计人员可以专注于界面(View)的设计。
- 可测试性。可以针对ViewModel来对界面(View)进行测试
为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?
代理是使用weak来修饰的。1.使用weak是为了避免循环引用。2.当使用weak修饰的属性,当对象释放的时候,系统会对属性赋值nil,object-c有个特性就是对nil对象发送消息也就是调用方法,不会cash。
delegate:传递的是事件(even),代理可以让A对象通知B对象,我(A)发生的变化,前提B遵循了A的代理,并且实现了A的代理方法。
dataSource: 传递的是数据。如果A对象声明了数据源,当我们创建A对象的时候,我们就该实现数据源,来告诉A,他所需要的一些数据。例如:tableView数据源方法,需要告诉它,我要实现几组cell,每组cell多少行cell,实现的cell什么样式,什么内容
同样delegate和 dataSource,都是可以使用require和optional来修饰的。
代理和Block的区别
相同点:
代理和Block大多是我们都可以用来做倒序传值的。我们都得注意避免循环引用。不然我们去使用代理还是Block的时候,都需要判断它们是否实现
不同点:
代理使用weak修饰,代理必须先声明方法。当我们调用代理的时候要判断是否已经实现。
block:
使用的是copy来修饰,block保存的是一段代码,其实也就是一个函数。并且可以自动捕捉自动变量,如果想修改此自动变量,还必须使用__block修饰。
属性的默认关键字是什么?
默认关键字,基本数据: atomic,readwrite,assign
普通的 OC 对象: atomic,readwrite,strong
nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?
nonatomic: 表示非原子,不安全,但是效率高。
atomic: 表示原子行,安全,但是效率低。
atomic: 不能绝对保证线程的安全,当多线程同时访问的时候,会造成线程不安全。可以使用线程锁来保证线程的安全。
block的实质是什么?一共有几种block?都是什么情况下生成的?
block:本质就是一个object-c对象.
block:存储位置,可能分为3个地方:代码去,堆区、栈区(ARC情况下会自动拷贝到堆区,因此ARC下只能有两个地方:代码去、堆区)
代码区:不访问栈区的变量(如局部变量),且不访问堆区的变量(alloc创建的对象),此时block存放在代码去。
堆区:访问了处于栈区的变量,或者堆区的变量,此时block存放在堆区。–需要注意实际是放在栈区,在ARC情况下会自动拷贝到堆区,如果不是ARC则存放在栈区,所在函数执行完毕就回释放,想再外面调用需要用copy指向它,这样就拷贝到了堆区,strong属性不会拷贝、会造成野指针错区。
为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?
默认情况下,block
里面的变量,拷贝进去的是变量的值,而不是指向变量的内存的指针。
当使用__block
修饰后的变量,拷贝到block里面的就是指向变量的指针,所以我们就可以修改变量的值。
模拟一下循环引用的一个情况?block实现界面反向传值如何实现?
Person *p = [[Person alloc]init];
[p setPersonBlock:^(NSString *str) {
p.name = str;
}];
类方法和实例方法有什么区别?
调用的方式不同,类方法必须使用类调用,在方法里面不能调用属性,类方法里面也必须调用类方法。存储在元类结构体里面的methodLists
里面
实例方法必须使用实例对象调用,可以在实例方法里面使用属性,实例方法里面也必须调用实例方法。存储在类结构体里面的methodLists
里面
运行时能增加成员变量么?能增加属性么?如果能,如何增加?如果不能,为什么?
可以添加属性的,但必须我们实现它的getter
和setter
方法。但是没有添加带下滑线同名的成员变量
但是我们使用runtime
我们就可以实现添加成员变量方法如下:
- (void)setName:(NSString *)name {
/**
* 为某个类关联某个对象
*
* @param object#> 要关联的对象 description#>
* @param key#> 要关联的属性key description#>
* @param value#> 你要关联的属性 description#>
* @param policy#> 添加的成员变量的修饰符 description#>
*/
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
/**
* 获取到某个类的某个关联对象
*
* @param object#> 关联的对象 description#>
* @param key#> 属性的key值 description#>
*/
return objc_getAssociatedObject(self, @selector(name));
}
objc中向一个nil对象发送消息将会发生什么?(返回值是对象,是标量,结构体)
• 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如:Person * motherInlaw = [ aPerson spouse] mother];
如果spouse
对象为nil
,那么发送给nil
的消息mother
也将返回nil
。
• 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double
或者long long
的整型标量,发送给nil
的消息将返回0。
• 如果方法返回值为结构体,正如在《Mac OS X ABI 函数调用指南》,发送给nil
的消息将返回0。结构体中各个字段的值将都是0。其他的结构体数据类型将不是用0填充的。
• 如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的。
有没有用过运行时,用它都能做什么?(交换方法,创建类,给新创建的类增加方法,改变isa指针)
交换方式:一般写在类的+(void)load方法里面
/** 获取原始setBackgroundColor方法 */
Method originalM = class_getInstanceMethod([self class], @selector(setBackgroundColor:));
/** 获取自定义的pb_setBackgroundColor方法 */
Method exchangeM = class_getInstanceMethod([self class], @selector(pb_setBackgroundColor:));
/** 交换方法 */
method_exchangeImplementations(originalM, exchangeM);
创建类:
Class MyClass = objc_allocateClassPair([NSObject class], "Person", 0);
添加方法
/**
参数一、类名参数
二、SEL 添加的方法名字参数
三、IMP指针 (IMP就是Implementation的缩写,它是指向一个方法实现的指针,每一个方法都有一个对应的IMP)
参数四、其中types参数为"i@:@“,按顺序分别表示:具体类型可参照[官方文档]
(https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html) 返回值类型int,若是v则表示void@ 参数id(self): SEL(_cmd)@ id(str)
V@:表示返回值是void 带有SEL参数 (An object (whether statically typed or typed id))
*/
class_addMethod(Person, @selector(addMethodForMyClass:), (IMP)addMethodForMyClass, "V@:");
添加实例变量
/**参数
一、类名参数
二、属性名称参数
三、开辟字节长度参数
四、对其方式参数
五、参数类型 “@” 官方解释 An object (whether statically typed or typed id) (对象 静态类型或者id类型) 具体类型可参照[官方文档](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)return: BOOL 是否添加成功
*/
BOOL isSuccess = class_addIvar(Person, "name", sizeof(NSString *), 0, "@");
isSuccess?NSLog(@"添加变量成功"):NSLog(@"添加变量失败");