ios面试题总结

谈谈对面向对象的理解,面向对象的三要素

对于多态的理解

category的原理,父类有category,子类没有;子类有父类没有,子类父类都有,有同一个方法,会调用哪个,能不能调用到他本来的这个方法?category可以添加成员变量嘛?

分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。
某个类有多个category,会调用编译顺序(Build Phases下.m文件的顺序)中的最后一个。编译时通过压栈的方式将多个分类压栈,根据后进先出的原则,后编译的会被先调用。
category不能添加成员变量,会报错,因为category_t结构体中没有成员变量,原理是成员变量在编译时就决定好了,而category是运行时加载的。
可以给category添加成员属性,但是系统不会自动实现get、set方法,可以通过runtime.h中objc_getAssociatedObject / objc_setAssociatedObject来访问和生成关联对象

说一个熟悉的三方库

三次握手 四次挥手

三次握手:
1、客户端发送同步SYN和序列号seq请求建立连接,客户端进入SYN_SENT状态
2、服务端收到报文,确认客户端的SYN,发送标记位SYN、ACK和确认ack,服务端进入SYN_RECV状态
3、客户端收到服务端的报文,像服务端发送标记位ACK、序列号seq和确认号ack
四次挥手:
1、客户端发出连接释放报文,并停止发送数据, FIN=1, 序列号seq=u,客户端进入FIN_WAIT_1 状态
2、服务端收到报文后,发出确认报文,并带上自己的序列号,服务端进入CLOSE_WAIT状态,客户端收到确认请求后,客户端进入FIN_WAIT_2状态,等待服务端发送连接释放报文
3、服务端发送连接释放报文,进入LAST_ACK状态,等待客户端的确认
4、客户端收到该报文后,发出确认,客户端进入TIME_WAIT状态, 服务端收到客户端发出的确认,立即进入CLOSED状态

cocopods的podfile是做什么的,pod install 和pod update的区别

库是从cocopods的官网上下载的 https://cocoapods.org
pod install : 执行该命令时,如果Podfile.lock文件存在, 则直接从此文件中读取框架信息并且它会只下载Podfile.lock文件中指定的版本安装。对于不在Podfile.lock文件中的pod库,pod install命令会搜索这个pod库在Podfile文件中指定的版本来安装;如果Podfile.lock不存在, 则会读取Podfile文件内的框架信息,然后执行下载并且根据下载好的框架信息, 生成Podfile.lock文件。
pod update : 只有当你想要更新pod库的版本时才使用pod update;它不管Podfile.lock是否存在, 都会读取Podfile文件的的框架信息去下载安装,下载好之后, 再根据下载好的框架信息, 生成Podfile.lock文件

单例为什么要用static修饰

静态局部变量,虽然作用域没变,但是可以整个程序生命周期都保持不被销毁。

销毁单例

-(void)clear {
    onceToken = 0;
    manager = nil;
}

assign,strong,copy,weak?为什么weak持有的对象引用计数变为0,会指向nil

runtime会维护一个weak表,weak表本质是hash表,以指向的对象内存地址做为key,value是weak指针的地址数组。
1、初始化时,runtime会调用objc_initWeak,初始化一个新的weak指针指向对象的地址
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

使用 Weak 指针的时候,应首先获取一个 Strong 指针再使用。倒不是为了防止在使用过程中,对象被回收,形成野指针。 这个不用担心,因为你使用了 Weak 指针,对象就会被加入到 autoreleasepool 中,可以放心使用。但是要注意的是,如果在一个代码块中频繁使用 Weak 指针,还是应首先获取一个 Strong 指针,否则这个对象会被一次又一次的加入 autoreleasepool 中,也存在一定的性能开销。

NSTimer计时器会不会产生循环引用,怎么解决

不直接持有

NSTimer内存泄漏的原因

调用invalidate,从runloop中移除,释放对target对象的强引用

mutbaleCopy和copy的区别

