主题一《OC对象的本质》===========
1、OC的本质
Objective-C ----> C\C++ ----> 汇编语言 ---->机器语言
Objective-C的面向对象都是基于C\C++的结构体数据结构实现的
2、OC对象的分类
三种分类、三种分类中分别存储的信息
3、ISA指针、SuperClass指针
4、实例对象结构体、类对象结构体
主题二《KVO、KVC》===========
1、iOS 用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
1)、利用RuntimeAPI动态生成一个子类(命名eg:NSKVONotifying_MJPerson),并且让instance对象的isa指针指向这个全新的子类;
2)、当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数:
(1)willChangeValueForKey:
(2)父类原来的setter
(3)didChangeValueForKey:(内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:))
2、如何手动触发KVO?
手动调用willChangeValueForKey:和didChangeValueForKey:
3、直接修改成员变量会触发KVO么?
不会触发KVO
4、KVC的赋值和取值过程是怎样的?原理是什么?
如setValue:forKey:原理图所示;
如valueForKey:原理图所示;
5、通过KVC修改属性会触发KVO么?
会触发KVO
原因:
[person setValue:@10 forKey:@“age”];
系统内部实现等同于
[person willChangeValueForKey:@“age”];
person -> _age = 10;
[person didChangeValueForKey:@“age”];
主题三《Category、关联对象》===========
1、Category的实现原理(Category底层结构及Category的加载处理过程)?
2、Category和Class Extension的区别?
3、load、initialize方法的区别(调用方式、调用时机、顺序)?
1.调用方式
1> load是根据函数地址直接调用
2> initialize是通过objc_msgSend调用
2.调用时刻
1> load是runtime加载类、分类的时候调用(只会调用1次)
2> initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
3.load、initialize的调用顺序?
(1).load
1> 先调用类的load
a) 先编译的类,优先调用load
b) 调用子类的load之前,会先调用父类的load
2> 再调用分类的load
a) 先编译的分类,优先调用load
(2).initialize
1> 先初始化父类
2> 再初始化子类(可能最终调用的是父类的initialize方法)
3> 如果分类实现了initialize方法就会覆盖类本身的initialize方法调用
4、通过关联对象给分类添加属性?
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
// 隐式参数
// _cmd == @selector(name)
return objc_getAssociatedObject(self, _cmd);
}
主题四《Block》===========
1、block的本质
2、block的捕获
3、block的类型
4、block的copy
5、对象类型的auto变量内存管理
6、__block修饰符及__block的内存管理及被__block修饰的对象类型的auto变量内存管理
7、ARC、MRC解决循环引用问题
主题五《Runtime》===========
1、oc的方法调用/oc消息机制?
(1)oc中的方法调用其实都是转成了objc_msgSend函数的调用;
(2)objc_msgSend底层有三大阶段:消息发送、动态解析、消息转发。
2、什么是runtime?
(1)oc是一门动态性很强的编程语言,允许很多操做推迟到运行时进行;
(2)oc的动态性是有runtime支撑和实现的,runtime是一套c语言的API,封装了很多动态性的函数;
(3)平时编写的oc代码底层都是转换成runtimeAPI进行调用的。
3、runtime在项目中的具体应用?
(1)利用关联对象(objc_setAssociatedObject)给分类添加属性;
(2)遍历类的所有成员变量(修改输入框占位符字体颜色、字典转模型、自动归档解档);
(3)交换方法实现(交换系统方法);
(4)利用消息转发机制解决方法找不到的异常问题等等。
4、常用API?
主题六《RunLoop》===========
1、什么是RunLoop,基本作用是什么?
字面意思:
- 运行循环、跑圈
- 其实它内部就是do-while循环,在这个循环内部不断的处理各种任务(比如source、timer、observer)
基本作用:
- 保持程序持续运行不退出
- 处理APP中各种事件(port事件,自定义事件,Selector事件,定时器事件)
- 节省CPU资源,提高程序性能,该做事的时候做事,该休息的时候休息
2、RunLoop与线程之间的关系?
- RunLoop与线程是一一对应的关系
- 主线程的RunLoop已经创建,子线程的RunLoop需要主动创建
- RunLoop第一次调用时创建,线程销毁时退出
3、RunLoop五大相关类及其之间的关系?
- CFRunloopRef
- CFRunloopModeRef
- CFRunloopTimerRef
- CFRunloopSourceRef
- CFRunloopObserverRef
RunLoop启动的时候必须要选择一种运行模式,当RunLoop选择了一种运行模式之后它会做一个判断做一个检查,它要检查一下这个运行模式是否为空,主要检查这个运行模式里面有没有source有没有timer,如果一个source也没有一个timer也没有它就认为这个运行模式为空,运行模式为空是这个RunLoop就马上退出了,如果运行模式不为空,里面有source或者有timer,那么这个RunLoop就启动了,也就是说这个死循环就开起来了,它就是这样运作的。注意检查的时候只会判断source和timer,它是不会判断observer的。
4、RunLoop的运行逻辑?
- 1、RunLoop启动
- 2、即将处理timer
- 3、即将处理source
- 4、处理source0
- 5、如果有source1,跳转到第九步
- 6、即将进入休眠
- 7、休眠中,等待被唤醒
- 8、结束休眠被唤醒
- 9、处理唤醒时收到的消息,timer消息和source1消息,然后跳转到第二步
- 10、退出RunLoop
5、RunLoop的应用有哪些?
- 开启一个常驻线程,让子线程不被消亡,等待其它线程发来消息,处理消息事件
(1)在子线程中开启一个定时器
(2)在子线程中进行一些长期的监控 - 可以控制定时器在特定模式下执行
- 可以让某些事件(行为、任务)在特定模式下执行
- 添加observer监听RunLoop状态
- 自动释放池的创建与销毁
(1)自动释放池第一次创建:当RunLoop启动的时候
(2)自动释放池最后一次销毁:当RunLoop退出的时候
(3)自动释放池其它时间的创建和销毁:当RunLoop将要进入休眠的时候,会把之前的自动释放池释放掉,重新创建一个新的自动释放池
主题七《多线程》===========
1、进程/线程/同步/异步/串行/并发的概念?
2、你理解的多线程(什么是多线程、多线程的原理、多线程的优缺点)?
首先多线程就是在一个进程里面开启了多条线程同时执行任务。但是多线程只是一个假象,同一时间CPU只能处理1条线程,多线程并发其实就是CPU快速的在多条线程之间切换,给人的感觉好像是多条线程同时执行任务。
-
优点:
1、防止线程阻塞、增加运行效率
2、提高资源利用率(CPU、内存利用率) -
缺点:
1、占空间(默认情况下,主线程占用1M,子线程占用512KB)、花时间(创建线程大约需要90毫秒的创建时间)
2、如果开启大量的线程,会降低程序的性能(消耗大量的CPU资源,CPU会在N多条线程之间调度,每条线程被调度执行的频次会降低),(建议3-5条线程,推荐3条)
3、程序设计复杂(线程之间的通信、多线程的数据共享)
3、iOS的多线程方案有哪几种?你更倾向于哪一种?
3532634-5b5c9c9dd3d33a80.jpg4、你在项目中用过GCD吗,都在哪里用的?
1)GCD一次性代码dispatch_once
2)GCD延迟函数dispatch_after
3)GCD快速迭代dispatch_apply(没用过)
4)GCD栅栏函数dispatch_barrier_async(没用过)
5)队列组
6)定时器
7)多线程(想想)
5、说一下NSOperation和GCD的区别?以及各自的优势?
1)GCD是纯C语言API,而操作队列是OC的对象;
2)GCD中任务用block块来表示,而块是个轻量级的数据结构;NSOperation则是个更加重量级的OC对象;
3)具体该使用GCD还是使用NSOperation需要看具体的情况;
NSOperation和NSOperationQueue的好处有:
1)NSOperationQueue可以挂起(暂停/恢复)和取消某个操作,而GCD中的任务是无法被取消的(安排好任务之后就不管了)。
2)NSOperation可以通过KVO提供对NSOperation对象的精细控制(如监听当前操作是否正在执行、是否被取消、是否已经完成等)。
3)NSOperation可以方便的指定操作间的依赖关系。
4)NSOperation可以方便的指定操作间的优先级(操做优先级表示此操做与队列中其它操作之间的优先关系,优先级高的操作先执行,优先级低的操作后执行)。
5)通过自定义NSOperation的子类可以实现操做重用。
6、线程安全处理的手段有哪些?
-
1、OSSpinLock:
叫做“自旋锁”,等待锁的线程会处于忙等状态,一直占用着CPU资源;
目前已经不再安全,可能会出现优先级反转问题; -
2、os_unfair_lock:
用于取代不安全的OSSpinLock,从iOS10开始支持,等待锁的线程处于休眠状态; -
3、Pthread_mutex:
互斥锁,等待锁的线程会处于休眠状态; -
4、NSLock:
是对mutex普通锁的封装; -
5、NSRecursiveLock:
是对mutex递归锁的封装; -
6、NSCondition:
是对mutex的条件的封装; -
7、NSConditionLock:
是对NSCondition的进一步封装,可以设置具体的条件值; -
8、dispatch_queue:
直接使用GCD的串行队列,也是可以实现线程同步的; -
9、dispatch_semaphore:
semaphore叫做“信号量”,信号量的初始值可以用来控制线程并发访问的最大数量;信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步; -
10、@synchronized:
是对mutex递归锁的封装;
@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁解锁操做;
注意:性能排序前四名
第一:os_unfair_lock(iOS10推出低版本不支持)
第二:OSSpinLock(存在优先级反转问题)
第三:dispatch_semaphore
第四:pthread_mutex
7、atomic关键字?
- atomic保证了属性setter、getter的原子性操作,相当于setter和getter内部加了线程同步的锁。
- 它并不能保证使用属性的过程是线程安全的。
8、多线程的读写安全?
- 1、pthread_rwlock:读写锁
- 2、dispatch_barrier_async:异步栅栏调用
主题八《内存管理》===========
1、CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么会引起循环引用,那么有两种解决的方案分别是什么(使用block、使用代理对象NSProxy)?
2、NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时,而GCD的定时器会更加准时,因为GCD是直接跟系统内核挂钩的,它不依赖于RunLoop,所以非常非常准时,并且不需要自己销毁,可将GCD定时器装成工具类来使用?
3、IOS程序的内存布局?
4、从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储 ?
5、OC的内存管理?
6、“深拷贝”和“浅拷贝”?
7、引用计数的存储?
在ARM64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中,SideTable类中的成员变量refcnts是一个存放着对象引用计数的散列表。
8、weak指针的实现原理?
(1)runtime维护了一个存储所有weak的散列表weak_table_t,它存储在SideTable结构体里。
(2)weak_table_t是一个哈希表,其中key为所指向对象的指针,value为weak指针的地址数组。
(3)当一个对象被销毁时,会自动调用dealloc,此时会查看weak_table_t散列表,根据对象的地址取出weak清空,并将指向当前对象的弱指针置为nil,防止野指针产生。
9、Runloop和Autorelease的关系?
主题九《性能优化》===========
主题十《架构设计》===========
主题十一《事件》===========
1、事件产生和传递?
当触摸一个视图时,首先系统会捕捉此事件,并为此事件创建一个UIEvent对象,将此对象加入当前应用程序的事件队列中,然后由UIApplication对象从队列中一个一个取出来进行分发,首先分发给主UIWindow对象,然后由主UIWindow对象分发给触摸的视图对象,也就是第一响应者对象(最合适的视图来处理触摸事件);如果第一响应者不处理,事件被沿着响应者链向上传递,交给下一个响应者。(首先看当前view是否是控制器的view,如果是,那么下一个响应者就是view所在的控制器;如果不是,下一个响应者就是它的父控件)。如果整个过程都没有响应这个事件,该事件就被丢弃。一般情况下,在响应链中只要有对象处理事件,事件就停止传递,但有时候可以在视图的响应方法中根据一些条件判断来决定是否需要继续传递事件。
2、如何找到最合适的控件来处理事件?
当事件传递给当前view时,会调用当前view的hitTest方法来寻找最合适的view,返回谁,谁就是最合适的view,谁就响应事件,就会调用谁的touches方法。
(1)自己是否能接收触摸事件
(2)触摸点是否在自己身上
(3)从后往前遍历子控件,重复前面的两个步骤
(4)如果没有符合条件的子控件,那么自己就是最合适的控件
3、UIView不能接收事件的三种情况?
(1)不接收用户交互
userInteractionEnabled = NO;
(2)隐藏
hidden = YES;
(3)透明
alpha = 0.0 ~ 0.01;
网友评论