最全
iOSInterviewsAndDevNotes
零散
21出一套iOS高级面试题2018年7月.md
阿里、字节 一套高效的iOS面试题解答
iOS面试了20几家总结出来的面试题(一)
2020年面试:整理出一份高级iOS面试题
2019 iOS 面试题大全(补充完整版)
2020年6月最新iOS面试题总结(答案篇)
20阿里字节一套高效的iOS面试题2020年2月.md
基础知识,操作系统,网络
待补充
lru缓存
哈希桶
链式地址法和开放地址法的优缺点分别是什么?
链式地址法(HashMap)
优点:
处理冲突简单,且无堆积现象,平均查找长度短;
链表中的结点是动态申请的,适合构造表不能确定长度的情况;
相对而言,拉链法的指针域可以忽略不计,因此较开放地址法更加节省空间。
插入结点应该在链首,删除结点比较方便,只需调整指针而不需要对其他冲突元素作调整。
缺点:
指针占用较大空间时,会造成空间浪费。若空间用于增大散列表规模进而提高开放地址法的效率。
开放地址法:
缺点:
容易产生堆积问题;
不适于大规模的数据存储;
结点规模很大时会浪费很多空间;
散列函数的设计对冲突会有很大的影响;
插入时可能会出现多次冲突的现象,删除的元素是多个冲突元素中的一个,需要对后面的元素作处理,实现较复杂;
优点:
当节点规模较少,或者装载因子较少的时候,使用开发寻址较为节省空间,如果将链式表的指针用于扩大散列表的规模时,可使得装载因子变小从而减少了开放寻址中的冲突,从而提高平均查找效率。
作者:沈先生的影子
链接:https://www.jianshu.com/p/4de541a56c03
来源:
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
阿里字节一套高效的iOS面试题2020年2月
启动顺序
OS的启动流程
根据 info.plist 里的设置加载闪屏,建立沙箱,对权限进行检查等
加载可执行文件
加载动态链接库,进行 rebase 指针调整和 bind 符号绑定
Objc 运行时的初始处理,包括 Objc 相关类的注册、category 注册、selector 唯一性检查等;
初始化,包括了执行 +load() 方法、attribute((constructor)) 修饰的函数的调用、创建 C++ 静态全局变量。
执行 main 函数
Application 初始化,到 applicationDidFinishLaunchingWithOptions 执行完
初始化帧渲染,到 viewDidAppear 执行完,用户可见可操作。
介绍下runtime的内存模型(isa、对象、类、metaclass、结构体的存储信息等)
对于objc1.0来说
breme大神讲解
struct objc_class {
struct objc_class *isa;
};
struct objc_object {
struct objc_class *isa;
};
typedef struct objc_class *Class; //类 (class object)
typedef struct objc_object *id; //对象 (instance of class)
凡是有isa指针的,都可以称做一个对象
对象(objc_object)的isa指针指向类对象(objc_class),类对象的isa指针指向metaclass(objc_class),metaclass的isa指针指向NSObject的meta_class,NSObject的meta_class指向自己。可以总结为,一切皆为objc_class
贴个breme大神的代码,将objc转成C++,可以看到,创建一个类,会创建OBJC_CLASS__NyanCat ,分别对应类和元类,他们都是_class_t
//Class的实际结构
struct _class_t {
struct _class_t *isa; //isa指针
struct _class_t *superclass; //父类
void *cache;
void *vtable;
struct _class_ro_t *ro; //Class包含的信息
};
//Class包含的信息
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
unsigned int reserved;
const unsigned char *ivarLayout;
const char *name; //类名
const struct _method_list_t *baseMethods; //方法列表
const struct _objc_protocol_list *baseProtocols; //协议列表
const struct _ivar_list_t *ivars; //ivar列表
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties; //属性列表
};
//NyanCat(meta-class)
struct _class_t OBJC_METACLASS_$_NyanCat = {
.isa = &OBJC_METACLASS_$_NSObject,
.superclass = &OBJC_METACLASS_$_NSObject,
.cache = (void *)&_objc_empty_cache,
.vtable = (void *)&_objc_empty_vtable,
.ro = &_OBJC_METACLASS_RO_$_NyanCat, //包含了类方法等
};
//NyanCat(Class)
struct _class_t OBJC_CLASS_$_NyanCat = {
.isa = &OBJC_METACLASS_$_NyanCat, //此处isa指向meta-class
.superclass = &OBJC_CLASS_$_NSObject,
.superclass = (void *)&_objc_empty_cache,
.vtable = (void *)&_objc_empty_vtable,
.ro = &_OBJC_CLASS_RO_$_NyanCat, //包含了实例方法 ivar信息等
};
typedef struct objc_object NyanCat; //定义NyanCat类型
//更详细的不贴代码了..
对于objc2.0来说
Runtime内存模型探究
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
}
struct objc_object {
private:
isa_t isa;
}
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
}
2.0版本的内存模型,objc_object里的isa指针不是Class类型,是个isa_t的结构体,但是isa_t结构体里还是包含了Class指针
class_data_bits_t
1.0里Class的_class_ro_t替换成了class_data_bits_t里的class_rw_t,里面也是有方法列表和属性列表
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
}
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t version;
uint16_t witness;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
}
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
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_copyIvarList & class_copyPropertyList区别
# class_copyIvarList方法获取实例变量问题引发的思考
class_copyIvarList获取当前类的实例变量,但不包括父类的,json转模型很多用到这个方法
class_copyPropertyList获取用property定义的属性名,也可以用来json转模型。
题解答案从底层源码(objc2.0)出发
class_copyIvarList是拿的class_rw_t里的ro(class_ro_t)的ivars属性
class_copyPropertyList拿的是class_rw_t里的properties属性
Ivar *
class_copyIvarList(Class cls, unsigned int *outCount)
{
const ivar_list_t *ivars;
Ivar *result = nil;
unsigned int count = 0;
if (!cls) {
if (outCount) *outCount = 0;
return nil;
}
mutex_locker_t lock(runtimeLock);
assert(cls->isRealized());
//class_copyIvarList是拿的class_rw_t里的ro(class_ro_t)的ivars属性
if ((ivars = cls->data()->ro->ivars) && ivars->count) {
result = (Ivar *)malloc((ivars->count+1) * sizeof(Ivar));
for (auto& ivar : *ivars) {
if (!ivar.offset) continue; // anonymous bitfield
result[count++] = &ivar;
}
result[count] = nil;
}
if (outCount) *outCount = count;
return result;
}
objc_property_t *
class_copyPropertyList(Class cls, unsigned int *outCount)
{
if (!cls) {
if (outCount) *outCount = 0;
return nil;
}
mutex_locker_t lock(runtimeLock);
checkIsKnownClass(cls);
assert(cls->isRealized());
auto rw = cls->data();
property_t **result = nil;
unsigned int count = rw->properties.count();
if (count > 0) {
result = (property_t **)malloc((count + 1) * sizeof(property_t *));
count = 0;
for (auto& prop : rw->properties) {
result[count++] = ∝
}
result[count] = nil;
}
if (outCount) *outCount = count;
return (objc_property_t *)result;
}
对象模型
OC对象占用内存原理
既然类方法都存在Class里,那oc的属性指向真正的对象的指针都存在哪里呢?其实在对象alloc的时候,所有指向指针的指针都分配在了objc_object这结构体后面的若干offset后面
weak实现
OC Runtime之Weak(2)---weak_entry_t
OC Runtime之Weak(1)---weak_table_t
OC对象之旅 weak弱引用实现分析
全局SideTables()->side table->weak table--->weak entry---> referrers + inline_referrers
设置weak对象的时候,根据对象的地址在全局SideTables()(哈希表)里,去除对应的side table(其实也算一个自定义的哈希表)
category如何被加载的,两个category的load方法的加载顺序,两个category的同名方法的加载顺序
美团category
category会在runtime启动_objc_init的时候加载(此时main函数还没执行),在_read_images函数里
category的加载是在运行时发生的,加载过程是,把category的实例方法、属性、协议添加到类对象上。把category的类方法、属性、协议添加到metaclass上。
category的load方法执行顺序是根据类的编译顺序决定的,即:xcode中的Build Phases中的Compile Sources中的文件从上到下的顺序加载的。
category并不会替换掉同名的方法的,也就是说如果 category 和原来类都有 methodA,那么 category 附加完成之后,类的方法列表里会有两个 methodA,并且category添加的methodA会排在原有类的methodA的前面,因此如果存在category的同名方法,那么在调用的时候,则会先找到最后一个编译的 category 里的对应方法。
initialize && Load
深入详解 iOS的 +load和+initialize
load调用时机和顺序
一、+load 方法是在所有类被加入到runtime以后,main函数执行之前被系统自动调用的。
二、系统自动为每一个类调用+load方法(如果有),无需手动调用,也无需手动调用[super load]。
三、+load方法会按照文件所在的Compile Sources顺序加载,在调用类的+load之前,会优先调用其父类的+load方法。
四、在所有类的+load方法调用完以后再调用[Category load]方法,加载顺序按照Compile Sources排列顺序。
+initialize调用时机和顺序
一、+ initialize 在类第一次接收到消息之前被系统自动调用,无需手动调用。
二、在调用子类的+ initialize 方法之前,会先调用父类的+ initialize 方法(如果有),所以也无需手动调用[super initialize]方法。
三、如果父类中有+ initialize方法,而子类中没有+ initialize方法,子类会自动继承父类的+ initialize方法,也就是说父类的+ initialize方法会调用两次。
四、Category中+ initialize方法会覆盖类中的+ initialize,同一个类有多个Category都实现了+initialize方法时,Compile Sources 列表中最后一个Category 的+initialize方法会覆盖其他的+ initialize方法。
AutoReleasePool释放
AutoreleasePool的原理和实现
AutoReleasePool
在没有手动加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop
如果采取一些非cocoa创建的一些线程,将不会自动生成autoreleasepool给你,你需要手动去创建它。
AutoReleasePool包含push和pop操作每一个线程的 autoreleasepool 其实就是一个指针的堆栈;
每一个指针代表一个需要 release 的对象或者 POOL_SENTINEL(哨兵- 对象,代表一个 autoreleasepool 的边界);
一个 pool token 就是这个 pool 所对应的 POOL_SENTINEL 的内存地址。当这个 pool 被 pop 的时候,所有内存地址在 pool token 之后的对象都会被 release ;
这个堆栈被划分成了一个以 page 为结点的双向链表。pages 会在必要的时候动态地增加或删除;
Thread-local storage(线程局部存储)指向 hot page ,即最新添加的 autoreleased 对象所在的那个 page 。
Autoreleasepool是由多个AutoreleasePoolPage以双向链表的形式连接起来的,
Autoreleasepool的基本原理:在每个自动释放池创建的时候,会在当前的AutoreleasePoolPage中设置一个标记位,在此期间,当有对象调用autorelsease时,会把对象添加到AutoreleasePoolPage中,若当前页添加满了,会初始化一个新页,然后用双向量表链接起来,并把新初始化的这一页设置为hotPage,当自动释放池pop时,从最下面依次往上pop,调用每个对象的release方法,直到遇到标志位。 AutoreleasePoolPage结构如下
class AutoreleasePoolPage {
magic_t const magic;
id *next;//下一个存放autorelease对象的地址
pthread_t const thread; //AutoreleasePoolPage 所在的线程
AutoreleasePoolPage * const parent;//父节点
AutoreleasePoolPage *child;//子节点
uint32_t const depth;//深度,也可以理解为当前page在链表中的位置
uint32_t hiwat;
}
谈下iOS开发中知道的哪些锁?
不再安全的 OSSpinLock
@synchronized 性能最差,SD和AFN等框架内部有使用这个.它内部也是递归锁
NSRecursiveLock 和 NSLock :建议使用前者,避免循环调用出现死锁
OSSpinLock 自旋锁,存在的问题是:优先级反转问题,破坏了spinlock。优先级翻转,即当低优先级线程占用了资源,高优先级线程这时也需要这个资源,但是被低优先级的抢占了,但是spinlock会一直等待,即忙等待,不会让出时间片,导致高优先级线程一直占用cpu,低优先级线程没有时间片,这样就导致卡顿
dispatch_semaphore 信号量 : 保持线程同步为线程加锁
NSCondition,一般用于生产者消费者问题
iOS NSCondition生产者消费者问题
How do I use NSConditionLock? Or NSCondition
iOS多线程下的锁
如果缓冲区满了怎么办?
生产者-消费者问题理解
NSCondition *condition = [NSCondition new];
-(void) crateConsumenr{
[condition lock];
while(self.products.count == 0){
NSLog(@"等待产品");
[condition wait];
}
[self.products removeObject:0];
NSLog(@"消费产品");
[condition unlock];
}
-(void)createProducter{
[condition lock];
[self.products addObject:[[NSObject alloc] init]];
NSLog(@"生产了一个产品");
[condition signal];
[condition unlock];
}
App 启动速度怎么做优化与监控
- main() 函数执行前;
- 加载可执行文件(App 的.o 文件的集合);
- 加载动态链接库,进行 rebase 指针调整和 bind 符号绑定;
- Objc 运行时的初始处理,包括 Objc 相关类的注册、category 注册、selector 唯一性
检查等; - 初始化,包括了执行 +load() 方法、attribute((constructor)) 修饰的函数的调用、创建C++ 静态全局变量。
根据上面,减少动态库使用,如果数量多,尽可能合并,减少load方法执行任务
- main() 函数执行后;
- 从功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是 App 启动必要的初始化功能,而哪些是只需要在对应功能开始使用时才需要初始化的。梳理完之后,将这些初始化功能分别放到合适的阶段进行
- main() 函数开始执行后到首屏渲染完成前只处理首屏相关的业务,其他非首屏业务的初始化、监听注册、配置文件读取等都放到首屏渲染完成后去做。
- 方法级别优化
- Time Profiler
- objc_msgSend 方法进行 hook
线程保活
三次握手,四次挥手
TCP好文
本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”。主要目的防止server端一直等待,浪费资源。
runloop
- runloop在被获取创建
- runloop由若干个mode,比如默认的defaultMode和UITrackingMode,这两个mode都标记为common mode,所以加入timer时,不加入到这两个mode,而是加入到common mode items里面,会被runloop直接加到标记为common mode的mode里(上面提到的两个都是)
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set
runloop里面,系统自己创建了很多时间的obeserver,如_wrapRunLoopWithAutoreleasePoolHandler,负责每个循环创建和销毁autorelease pool
又如_UIGestureRecognizerUpdateObserver手势事件和_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv UI绘制可以看到上面的source0和source1,__IOHIDEventSystemClientQueueCallback是souce1,手势触发的回调
common mode items = {
// source0 (manual)
CFRunLoopSource {order =-1, {
callout = _UIApplicationHandleEventQueue}}
CFRunLoopSource {order =-1, {
callout = PurpleEventSignalCallback }}
CFRunLoopSource {order = 0, {
callout = FBSSerialQueueRunLoopSourceHandler}}
// source1 (mach port)
CFRunLoopSource {order = 0, {port = 17923}}
CFRunLoopSource {order = 0, {port = 12039}}
CFRunLoopSource {order = 0, {port = 16647}}
CFRunLoopSource {order =-1, {
callout = PurpleEventCallback}}
CFRunLoopSource {order = 0, {port = 2407,
callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}}
CFRunLoopSource {order = 0, {port = 1c03,
callout = __IOHIDEventSystemClientAvailabilityCallback}}
CFRunLoopSource {order = 0, {port = 1b03,
callout = __IOHIDEventSystemClientQueueCallback}}
CFRunLoopSource {order = 1, {port = 1903,
callout = __IOMIGMachPortPortCallback}}
// Ovserver
CFRunLoopObserver {order = -2147483647, activities = 0x1, // Entry
callout = _wrapRunLoopWithAutoreleasePoolHandler}
CFRunLoopObserver {order = 0, activities = 0x20, // BeforeWaiting
callout = _UIGestureRecognizerUpdateObserver}
CFRunLoopObserver {order = 1999000, activities = 0xa0, // BeforeWaiting | Exit
callout = _afterCACommitHandler}
CFRunLoopObserver {order = 2000000, activities = 0xa0, // BeforeWaiting | Exit
callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
CFRunLoopObserver {order = 2147483647, activities = 0xa0, // BeforeWaiting | Exit
callout = _wrapRunLoopWithAutoreleasePoolHandler}
// Timer
CFRunLoopTimer {firing = No, interval = 3.1536e+09, tolerance = 0,
next fire date = 453098071 (-4421.76019 @ 96223387169499),
callout = _ZN2CAL14timer_callbackEP16__CFRunLoopTimerPv (QuartzCore.framework)}
},
卡顿优化
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Loop
};
可以看出,做事情都是在kCFRunLoopBeforeSources后和kCFRunLoopAfterWaiting后,如果长时间停留在这两个状态,则证明主线程发生卡顿
线程保活
线程保活
- 子线程需要先获取(创建)runloop并切启动
- 往runloop加入事件源或者timer
消息转发流程
objc_msgSend流程分析
objc_msgSend找到imp的流程
1、OC方法经过xcode编译会变成一个objc_msgSend函数,函数是由汇编实现的,进入到汇编代码,
2、我们会发现在objc_msgSend中通过CacheLookup去查找函数的指针,并且在CacheLookup里面会递归从buckets里面查找方法,如果找到指针直接调用CacheHit 触发TailCallCachedImp进行执行方法,如果没找到就触发了CheckMiss,然后走慢速查找流程
3、在Checkmiss里面又调用了__objc_msgSend_uncached函数,在__objc_msgSend_uncached里面调用了MethodTableLookup,
4、在MethodTableLookup里面调用了__class_lookupMethodAndLoadCache3,该函数是由C语言编写的,
5、然后通过lookUpImpOrForward调用了cache_fill_nolock逻辑。
消息转发
当开发者调用了未实现的方法,苹果提供了三个解决途径:
1、resolveInstanceMethod:为发送消息的对象的添加一个IMP,然后再让该对象去处理
2、forwardingTargetForSelector:将该消息转发给能处理该消息的对象
3、methodSignatureForSelector和forwardInvocation:第一个方法生成方法签名,然后创建NSInvocation对象作为参数给第二个方法,
4、然后在第二个方法里面做消息处理,只要在第二个方法里面不执行父类的方法,即使不处理也不会崩溃
https
证书真伪
数字签名的制作过程:
CA拥有非对称加密的私钥和公钥。
CA对证书明文信息进行hash。
对hash后的值用私钥加密,得到数字签名。
明文和数字签名共同组成了数字证书,这样一份数字证书就可以颁发给网站了。
那浏览器拿到服务器传来的数字证书后,如何验证它是不是真的?(有没有被篡改、掉包)
浏览器验证过程:
拿到证书,得到明文T,数字签名S。
用CA机构的公钥对S解密(由于是浏览器信任的机构,所以浏览器保有它的公钥。详情见下文),得到S’。
用证书里说明的hash算法对明文T进行hash得到T’。
比较S’是否等于T’,等于则表明证书可信。
关联对象的应用?系统如何实现关联对象的
iOS内存管理-week和关联对象怎么释放
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
线程安全的