不可变对象调用copy是浅拷贝,调用mutableCopy是深拷贝;可变都是深拷贝,但是调用copy拷贝出来的对象为不可变类型,数组是不完全深拷贝,只拷贝容器,item为浅拷贝,完全深拷贝需要调用copyItem

深拷贝和浅拷贝分别是什么

响应链和事件传递,给button添加手势会响应哪个(手势),给button的父试图添加手势会响应哪个(button

1.当iOS程序中发生触摸事件后,系统会将事件加入到UIApplication管理的一个任务队列中
2.UIApplication将处于任务队列最前端的事件向下分发。即UIWindow。
3.UIWindow将事件向下分发,即UIView。
4.UIView首先看自己是否能处理事件,触摸点是否在自己身上(hitTest:withEvent)。如果能,那么继续寻找子视图。

setvalue:forkey:和setobject:forkey:的区别,哪个值可为nil

setvalueforkey的value为nil时,调用removeObject:forKey

动态库和静态库的区别

编译/运行时加载

runtime,消息转发机制

runtime

给一个类动态添加成员变量
class_addIvar

一个排好序的数组,怎么找到第一次出现的某个值的下标

二分查找,找到后while循环看前一位是否相等

瀑布流效果

多代理

KVO的原理

isa_swizzing,A在运行时会动态生成一个notify_A类,然后将A类的实例对象的isa指针指向notify_A类,
观察A的name属性:
1、重写set方法,并在设置操作的前后分别调用 willChangeValueForKey: 和 didChangeValueForKey方法,这两个方法用于通知系统该 key 的属性值即将和已经变更了
2、重写class方法,返回原来的A类对象
3、重写dealloc方法,再合适的时候销毁这个运行时创建的类

KVC的原理

当调用setValue:forKey:时 ,
1、程序会先通过setter(set:)方法,对属性进行设置;
2、如果没有找到setKey:方法,KVC机制会检查+(BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法是返回YES的,如果重写返回了NO,那么这一步会执行setValue:forUndefinedKey:方法。若为YES,KVC机制会搜索该类中是否有名为key的成员变量,不管变量在类接口处定义没有,只要存在以key命名的变量,KVC都可以对该成员变量赋值。
3、如果该类既没有setKey方法,也没有_key成员变量,KVC机制会搜索_isKey的成员变量
4、如果_isKey成员变量也没有,KVC机制再会继续搜索key和isKey给它们赋值,如果上面列出的方法或者成员变量都不存在,系统会执行该对象的setValue:forUndefinedKey:方法
抛出异常(既 如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作)

重写isEqual:方法必须重写hash:方法

几种定时器的区别

NSTimer:通过RunLoop来实现;
CADisplayLink:基于屏幕刷新的周期,本质也是通过RunLoop,度量单位是帧,可设置preferredFramesPerSecond 属性,屏幕刷新多少帧时调用该方法;
GCD:使用了dispatch源,dispatch源使用内核

算法:二叉树的深度、链表是否有环、平衡二叉树

runloop

方法调用的三个步骤

消息发送obj_sendMessage(),动态方法解析(是否动态添加该方法),和消息转发

NSArray和NSSet的区别

1、NSArray内存中存储地址连续,而NSSet不连续
2、在搜索一个元素时NSSet比NSArray效率高,主要是它用到了一个算法hash;NSArray查找需要遍历
3、NSSet通过anyObject访问元素,NSArray通过下标访问

NSCache的好处

缓存功能,提供了类似可变字典的使用方式,
1、NSCache是线程安全的
2、内存不足时NSCache会自动释放存储的对象,不需要手动干预
3、NSCache的键key不会被复制,所以key不需要实现NSCopying协议

只用一个循环删除 一个单项链表的倒数第n个值

tcp和udp的区别,tcp接收到数据后如何反馈,tcp丢包情况下怎么处理

block有几种类型

block的本质是oc对象,内部有isa指针;类型:NSGlobalBlock,NSStackBlock,NSMallocBlock;变量捕获机制:仅捕获了值,没有捕获内存地址,block禁止从栈区上修改自动变量,因为变量进入了block实际就是修改了变量的作用域,在几个作用域之间进行切换时,如果不加上这样的限制,变量的可维护性将大大降低。 所以当申明__block的时候,实际就是把变量的内存地址从栈中的放到了堆中。进而在block内部也可以修改外部变量的值。
block在修改NSMutableArray时,如果修改的是NSMutableArray的存储内容的话,是不需要添加__block修饰的;如果修改的是NSMutableArray对象的本身,那必须添加__block修饰。

NSMutableArray *mutArr = [[NSMutableArray alloc] initWithArray:@[@"1",@"2",@"3"]];
    void (^block)(void) = ^{
        NSLog(@"%@", mutArr);  1,2,3,4
        [mutArr addObject:@"5"];
        NSLog(@"%@", mutArr);  1,2,3,4,5
    };
    [mutArr addObject:@"4"];
    mutArr = nil;
    block();

栈上的block copy到堆上:
1、调用copy
2、block内部用到了局部变量
3、外部调用block,self.block(); vc.block=^{};

ReactNative和Flutter的区别

https://zhuanlan.zhihu.com/p/70070316
reactnative是javaScript语言,是动态语言,代码产物是JS Bundle文件,是一套UI框架,会在Activity下加载JS文件,然后运行在JavaScriptCore中解析Bundle文件布局,最终堆叠出一系列的原生控件进行渲染。如标签对应ViewGroup/UIView
Flutter是Dart语言,是伪动态语言的强类型语言(通过var语法糖声明,在赋值时其实会通过自推导出类型;dynamic声明的才是真的动态变量),代码产物是二进制文件,Flutter中大部分Widget都与平台无关,开发者基于Framework开发App,Framework运行在Engine之上,由Engine进行适配和跨平台支持,将Flutter UI中的Widget数据化,然后通过Engine的Skia直接绘制到屏幕上。Flutter的整体渲染脱离了原生层面,直接和GPU交互。
他们在一定程度上有很大的通识性:都支持var定义变量,支持asycn/await语法糖,支持Promise(Future)等链式异步处理

Flutter热更新

MRC和ARC的区别

https://www.jianshu.com/p/5eac83471b23

NSURLSession和NSURLConnection的区别

NSURLConnection是基于HTTP1.0
NSURLSession是基于HTTP2.0
NSURLSession是iOS7中新的网络接口,最直接的改进就是可以配置每个session的缓存,协议,cookie,以及证书,甚至跨进程共享这些信息。
NSURLSession的另一重要组成部分是会话任务,它负责处理数据的加载,以及客户端与服务器之间的文件和数据的上传下载服务。NSURLSessionTask与NSURLConnection是及其相似的,因为它负责加载数据,而主要的区别在于,任务共享它们父类NSURLSession的共同委托(common delegate)。
https://blog.csdn.net/ioswcc/article/details/49183013

程序启动的过程

main 函数执行前,程序会做一系列的初始化工作,动态加载依赖库:
1、系统会读取程序的可执行文件mach-o,从里面获取动态加载器(dynamic link editor)的路径
2、加载dyld,dyld会初始化运行环境,配合ImageLoader将二进制文件加载到内存中去
3、动态链接依赖库,初始化依赖库,初始化runtime
4、runtime会对项目中所有的类进行类结构初始化,调用所有的load方法(再调用main函数前打印信息,可在分类的load方法中实现)
5、最后dyld会返回main函数地址,main函数被调用,进入程序入口
main函数执行后:
1、内部调用UIApplicationMain,创建一个UIApplication对象和它的代理,就是项目中的Appdelegate类
2、开始一个时间循环(main runLoop),监听系统事件
3、程序启动完毕时,通知代理Appdelegate,调用didFinishLaunching代理方法,在这里会创建UIWindow,设置它的rootViewController,
4、最后调用makeKeyAndVisable显示窗口

组件化插件化

CPU的度量单位

CPU的单位是Hz(赫兹)。
主频也叫时钟频率,单位是兆赫(MHz)或千兆赫(GHz),用来表示CPU的运算、处理数据的速度。通常,主频越高,CPU处理数据的速度就越快。

什么会导致视频卡顿

购物车倒计时页面怎样实现

Controller中创建一个timer,遍历数组,时间-1,刷新tableView(优化点:数组数据过大,遍历数组,刷新tableView有什么问题???)

block中添加一个通知,会不会产生循环引用,怎么避免

系统block为什么不会产生循环引用

GCD下暂停线程,取消线程

暂停

dispatch_suspend(queue)
dispatch_resume(queue);

取消
NSOperation有cancel可以取消还未执行的线程。但是没办法做到取消一个正在执行的线程。
GCD通过dispatch_block_cancel可以cancel掉dispatch_block_t,需要注意的是,未执行的可以用此方法cancel掉,若已经执行则cancel不掉;
iOS8以后, 如果想中断(interrupt)线程,也就是取消一个正在执行的线程,可以使用 dispatch_block_testcancel方法

线程间的通信

NSThread

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

GCD

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    dispatch_async(dispatch_get_main_queue(), ^{
    });
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});

