iOS 高级面试题尝试解答
2018-12-19 本文已影响130人
Fisland_枫
[TOC]
iOS 基础题
- 分类和扩展有什么区别?可以分别用来做什么?分类有哪些局限性?分类的结构体里面有哪些成员?
- category:类别,分类
- 专门给类添加新的方法
- 不能给类添加成员属性,就算添加了,也无法获取到(其实可以通过runtime给分类添加属性)
- 分类中用@property定义变量,只会生成变量的getter,setter方法的声明,不能生成方法实现以及带下划线的成员变量
- 是运行期决议的
- 不能添加实例变量,和extension不同
- 原因:因为在运行期,对象的内存布局已经确定,如果添加实例变量会破坏类的内部布局,这对编译性语言是灾难性的。
- extension
- 可以说成是特殊的分类,也可以称为匿名分类
- 可给类添加成员变量,但是是私有变量
- 可以给类添加方法,也是私有方法
- 在编译器决议,是类的一部分,在编译器和头文件的@interface和实现文件里的@implement一起形成了一个完整的类
- 伴随类的产生而产生,也随着类的消息而消息
- extension一般用于隐藏类的私有消息,你必须有一个类的源码才能添加一个类的extension,类似NSString就无法添加
- category:类别,分类
- 讲一下atomic的实现机制;为什么不能保证绝对的线程安全(最好可以结合场景来说)?
- atomic会加一个锁来保障线程安全,也就是保证了读写操作是安全的,并且引用计数会+1,来向调用者保证这个对象会一直存在
- 但是不能保证绝对的线程安全,而且还会造成额外的性能消耗,所以通常会使用nonatomic。例如,有一个线程a不断读取属性name(用atomic修饰)的值,同时有一个线程b不断修改name的值,那么线程读到的还是修改后的值,可见不是线程安全的。
@property (atomic,copy) NSString *name;
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"aaa";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 1000; i++) {
NSLog(@"self.name ===== %@",self.name);
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.name = @"bbb";
});
}
/*输出
2018-12-18 16:08:05.269780+0800 testcopy[3154:2072066] index = 10,self.name ===== aaa
2018-12-18 16:08:05.269937+0800 testcopy[3154:2072066] index = 11,self.name ===== bbb
*/
- 被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?
- 释放时,会调用clearDeallocating函数,该函数动作如下
- 从weak表中获取废弃对象的地址作为键值的记录
- 将包含在记录中的所有附有weak修复符变量的地址,赋值为nil
- 将weak表中该记录删除
- 从引用计数表中删除废弃对象的地址为键值的记录
- Side这个结构体主要用于管理对象的引用计数和weak表,在NSObject.mm中声明起数据结构
- 释放时,会调用clearDeallocating函数,该函数动作如下
struct SideTable {
spinlock_t slock;//保证原子操作的自旋锁
RefcountMap refcnts;//引用计数的hash表
weak_table_t weak_table;//weak引用全局的hash表
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
- 关联对象有什么应用,系统如何管理关联对象?其被释放的时候需要手动将所有的关联对象的指针置空么?
- 因为在分类category@property不会自动生成实例变量和存取方法,这时候就可以使用关联对象可以为已经存在的类中添加属性
- 系统通过管理一个全局哈希表,通过对象指针地址和传递的固定参数地址来获取关联对象。根据setter传入的参数协议,来管理对象的生命周期。
- 关联对象其实就是 ObjcAssociation 对象
- 关联对象由 AssociationsManager 管理并在 AssociationsHashMap 存储
- 对象的指针以及其对应 ObjectAssociationMap 以键值对的形式存储在 AssociationsHashMap 中
- ObjectAssociationMap 则是用于存储关联对象的数据结构
- 每一个对象都有一个标记位 has_assoc 指示对象是否含有关联对象 (isa)
- 不需要,传入的objc_AssociationPolicy是内存管理的枚举类型,无论是哪个协议都不需要手动自动,ps:unsafe_unretain一般认为外部有对象控制,所以对象不用处理
#import "DKObject+Category.h"
#import <objc/runtime.h>
@implementation DKObject (Category)
- (NSString *)categoryProperty {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setCategoryProperty:(NSString *)categoryProperty {
objc_setAssociatedObject(self, @selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//objc_AssociationPolicy 协议类型
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
@end
- KVO的底层实现?如何取消系统默认的KVO并手动触发(给KVO的触发设定条件:改变的值符合某个条件时再触发KVO)?
- 当观察某个对象A时,KVO机制动态创建一个对象A当前类的子类,并为这个新的子类重写了观察属性keypath的setter方法。重写的setter方法会负责在调用原setter方法之前和之后,通知所有的观察对象:值的改变
- Apple使用isa混写(isa-swizzling)来实现KVO。当观察对象A时候,KVO机制动态创建一个新的名为:NSKVONotifying_A的新类,该类集成自对象A的本类,NSKVONotifying_A的重写观察属性的setter方法,并在setter调用前后,通知所有观察属性值得更改情况
@property (nonatomic,copy) NSString *name;
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
if ([key isEqualToString:@"name"]) {
return NO;
}
else{
return [super automaticallyNotifiesObserversForKey:key];
}
}
- (void)setName:(NSString *)name{
if (_name != name) {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
}
- Autoreleasepool所使用的数据结构是什么?AutoreleasePoolPage结构体了解么?
- 双链表结构,将main.m转换为cpp代码会发现autoreleasepool有这样的代码,一个构造函数,一个析构函数,再到runtime源码中看到AutoreleasePoolPage,发现调用了Autorelease对象都是通过调用AutoreleasePoolPage对象来管理的
- AutoreleasePoolPage有以下两点
- 每个AutoReleasePoolPage对象占用4096个字节的内存,出了用来存放它内部的成员变量外,剩下的空间用来存放Autorelease对象的地址。
- 所有的AutoreleasePoolPage对象都是通过双向链表的形式连接在一起
//main.cpp
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
//runtime 源代码 NSObject.mm
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
class AutoreleasePoolPage
{
...
static size_t const SIZE = PAGE_MAX_SIZE;
...
magic_t const magic; // 16字节
id *next; // 8字节
pthread_t const thread; // 8字节
AutoreleasePoolPage * const parent; // 8字节
AutoreleasePoolPage *child; // 8字节
uint32_t const depth; // 4字节
uint32_t hiwat; // 4字节
...
}
#define PAGE_MAX_SIZE PAGE_SIZE
#define PAGE_SIZE I386_PGBYTES
#define I386_PGBYTES 4096
- 讲一下对象,类对象,元类,跟元类结构体的组成以及他们是如何相关联的?为什么对象方法没有保存的对象结构体里,而是保存在类对象的结构体里?
- class_ro_t 和 class_rw_t 的区别?
- class_ro_t,objc类中的属性、方法还有遵循的协议等信息都保存在class_rw_t中
- 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;
};
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;
}
-
iOS 中内省的几个方法?class方法和objc_getClass方法有什么区别?
- 内省方法
- isKindOfClass:Class 检查对象是否是那个类或者其继承类实例化的对象
- isMemberOfClass:Class 检查对象是否是那个类但不包括继承类而实例化的对象
- respondToSelector:selector,检查对象是否包含这个方法
- conformsToProtocol:protocol 检查对象是否符合协议,是否实现协议中所有的必选方法
- 区别
- 内省方法
-
在运行时创建类的方法objc_allocateClassPair的方法名尾部为什么是pair(成对的意思)?
-
一个int变量被__block修饰与否的区别?
- 如果在block内部修改int变量的值,需要加上__block,不然会报错
-
为什么在block外部使用__weak修饰的同时需要在内部使用__strong修饰?
- 使用__weak只要是为了避免循环引用,导致内存无法释放
- 在内部使用__strong这个对象是为了防止这个对象在别处被释放,如果调用了改变量的block还没执行结束,那么系统就会保等待block执行完后再释放,对该变量在block中的使用起到保护作用
- 当block执行结束后自动释放掉
-
RunLoop的作用是什么?它的内部工作机制了解么?(最好结合线程和内存管理来说)
-
哪些场景可以触发离屏渲染?(知道多少说多少)
- 高耗能的绘制动作,类似圆角、阴影(mask, shadow, group opacity, edge antialiasing)
iOS 实战题
- AppDelegate如何瘦身?
- 反射是什么?可以举出几个应用场景么?(知道多少说多少)
- 有哪些场景是NSOperation比GCD更容易实现的?(或是NSOperation优于GCD的几点,知道多少说多少)
- App 启动优化策略?最好结合启动流程来说(main()函数的执行前后都分别说一下,知道多少说多少)
- App 无痕埋点的思路了解么?你认为理想的无痕埋点系统应该具备哪些特点?(知道多少说多少)
- 你知道有哪些情况会导致app崩溃,分别可以用什么方法拦截并化解?(知道多少说多少)
- 你知道有哪些情况会导致app卡顿,分别可以用什么方法来避免?(知道多少说多少)
网络题
- App 网络层有哪些优化策略?
- TCP为什么要三次握手,四次挥手?
- 对称加密和非对称加密的区别?分别有哪些算法的实现?
- HTTPS的握手流程?为什么密钥的传递需要使用非对称加密?双向认证了解么?
- HTTPS是如何实现验证身份和验证完整性的?
- 如何用Charles抓HTTPS的包?其中原理和流程是什么?
- 什么是中间人攻击?如何避免?
计算机系统题
- 了解编译的过程么?分为哪几个步骤?
- 静态链接了解么?静态库和动态库的区别?
- 内存的几大区域,各自的职能分别是什么?
- static和const有什么区别?
- 了解内联函数么?
- 什么时候会出现死锁?如何避免?
- 说一说你对线程安全的理解?
- 列举你知道的线程同步策略?
- 有哪几种锁?各自的原理?它们之间的区别是什么?最好可以结合使用场景来说
设计模式题
- 除了单例,观察者设计模式以外,还知道哪些设计模式?分别介绍一下
- 最喜欢哪个设计模式?为什么?
- iOS SDK 里面有哪些设计模式的实践?
- **设计模式是为了解决什么问题的?
- **设计模式的成员构成以及工作机制是什么?
- **设计模式的优缺点是什么?
架构 & 设计题
- MVC和MVVM的区别?MVVM和MVP的区别?
- 面向对象的几个设计原则了解么?最好可以结合场景来说。
- 可以说几个重构的技巧么?你觉得重构适合什么时候来做?
- 你觉得框架和设计模式的区别是什么?
- 看过哪些第三方框架的源码,它们是怎么设计的?设计好的地方在哪里,不好的地方在哪里,如何改进?(这道题的后三个问题的难度已经很高了,如果不是太N的公司不建议深究)
数据结构&算法题
- 链表和数组的区别是什么?插入和查询的时间复杂度分别是多少?
- 哈希表是如何实现的?如何解决地址冲突?
- 排序题:冒泡排序,选择排序,插入排序,快速排序(二路,三路)能写出那些?
- 链表题:如何检测链表中是否有环?如何删除链表中等于某个值的所有节点?
- 数组题:如何在有序数组中找出和等于给定值的两个元素?如何合并两个有序的数组之后保持有序?
- 二叉树题:如何反转二叉树?如何验证两个二叉树是完全相等的?