最近从公司离职了,准备了一下接下来的面试,翻出了J_Knight的高级面试题复习了一下面试html
iOS 基础题ios
分类和扩展有什么区别?能够分别用来作什么?分类有哪些局限性?分类的结构体里面有哪些成员?c++
区别: extension在编译期决议 它伴随类的产生而产生,亦随之一块儿消亡 category则彻底不同,它是在运行期决议的 extension能够添加实例变量,而category是没法添加实例变量的(由于在运行期,对象的内存布局已经肯定,若是添加实例变量就会破坏类的内部布局,这对编译型语言来讲是灾难性的)。面试
分类应用 能够把类的实现分开在几个不一样的文件里面。这样作有几个显而易见的好处,编程
1:能够减小单个文件的体积缓存
2:能够把不一样的功能组织到不一样的category里安全
3:能够由多个开发者共同完成一个类bash
4:能够按需加载想要的category 等等。markdown
5:声明私有方法数据结构
扩展应用 extension通常用来隐藏类的私有信息
struct category_t {
constchar*name;//类的名字(name)
classref_t cls;//类(cls)
struct method_list_t *instanceMethods; //category中全部给类添加的实例方法的列表(instanceMethods)
structmethod_list_t *classMethods;//category中全部添加的类方法的列表(classMethods)
structprotocol_list_t *protocols; //category实现的全部协议的列表(protocols)
structproperty_list_t *instanceProperties;//category中添加的全部属性(instanceProperties)
};
复制代码
讲一下atomic的实现机制;为何不能保证绝对的线程安全(最好能够结合场景来讲)?
atomic只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的,所以在多线程编程时,线程安全还须要开发者本身来处理.关 于选择:atomic系统生成的getter、setter会保证get、set操做的安全性,但相对nonatomic来讲,atomic要更耗费资源,且速度要慢,故在iPhone等小型设备上,若是没有多线程之间的通信,使用nonatomic是更好的选 atomic系统自动生成的getter/setter方法会进行加锁操做 nonatomic系统自动生成的getter/setter方法不会进行加锁操做
例如:线程1调用了某一属性的setter方法并进行到了一半,线程2调用其getter方法,那么会执行完setter操做后,在执行getter操做,线程2会获取到线程1 setter后的完整的值. 当几个线程同时调用同一属性的setter、getter方法时,会get到一个完整的值,但get到的值不可控.例如:线程1 调用getter线程2 调用setter线程3 调用setter这3个线程并行同时开始,线程1会get到一个值,可是这个值不可控,多是线程2,线程3 set以前的原始值,多是线程2set的值,也多是线程3 set的值
被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构能够画出来么?
https://blog.csdn.net/future_one/article/details/81606895 。 浅谈iOS之weak底层实现原理
https://www.jianshu.com/p/f331bd5ce8f8 浅谈iOS之weak底层实现原理
复制代码
关联对象有什么应用,系统如何管理关联对象?其被释放的时候须要手动将全部的关联对象的指针置空么?
1.2 如何关联对象
runtime提供了給咱们3个API以管理关联对象(存储、获取、移除):
123456 //关联对象void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)//获取关联的对象id objc_getAssociatedObject(id object, const void *key)//移除关联的对象void objc_removeAssociatedObjects(id object)
其中的参数
id object:被关联的对象
const void *key:关联的key,要求惟一
id value:关联的对象
objc_AssociationPolicy policy:内存管理的策略
void objc_removeAssociatedObjects(id object);,会移除全部的关联,包括其余模块添加的,所以应该用 objc_setAssociatedObject(..,nil,..) 的方式去卸载。
苹果官方文档说 OBJC_ASSOCIATION_ASSIGN 至关于一个 weak reference,但其实等于 assign/unsafe_unretained。
对于与weak的区别不在本文讨论范围内,浅显的区别在于变量释放后,weak 会把引用置空,unsafe_unretained会保留内存地址,一旦获取可能会野指针闪退。
详情:https://www.jianshu.com/p/1feae48a5dda AssociatedObject关联对象原理实现
复制代码
KVO的底层实现?如何取消系统默认的KVO并手动触发(给KVO的触发设定条件:改变的值符合某个条件时再触发KVO)?
KVO是经过isa-swizzling技术实现的(这句话是整个KVO实现的重点)。 在运行时根据原类建立一个中间类,这个中间类是原类的子类, 并动态修改当前对象的isa指向中间类。而且将class方法重写,返回原类的 Class。 1.在运行的时候建立被监类的子类 2.在子类中重写父类属性的set方法(故kvo之监听属性) 3注册这个子类 4.修改当前被监听的isa指针指向子类 5.实现set函数 在分析KVO的内部实现以前,先来分析一下KVO的存储结构,主要用到了如下几个类: GSKVOInfo GSKVOPathInfo GSKVOObservation @interface GSKVOInfo : NSObject { NSObject *instance; // Not retained. observer保存观察者 注意这里也是 Not retained 释放以后,在调用会崩溃,须要在对象销毁前,移除全部观察者 GSLazyRecursiveLock *iLock; NSMapTable *paths; paths 用于保存keyPath 到 GSKVOPathInfo 的映射: } @interface GSKVOPathInfo : NSObject { @public unsigned recursion; unsigned allOptions; 保存了观察者的options集合 NSMutableArray *observations; 保存了全部的观察者(GSKVOObservation 类型) NSMutableDictionary *change; 保存了KVO触发要传递的内容 } @interface GSKVOObservation : NSObject { @public NSObject *observer; // Not retained (zeroing weak pointer) void *context; 都是添加观察者时传入的参数 int options; 都是添加观察者时传入的参数 } @end KVO内部屡次用到了KVC 1️⃣ 重写 setValue:forKey 2️⃣ 使用valueForKey --- valueForKeyPath获取属性的值,尤为是在使用点语法的时候,只有valueForKeyPath能够得到深层次的属性值。 因此KVO是基于KVC而实现的。 详细连接:https://www.jianshu.com/p/d6e4ba25acd2 复制代码
Autoreleasepool所使用的数据结构是什么?AutoreleasePoolPage结构体了解么?
Autorelease pool的实现原理 Autorelease pool是有objc_autoreleasePoolpush和objc_autoreleasePoolpop实现 objc_autoreleasePoolpush和objc_autoreleasePoolpop是由AutoreleasePoolPage 实现的 class AutoreleasePoolPage { //大小4096 字节字节 双向列列表实现的 magic_t const magic; 用于对当前 AutoreleasePoolPage 完整性的校验 id *next; 指向了下一个为空的内存地址 pthread_t const thread; /当前所在的线程 AutoreleasePoolPage * const parent; 头节点 AutoreleasePoolPage *child; 尾节点 。 uint32_t const depth; 深度 uint32_t hiwat; POOL_SENTINEL(哨兵对象) id next next 指向了下一个为空的内存地址,若是 next 指向的地址加入一个 object }; 在每一个自动释放池初始化调用 objc_autoreleasePoolPush 的时候,都会把一个 POOL_SENTINEL push 到自动释放池的栈顶,而且返回这个 POOL_SENTINEL 哨兵对象。 而当方法 objc_autoreleasePoolPop 调用时,就会向自动释放池中的对象发送 release 消息,直到第一个 POOL_SENTINEL: 注意:在这里会进入一个比较关键的方法 autoreleaseFast,并传入哨兵对象 POOL_SENTINEL: void *objc_autoreleasePoolPush(void) { return AutoreleasePoolPage::push(); } static inline void *push() { return autoreleaseFast(POOL_SENTINEL); } static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); hotPage 能够理解为当前正在使用的 AutoreleasePoolPage。 if (page && !page->full()) { return page->add(obj); } 有 hotPage 而且当前 page 不满,有 hotPage 而且当前 page 不满 调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中 else if (page) { return autoreleaseFullPage(obj, page); 有 hotPage 而且当前 page 已满,调用 autoreleaseFullPage 初始化一个新的页,调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中 } else { return autoreleaseNoPage(obj); } 无 hotPage 调用 autoreleaseNoPage 建立一个 hotPage } id *add(id obj) { id *ret = next; *next = obj; next++; return ret; } 这个方法其实就是一个压栈的操做,将对象加入 AutoreleasePoolPage 而后移动栈顶的指针 它会从传入的 page 开始遍历整个双向链表,直到: 查找到一个未满的 AutoreleasePoolPage 使用构造器传入 parent 建立一个新的 AutoreleasePoolPage 在查找到一个可使用的 AutoreleasePoolPage 以后,会将该页面标记成 hotPage,而后调动上面分析过的 page->add 方法添加对象。 既然当前内存中不存在 AutoreleasePoolPage,就要从头开始构建这个自动释放池的双向链表,也就是说,新的 AutoreleasePoolPage 是没有 parent 指针的。 初始化以后,将当前页标记为 hotPage,而后会先向这个 page 中添加一个 POOL_SENTINEL 对象,来确保在 pop 调用的时候,不会出现异常。 最后,将 obj 添加到自动释放池中。 4.3 objc_autoreleasePoolPop 方法 static inline void pop(void *token) { AutoreleasePoolPage *page = pageForPointer(token); //使用 pageForPointer 获取当前 token 所在的 AutoreleasePoolPage static AutoreleasePoolPage *pageForPointer(const void *p) { return pageForPointer((uintptr_t)p); } static AutoreleasePoolPage *pageForPointer(uintptr_t p) { AutoreleasePoolPage *result; uintptr_t offset = p % SIZE; assert(offset >= sizeof(AutoreleasePoolPage)); result = (AutoreleasePoolPage *)(p - offset); result->fastcheck(); return result; } pageForPointer 方法主要是经过内存地址的操做,获取当前指针所在页的首地址 将指针与页面的大小,也就是 4096 取模,获得当前指针的偏移量,由于全部的 AutoreleasePoolPage 在内存中都是对齐的 而最后调用的方法 fastCheck() 用来检查当前的 result 是否是一个 AutoreleasePoolPage。 id *stop = (id *)token; page->releaseUntil(stop); // 调用 releaseUntil 方法释放栈中的对象,直到 stop //它的实现仍是很容易的,用一个 while 循环持续释放 AutoreleasePoolPage 中的内容,直到 next 指向了 stop。 if (page->child) { if (page->lessThanHalfFull()) { page->child->kill(); //调用 child 的 kill 方法 } else if (page->child->child) { page->child->child->kill(); } } } 整个自动释放池 autoreleasepool 的实现以及 autorelease 方法都已经分析完了,咱们再来回顾一下文章中的一些内容: 自动释放池是由 AutoreleasePoolPage 以双向链表的方式实现的。 当对象调用 autorelease 方法时,会将对象加入 AutoreleasePoolPage 的栈中。 调用 AutoreleasePoolPage::pop 方法会向栈中的对象发送 release 消息。 做者:卡布达巨人 连接:https://juejin.cn/post/1 来源:掘金 复制代码
讲一下对象,类对象,元类,跟元类结构体的组成以及他们是如何相关联的?为何对象方法没有保存的对象结构体里,而是保存在类对象的结构体里?
class_ro_t 和 class_rw_t 的区别?
class_rw_t 和 class_ro_t
ObjC 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中:
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
};
其中还有一个指向常量的指针 class_ro_t,其中存储了当前类在编译期就已经肯定的属性、方法以及遵循的协议。
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
uint32_t reserved;
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
在编译期间类的结构中的 class_data_bits_t *data 指向的是一个 class_ro_t * 指针:
isa 是指向元类的指针,不了解元类的能够看:Classes and Metaclasses
super_class 指向当前类的父类
cache 用于缓存指针和 vtable,加速方法的调用
bits 就是存储类的方法、属性、遵循的协议等信息的地方
而后在加载 ObjC 运行时的过程当中在 realizeClass 方法中:
从 class_data_bits_t 调用 data 方法,将结果从 class_rw_t 强制转换为 class_ro_t 指针
初始化一个 class_rw_t 结构体
设置结构体 ro 的值以及 flag
最后设置正确的 data。
可是,在这段代码运行以后 class_rw_t 中的方法,属性以及协议列表均为空。这时须要 realizeClass 调用 methodizeClass 方法来将类本身实现的方法(包括分类)、属性和遵循的协议加载到 methods、 properties 和 protocols 列表中。
详情:https://blog.csdn.net/fishmai/article/details/71157861
复制代码
iOS 中内省的几个方法?class方法和objc_getClass方法有什么区别?
Class objc_getClass(const chat *aClassName)
1:Class objc_getClass(const chat *aClassName)
1> 传入字符串类名
2> 返回对应的类对象
Class object_getClass(id obj)
2. Class object_getClass(id obj)
1> 传入的obj多是instance对象,class对象、meta-class对象
2> 返回值
a:若是是instance对象,返回class对象
b:若是是class对象,返回meta-class对象
c:若是是meta-class对象,返回NSObject(基类)的meta-class对象
- (class)class、+(class)class
3:- (class)class、+(class)class
1>返回的就是类对象
结论:当obj为实例变量时,object_getClass(obj)与[obj class]输出结果一直,均得到isa指针,即指向类对象的指针。
总结:经上面初步的探索得知,object_getClass(obj)返回的是obj中的isa指针;而[obj class]则分两种状况:一是当obj为实例对象时,[obj class]中class是实例方法:- (Class)class,返回的obj对象中的isa指针;二是当obj为类对象(包括元类和根类以及根元类)时,调用的是类方法:+ (Class)class,返回的结果为其自己。
做者:洲洲哥
连接:https://www.jianshu.com/p/5cfd52d222f0
来源:简书
简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。
复制代码
在运行时建立类的方法objc_allocateClassPair的方法名尾部为何是pair(成对的意思)
动态建立类
动态建立类涉及到如下几个函数:
12345678 // 建立一个新类和元类Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );// 销毁一个类及其相关联的类void objc_disposeClassPair ( Class cls );// 在应用中注册由objc_allocateClassPair建立的类void objc_registerClassPair ( Class cls );
objc_allocateClassPair函数:若是咱们要建立一个根类,则superclass指定为Nil。extraBytes一般指定为0,该参数是分配给类和元类对象尾部的索引ivars的字节数。
为了建立一个新类,咱们须要调用objc_allocateClassPair。而后使用诸如class_addMethod,class_addIvar等函数来为新建立的类添加方法、实例变量和属性等。完成这些后,咱们须要调用objc_registerClassPair函数来注册类,以后这个新类就能够在程序中使用了。
实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上。
objc_disposeClassPair函数用于销毁一个类,不过须要注意的是,若是程序运行中还存在类或其子类的实例,则不能调用针对类调用该方法。
http://www.cocoachina.com/ios/20141031/10105.html
https://blog.csdn.net/hypercode/article/details/53931517
复制代码
一个int变量被__block修饰与否的区别?
Block的本质<一>
http://www.cocoachina.com/ios/20180910/24846.html
理清 Block 底层结构及其捕获行为 https://juejin.cn/post/6844903686615859213
iOS底层原理总结 - 探寻block的本质(一) http://www.cocoachina.com/ios/20180628/23965.html
iOS底层原理总结 - 探寻block的本质(二) http://www.cocoachina.com/ios/20180628/23968.html
iOS 看懂此文,你的block不再须要WeakSelf弱引用了! http://www.cocoachina.com/ios/20180110/21817.html
iOS中Block的用法,举例,解析与底层原理(这多是最详细的Block解析 . http://www.cocoachina.com/ios/20180424/23147.html
复制代码
为何在block外部使用__weak修饰的同时须要在内部使用__strong修饰?
关于使用__weak和__strong 你们都看到别人在block里面使用self或者self的属性的时候要使用__weak修饰self,而后才能block里面使用,在block里面使用的时候又将weakSelf使用__strong修饰进行使用,好比: __weak __typeof(self) weakSelf = self; self.block = ^{ __strong __typeof(self) strongSelf = weakSelf; [strongSelf doSomeThing]; [strongSelf doOtherThing]; }; 为何使用weakSelf 经过 clang -rewrite-objc 源代码文件名 将代码转为c++代码(实质是c代码),能够看到block是一个结构体,它会将全局变量保存为一个属性(是__strong的),而self强引用了block这会形成循环 引用。因此须要使用__weak修饰的weakSelf。 为何在block里面须要使用strongSelf 是为了保证block执行完毕以前self不会被释放,执行完毕的时候再释放。这时候会发现为何在block外边使用了__weak修饰self,里面使用__strong修饰weakSelf的时候不会发生循环引用?! PS:strongSelf只是为了保证在block内部执行的时候不会释放,但存在执行前self就已经被释放的状况,致使strongSelf=nil。注意判空处理。 不会引发循环引用的缘由 由于block截获self以后self属于block结构体中的一个由__strong修饰的属性会强引用self, 因此须要使用__weak修饰的weakSelf防止循环引用。 block使用的__strong修饰的weakSelf是为了在block(能够理解为函数)生命周期中self不会提早释放。strongSelf实质是一个局部变量(在block这个“函数”里面的局部变量),当block执行完毕就会释放自动变量strongSelf,不会对self进行一直进行强引用。 总结 外部使用了weakSelf,里面使用strongSelf却不会形成循环,究其缘由就是由于weakSelf是block截获的属性,而strongSelf是一个局部变量会在“函数”执行完释放。 复制代码
RunLoop的做用是什么?它的内部工做机制了解么?(最好结合线程和内存管理来讲)
https://juejin.cn/post/6844903604965523464 iOS底层原理探究-Runloop https://juejin.cn/post/6844903606932471822 RunLoop终极解析:输入源,定时源,观察者,线程间通讯,端口通讯,NSPort,NSMessagePort,NSMachPort,NSPortMessage https://juejin.cn/post/6844903598350925831 iOS底层原理总结 - RunLoop https://juejin.cn/post/1fc3ec8a0bb9f0065bd2889 iOS RunLoop 探究 http://www.cocoachina.com/ios/20180814/24550.html 老司机出品——源码解析之RunLoop详解 https://juejin.cn/post/6844903588712415239 iOS RunLoop详解 http://www.cocoachina.com/ios/20180522/23447.html iOS开发·RunLoop源码与用法彻底解析 http://www.cocoachina.com/ios/20180626/23932.html 深刻理解RunLoop 复制代码
哪些场景能够触发离屏渲染?(知道多少说多少)
做者:J_Knight_ 连接:juejin.cn/post/684490… 来源:掘金 著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。