FMDB中怎么保证线程安全

序列化(归解档)

需要实现NSCoding以及NSCopying(非必须)协议

- (void)encodeWithCoder:(NSCoder *)aCoder {
 
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeInteger:self.age forKey:@"age"];
}
//解档
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder {
 
    self = [super init];
    if (self) {
        self.name = [aDecoder decodeObjectForKey:@"name"];
        self.age  = [aDecoder decodeIntegerForKey:@"age"];
    }
    return self;
}

      Person *person = [[Person alloc] init];
    person.name = @"Frank";
    person.age  = 18;
        //这里以temp路径为例,存到temp路径下
    NSString *temp = NSTemporaryDirectory();
    NSString *filePath = [temp stringByAppendingPathComponent:@"obj.data"]; //注:保存文件的扩展名可以任意取,不影响。
    NSLog(@"%@", filePath);
    //归档
    BOOL isSuccess = [NSKeyedArchiver archiveRootObject:person toFile:filePath];
    if(isSuccess) {
        NSLog(@"归档成功");
    }else{
        NSLog(@"归档失败");
        }

      // 解档
    Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]

序列化只保存对象的状态,而不管对象的方法。
当一个父类实现了序列化,它的子类也自动实现序列化,不用显示进行实现了。
当一个实例对象引用其他对象,当序列化该对象时也把引用的对象进行了实例化。

架构

i386是针对intel通用微处理器32位处理器
x86_64是针对x86架构的64位处理器

模拟器32位处理器测试需要i386架构,
模拟器64位处理器测试需要x86_64架构,
真机32位处理器需要armv7,或者armv7s架构,
真机64位处理器需要arm64架构。

DNS劫持问题

域名请求时会先向DNS服务器发出请求,把域名转换成机器能够识别的ip地址
通过httpDNS,使用DNS协议向DNS服务器的53端口进行请求,运营商通过当前地址返回一个最近的ip地址,通过NSURLProtocol类即可实现。

消息转发的第一部,如果没有动态添加该方法,返回了YES,会有什么后果

返回YES后,会继续去方法列表中查找,没有找到的话进入消息转发,但是并不会导致死循环,会有一个标记

卡顿的计算

用 CADisplayLinker 来计数:
CADisplayLink可以以屏幕刷新的频率调用指定selector,iOS系统中正常的屏幕刷新率为60次/秒,只要在这个方法里面统计每秒这个方法执行的次数,通过次数/时间就可以得出当前屏幕的刷新率了。
通过子线程监测主线程的RunLoop:
开启子线程,实时计算这两个状态区域之间的耗时是否到达某个阀值,便能揪出这些性能杀手,假定连续6次超时50ms认为卡顿(当然也包含了单次超时300ms)

工厂设计模式

  • 简单工厂模式
    由一个工厂对象决定创建出哪一种产品类的示例
    create方法通常是静态的,所以称之为静态工厂方法
    缺点:集中了所有实例的创建逻辑,违反了高内聚的职责分配原则,扩展性差
 public ICar GetCar(CarType carType)
        {
            switch (carType)
            {
                case CarType.SportCarType:
                    return new SportCar();
                case CarType.JeepCarType:
                    return new JeepCar();
                case CarType.HatchbackCarType:
                    return new HatchbackCar();
                default:
                    throw new Exception("爱上一匹野马,可我的家里没有草原. 你走吧!");
            }
        }
  • 工厂方法模式
    动态性工厂模式,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,完全实现开闭原则
  • 抽象工厂模式
    抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。为创建一组相关或相互依赖的对象提供一个接口,而且无需制定他们的具体类。工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则是针对多个产品等级结构
    缺点:产品族的扩展将是一件十分费力的事情。

layoutIfNeed和setNeedsLayout

layoutIfNeed:立即更新布局
setNeedsLayout:异步绘制的方法。会调用layoutSubViews
setNeedsDisplay:异步绘制的方法。会自动调用drawRect方法

HTTPS的过程

HTTP请求都是明文传输的,没有经过加密的信息,导致数据不安全;HTTPS可以将数据加密传输,也就是传输的密文,保证了网络通信的安全。
HTTPS协议=HTTP协议+SSL/TLS协议(安全套接层协议),再HTTPS数据传输过程中,需要用SSL/TLS对数据进行加密和解密,需要用HTTP对加密的数据进行传输。
1、客户端向服务端发起请求
2、服务端把自己的证书(包含公钥)发送给客户端
3、客户端对证书进行验证,通过,客户端通过公钥对自己随机生成的字符串(对称加密中使用的密钥)进行加密传给服务端,服务端用私钥进行解密(非对称加密),三次握手完成。
4、客户端对数据进行对称加密给到服务端,服务端解密

红黑二叉树

是一种特殊的二叉查找树。红黑树的每个结点上都有存储位表示结点的颜色,可以是红(Red)或黑(Black)。
红黑树的特性:
每个结点是黑色或者红色。
根结点是黑色。
每个叶子结点(NIL)是黑色。 [注意:这里叶子结点,是指为空(NIL或NULL)的叶子结点!]
如果一个结点是红色的,则它的子结点必须是黑色的。
每个结点到叶子结点NIL所经过的黑色结点的个数一样的。[确保没有一条路径会比其他路径长出俩倍,所以红黑树是相对接近平衡的二叉树的!]

NSArray、NSDictionary应该如何选关键词

同NSString,如果用MutableString给String赋值的话,对MutableString进行copy操作是深拷贝,开辟了新的内存,MutableString修改的话不会影响string,如果用strong修饰,只是引用计数+1,地址还是源字符串的,会影响string的值。

如何实现自定义类的深拷贝

相当于创建一个新的对象,把源对象的成员属性值付给新对象的成员属性

- (id)copyWithZone:(NSZone *)zone {
    CustomModel *copy = [[[self class] alloc] init];
    unsigned int propertyCount = 0;
    objc_property_t *propertyList = class_copyPropertyList([self class], &propertyCount);
    for (int i = 0; i < propertyCount; i++ ) {
        objc_property_t thisProperty = propertyList[I];
        const char* propertyCName = property_getName(thisProperty);
        NSString *propertyName = [NSString stringWithCString:propertyCName encoding:NSUTF8StringEncoding];
        id value = [self valueForKey:propertyName];
        [copy setValue:value forKey:propertyName];
    }
    return copy;
}
// 注意此处需要实现这个函数,因为在通过Runtime获取属性列表时,会获取到一个名字为hash的属性名,这个是系统帮你生成的一个属性
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {}

父类实现深拷贝之后,子类只要重写copyWithZone方法,在方法内部调用父类的copyWithZone方法,之后实现自己的属性的处理;父类没有实现深拷贝,子类除了需要对自己的属性进行处理,还要对父类的属性进行处理

CoreAnimation如何在动画过程中获取当前的frame

 [[v.layer presentationLayer] frame]

__block原理

相比于不用__block修饰,结构体中多保存了一个指针变量,block体内修改的实际是a指向的堆中的内容,所以任何对这个指针的操作,是可以影响到原来的变量的。
进一步,我们考虑截获的自动变量是Objective-C的对象的情况。在开启ARC的情况下,将会强引用这个对象一次。这也保证了原对象不被销毁,但与此同时,也会导致循环引用问题。

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0 *Desc;
    __Block_byref_val_0 *val;   //指针变量

    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,__Block_byref_val_0 *_val, int flags=0) : val(_val->__forwrding){
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

dispatch_once原理

dispatch_once

void dispatch_once(dispatch_once_t *val, void (^block)(void)){
    struct Block_basic *bb = (void *)block;
    dispatch_once_f(val, block, (void *)bb->Block_invoke);
}
void dispatch_once_f(dispatch_once_t *val, void *ctxt, void (*func)(void *)){
    volatile long *vval = val;
    if (dispatch_atomic_cmpxchg(val, 0l, 1l)) {
        func(ctxt); // block真正执行
        dispatch_atomic_barrier();
        *val = ~0l;
    } else {
        do {
            _dispatch_hardware_pause();
        } while (*vval != ~0l);
        dispatch_atomic_barrier();
    }
}

1、dispatch_atomic_cmpxchg,它是一个宏定义,原型为__sync_bool_compare_and_swap((p), (o), (n)) ,这是LockFree给予CAS的一种原子操作机制,原理就是 如果p==o,那么将p设置为n,然后返回true;否则,不做任何处理返回false

2、在多线程环境中,如果某一个线程A首次进入dispatch_once_f,val==0,这个时候直接将其原子操作设为1,然后执行传入dispatch_once_f的block,然后调用dispatch_atomic_barrier,最后将val的值修改为~0。

3、dispatch_atomic_barrier是一种内存屏障,所谓内存屏障,从处理器角度来说,是用来串行化读写操作的,从软件角度来讲,就是用来解决顺序一致性问题的。编译器不是要打乱代码执行顺序吗,处理器不是要乱序执行吗,你插入一个内存屏障,就相当于告诉编译器,屏障前后的指令顺序不能颠倒,告诉处理器,只有等屏障前的指令执行完了,屏障后的指令才能开始执行。所以这里dispatch_atomic_barrier能保证只有在block执行完毕后才能修改*val的值。

4、在首个线程A执行block的过程中,如果其它的线程也进入dispatch_once_f,那么这个时候if的原子判断一定是返回false,于是走到了else分支,于是执行了do while循环,其中调用了_dispatch_hardware_pause,这有助于提高性能和节省CPU耗电,pause就像nop,干的事情就是延迟空等的事情。直到首个线程已经将block执行完毕且将*val修改为~0,调用dispatch_atomic_barrier后退出。这么看来其它的线程是无法执行block的,这就保证了在dispatch_once_f的block的执行的唯一性,生成的单例也是唯一的。

NSDictionary原理,重复key是怎么处理的?

NSDictionary是通过Hash表来实现key和value之间的映射和存储的
哈希表的本质是一个数组,数组中的每一个元素成为箱子(bin),箱子中存放着键值对。
哈希表的存储过程:
1、根据key计算出hash值h
2、假设有n个箱子,那么这个键值对放在h%n个箱子中
3、如果该箱子已经有了键值对,就使用开放寻址法或者拉链法解决冲突
重复的key,会覆盖value值

二叉树的先序、中序、后序遍历

ios面试题总结_第1张图片
image.png

前序:先遍历根节点,然后是左节点,最后是右节点(A B D H E I C F J K G)
中序:先遍历左节点,然后是根节点,最后是右节点(D H B E I A J F K C G)
后序:先左节点,然后是右节点,最后是根节点(H D I E B J K F G C A)

图片编解码

静态图的解码,基本可以分为以下步骤:
1、创建CGImageSource

CGImageSourceCreateWithData:
CGImageSourceCreateWithURL:
CGImageSourceCreateWithDataProvider:

2、读取图像格式元数据(可选)
需要获取一些相关的图像信息,包括图像的格式,图像数量,EXIF元数据等

图像格式:CGImageSourceGetType
图像数量(动图):CGImageSourceGetCount
CGImageSourceCopyPropertiesAtIndex:
NSDictionary *imageProperties = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source, 0, NULL); //静态图都为0
NSUInteger width = [imageProperties[(__bridge NSString *)kCGImagePropertyPixelWidth] unsignedIntegerValue]; //宽度,像素值
NSUInteger height = [imageProperties[(__bridge NSString *)kCGImagePropertyPixelHeight] unsignedIntegerValue]; //高度,像素值
BOOL hasAlpha = [imageProperties[(__bridge NSString *)kCGImagePropertyHasAlpha] boolValue]; //是否含有Alpha通道
CGImagePropertyOrientation exifOrientation = [imageProperties[(__bridge NSString *)kCGImagePropertyOrientation] integerValue]; // 这里也能直接拿到EXIF方向信息,和前面的一样。如果是iOS 7,就用NSInteger取吧 :)

3、解码得到CGImage

CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL);

4、生成上层的UIImage,清理

// UIImageOrientation和CGImagePropertyOrientation枚举定义顺序不同,封装一个方法搞一个switch case就行
UIImageOrientation imageOrientation = [self imageOrientationFromExifOrientation:exifOrientation];
UIImage *image = [[UIImage imageWithCGImage:imageRef scale:[UIScreen mainScreen].scale orientation:imageOrientation];

// 清理,都是C指针,避免内存泄漏
CGImageRelease(imageRef);
CFRelease(source)

用户态和内核态

当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。这与处于内核态的进程的状态有些类似。
用户态切换到内核态的3种方式:
1、系统调用
2、异常
3、外围设备的中断

LRU缓存

删除最近使用最少的数据
算法:
去维护一个有序的单向链表,越靠近链表尾部的结点是越早之前访问的,当有一个新的数据被访问的时候,我们从头开始遍历链表
如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除,再插入到链表头部。
如果此数据没有被缓存在链表中,如果此时缓存未满,直接将此结点插入到链表的头部;如果缓存已满,则删除链表尾部结点,将新数据插入到链表的头部。

RunLoop中source0和source1的区别

source0:需要手动唤醒线程
source1:具有唤醒线程的能力

load和initialize的区别

1、+load方法当类或分类添加到object-c runtime时被调用,子类的+load方法会在它所有父类的+load方法之后执行,而分类的+load方法会在它的主类的+load方法之后执行。但不同的类之间的+load方法的调用顺序是不确定的,所以不要在此方法中用另一个类。

2、+load方法不像普通方法一样,它不遵循那套继承规则。如果某个类本身没有实现+load方法,那么不管其它各级超类是否实现此方法,系统都不会调用。+load方法调用顺序是:SuperClass -->SubClass --> CategaryClass。

3、+initialize是在类或者它的子类接受第一条消息前被调用,但是在它的超类接收到initialize之后。也就是说+initialize是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的+initialize方法是不会被调用的。

4、+initialize方法和+load方法还有个区别,就是运行期系统完整度上来讲,此时可以安全使用并调用任意类中的任意方法。而且,运行期系统也能确保+initialize方法一定会在“线程安全的环境”中执行,这就是说,只有执行+initialize的那个线程可以操作类或类实例,其他线程都要阻塞等着+initialize执行完。

5、+initialize方法和其他类一样,如果某个类未实现它,而其超类实现了,那么就会运行超类的实现代码。如果本身和超类都没有实现,超类的分类实现了,就会去调用分类的initialize方法。如果本身没有实现,超类和父类的分类实现了就会去调分类的initialize方法。不管是在超类中还是分类中实现initialize方法都会被调多次,调用顺序是SuperClass -->SubClass。


ios面试题总结_第2张图片
image.png

tcp和ip协议的关系

TCP/IP提供点对点的链接机制,将数据应该如何封装、定址、传输、路由以及在目的地如何接收,都加以标准化。

TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。
而IP是给因特网的每一台电脑规定一个地址,并解决如何发现和找到这个地址。

weak原理中怎么解决哈希冲突

Method-Swizzling的原理

交换的是指针指向的地址(存放方法实现的)

frame和autoLayout

AutoReleasePool释放的时机

当前RunLoop周期结束前进行出栈操作

每个对象销毁前都会去判断有没有弱引用表嘛

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

对数据库的优化 FMDB

arc下NSNumber *num = [NSNumber numberWithInt:1];引用计数

通常情况下我们使用NSNumber都是通过 [NSNumbernumberWithInt:1] 建立自动释放的对象
在官方的解释中,retainCount对于某些特殊情况下的对象并不可靠
而NSNumber一般创建的是自动释放的对象,自动释放的对象的retainCount也是不可靠的
所以你使用init之后,记得release即可,不用关心它的retaincount

NSRunLoopCommonModes为什么可以在两个Mode下都运行

源码注解


void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {    
    CHECK_FOR_FORK();

    //如果要添加timer的Runloop对象已经正在释放了,就不要添加了,直接返回
    if (__CFRunLoopIsDeallocating(rl)) return;

    //判断timer对象是否存在,timer关联的runloop是否存在,
    //并且timer当前关联的runloop不能是要添加它的runloop,如果是的话直接返回,不需要添加了
    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;

    //给当前的runloop加锁,防止在其他地方操作
    __CFRunLoopLock(rl);

    //如果当前要将timer添加到Runloop的commonModes集合下的话
    if (modeName == kCFRunLoopCommonModes) {
        //先判断Runloop对象是否有commonModes集合
        //如果有 : 则直接拿到集合,否则set为NULL
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        //如果RunLoop对象没有commonModesItems
        if (NULL == rl->_commonModeItems) {
            //创建一个RunLoop的commonModes集合
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        //将timer添加到RunLoop的commonModeItems集合里面
        CFSetAddValue(rl->_commonModeItems, rlt);
        //如果RunLoop的commonModes集合不为空
        if (NULL != set) {
            //把runloop对象和timer包装成数组
            CFTypeRef context[2] = {rl, rlt};
            //添加一个新的commonModesItems,也就是添加一个新的事件到RunLoop里面
            /* add new item to all common-modes */
            //这里就是遍历commonModes集合,然后对每一个标示(defaultMode和trackingMode)调用
            //第二个参数那个函数,也就是在每一个commonModes的mode对象中都注册了一个timer的事件源
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set);
        }
    } else {
      
    }
    __CFRunLoopUnlock(rl);
}

就是在每一个commonModes的mode对象中都注册了一个timer的事件源

HTTP2.0

用Layer方法设置圆角时,为什么会产生离屏渲染

根据Profile监测帧率,发现大量的设置圆角,掉帧很严重。

UIViewController的生命周期

initWithCoder:(NSCoder *)aDecoder:(如果使用storyboard或者xib)
loadView:加载view
viewDidLoad: view加载完毕
viewWillAppear:控制器的view将要显示
viewWillLayoutSubviews:控制器的view将要布局子控件(可能会多次调用)
viewDidLayoutSubviews:
viewDidAppear:
viewWillDisappear
viewDidDisappear

nil和null的区别

nil:用于表示指向oc中对象的指针为空
null:表示c指针为空
Nil:表示OC类(class)类型的变量值为空

点击App icon到启动App有什么过程

内存管理问题

分别用assign、weak、strong修饰会输出什么
assign会崩溃
weak 0 self.subView为null
strong 1

@property (nonatomic, assign) UIView *subView;

self.subView = [[UIView alloc] init];
NSLog(@"%f", self.subView.alpha);

NSProxy和NSObject设计代理类的差异

NSProxy不是NSObject的子类,但是实现了NSObject协议

你可能感兴趣的:(ios面试题总结)