题目引自 zhangferry — 快手iOS面经,此处根据自己理解做出解答记录
一面
1. 用递归写一个算法,计算从1到100的和。
int fun(int x) {
if(x>1)
return x+fun(x-1); //
else
return x;
}
int main(int argc, const char * argv[]) {
int sum;
sum = fun(100);
printf("sum=%d\n",sum);
return 0;
}
// sum=5050
时间复杂度O(n)
,空间复杂度O(n)
非递归
时间复杂度O(n)
,空间复杂度O(1)
int fun(int n) {
int total = 0;
for (int i = 1; i <= n; i++) {
total += i;
}
return total;
}
等差数列公式
时间复杂度O(1)
,空间复杂度O(1)
int fun(int n) {
return n * (1+n)/2;
}
递归优缺点:
优点是代码易读易懂
缺点是代码效率低,空间复杂度高,调用太深容易栈溢出
2. property的作用是什么,有哪些关键词,分别是什么含义?
property
是一个属性自动生成器,自动生成setter、getter方法
常见属性关键词:atomic, nonatomic, assign, retain, strong, weak, copy, readonly, readwrite, unsafe_unretained, getter=, setter= 等。
默认属性关键词:继承于NSObject类的对象:(atomic, strong), 非继承于NSObject类的对象:(atomic, assign)
属性意义:
atomic:原子性的,在执行setter和getter方法时可以保证访问变量的线程安全。
nonatomic:非原子性的,无法保证访问变量的线程安全性,但是变量访问效率会提高。
assign:主要用于修饰非继承于NSObject类型的对象,例如int, double, NSInteger等,当该对象被其他对象引用时,该对象的引用计数不会自加1。
retain:一般情况下等同于ARC环境下的strong修饰符,但是在修饰block对象时,retain相当于assign,而strong相当于copy。
strong:主要用于继承于NSObject类的对象,当strong修饰的对象被其他对象引用时,引用计数会自加1。
weak:主要用于继承于NSObject类的对象,当weak修饰的对象被其他对象引用时,引用计数不会自加1,且retainCount为0时,指向该对象的指针将会置nil,指向堆栈的底部0x00000000,防止野指针的出现。
unsafe_unretained:主要用于继承于NSObject类型的对象,当retainCount为0时,指向该对象的指针不会置nil,因此可能会出现野指针,但是效率方面会比weak要高。
copy:主要适用于NSArray,NSDictionary,NSString,Block等类型的对象,开辟一块新的内存存储原来内存中的元素,对象指针指向新内存的地址。
readonly:只读属性
readwrite:读写属性
getter =:修改读属性名称
setter =:修改写属性名称
关键词:
readonly:只读
assign:单纯赋值(非对象类型使用)
retain:进行对象保持操作
strong:同retain,用于ARC
weak:不进行对象保持操作,同时访问安全的,用于ARC
copy:复制对象 (生成副本进行赋值,但只适用于遵循NSCopying协议的对象)
nonatomic:非原子操作,非线程安全,主要使用
atomic:原子操作,主要是在getter和setter方法里面加锁,基本不用
3. 父类的property是如何查找的?
@interface Person : NSObject
{
NSObject *bbbbb;
}
@property (nonatomic, strong) NSObject *aaaaaa;
@end
@implementation Person
@end
@interface Teacher : Person
@end
@implementation Teacher
@end
clang编译后
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSObject *bbbbb;
NSObject *_aaaaaa;
};
struct Teacher_IMPL {
struct Person_IMPL Person_IVARS;
};
1.子类中的
propert_list
、method_list
、ivar_list
并不包含父类
2.子类对象的_IMPL
包含父类的,所以应该是通过Person_IMPL
查找的父类property
。
4. NSArray和NSDictionary如何选关键词?
- 当修饰可变类型的属性时,如NSMutableArray、NSMutableDictionary、NSMutableString,用strong。
- 当修饰不可变类型的属性时,如NSArray、NSDictionary、NSString,用copy。
5. copy和mutableCopy有什么区别?深复制和浅复制是什么意思,如何实现深复制?
- 对于不可变对象,copy就是浅复制,mutableCopy 就是深复制。
- 对于可变对象,copy和mutableCopy都是深复制。
- 不论是可变还是不可变对象,copy返回的对象都是不可变的,mutableCopy返回的对象都是可变的。
- 容器类对象(NSArray,NSDictionary等):
不论是可变的还是不可变的,copy,mutableCopy返回的容器类对象里所包含的对象的地址和之前都是一样的,即容器内对象都是浅复制。
6. 用runtime做过什么事情?runtime中的方法交换是如何实现的?
- KVO监听属性变化
- 动态方法解析时给类添加方法
- 消息转发
- 方法交换
方法交换实现原理:
每个类都维护一个方法Method表,Method包含SEL
和其对应IMP
的信息,方法交换做的事情就是把SEL
和IMP
的对应关系断开,并和新的IMP
生成对应关系。比如:ASel -> AImp、BSel -> BImp,交换之后就是ASel -> BImp、BSel -> AImp。
7. 讲一下对KVC和KVO的理解?
- KVC:
- 通过
valueForKey:
和valueForKeyPath:
来取值,按照get<Key>
,<key>
,is<Key>
,_<key>
的顺序查找方法。- 通过
setValueForKey:
和setValueForKeyPath:
来赋值。按照set<Key>
,_set<Key>
的顺序查找方法。- iOS13以前可以通过KVC获取、设置系统属性,iOS13以后被禁用。
- KVO:
- 利用
Runtime API
动态生成一个子类,并且让对象的isa
指向这个全新的子类,当修改对象的属性时,会调用如下方法:
Foundation
的_NSSetXXXValueAndNotify
函数
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:
内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:
)
如何手动触发KVO?
手动调用willChangeValueForKey:和didChangeValueForKey:
直接修改成员变量会触发KVO么?
不会触发KVO,因为直接修改成员变量并没有走set方法。
通过KVC修改属性会触发KVO么?
会,KVC内部在修改成员变量的同时是会主动调用KVO的。
8. block的分类,__block的作用,block循环引用产生的原因及解决办法,block捕获变量的流程是什么?
- blcok有六种,三种系统(源码中有,不常用,此处不做细说),三种常用如下:
- 全局blcok:当生成的block是独立的,不引用外部变量或者作为参数参与其他函数,则处于全局区,为全局block。
- 堆block:当block 引用外部变量,此时block 会从全局区移动到堆区
- 栈block:当block 被copy 前,会处于栈区,当栈block赋值给block变量后,栈block就会被拷贝到堆上,成为堆block。
- 在 MRC下:只要没有访问外部变量,就是全局block。访问了外部变量,就是栈block。显示地调用[block copy]就是堆block。
- 在 ARC下:只要没有访问外部变量,就是全局block。如果访问了外部变量,那么在访问外部变量之前存储在栈区,访问外部变量之后存储在堆区。
- __block的作用:
__block
在作用域内部生成了变量的指针,通过改变指针指向的地址,来改变原变量。
- __block修改外面变量的原理:为对应的__block变量生成了一个结构体,该结构体中保存了外界变量的指针和值,传递了一个指针给block函数调用。从而可以实现修改外面变量。
- block循环引用问题:当一个类的对象持有block,block里面又引用了这个对象,那么就是一个循环引用的关系。
防止循环引用的三种方式:
- __weak打破强引用关系
- 中介者模式,传入临时量VC,在block执行完成后手动置为nil
- block传参防止循环引用
block捕获变量的流程:除了静态变量和全局变量以外,都是block 在内部自动生成一个新的变量,用来接收外部变量。
9. 说一下对GCD的了解,它有哪些方法,分别是做什么用的?
- GCD 可用于多核并行运算
- GCD 会自动利用更多的 CPU 内核(比如双核、四核)
- GCD 会自动管理线程的生命周期 (创建线程、调度任务、销毁线程)
- 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
GCD方法及使用
- GCD信号量: dispatch_semaphore
// 创建一个Semaphore并初始化信号的总量
dispatch_semaphore_create
// 发送一个信号,让信号量+1
dispatch_semaphore_signal
// 可使总信号量减1,信号总量小于0时会一直等待(阻塞当前线程),否则就可以正常执行
dispatch_semaphore_waite
- GCD队列组:dispatch_group
// 创建队列组
dispatch_group_t * group = dispatch_group_create();
// 把任务放在队列组
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
//任务一....
});
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
// 任务二....
});
// 执行完前两个异步任务后的两种操作:
dispatch_group_notify(group,dispatch_get_main_queue(),^{
//任务三
});
// 当前两个异步任务完成后,把任务三添加到group中
// 此函数会阻塞当前线程,等前两个异步任务完成后才可解除阻塞
dispatch_group_wait (group,DISPATCH_TIME_FOREVER);
- GCD一次性代码(只执行一次,单例):dispatch_once
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行 1 次的代码(这里面默认是线程安全的)
});
- GCD延时执行方法:dispatch_after
- GCD栅栏方法:dispatch_barrier_async
- 栅栏函数其实是作为堵塞队列而存在的(同步函数是作为堵塞线程存在的)
- 堵塞队列意思就是在栅栏函数之前的任务要全部执行完才开始执行栅栏函数
- 并且栅栏函数执行完后才执行栅栏函数之后的任务
- 但是栅栏函数不能用于全局并发队列,因为全局并发队列系统也在用,假如用栅栏函数将全局并发队列堵塞住了,会导致系统运行不正常而导致崩溃
// dispatch_barrier_async 方法会等待前边追加到并发队列中的任务全部执行完毕后,再将指定的任务追加到该barrier异步队列中。等该barrier任务完成后,异步队列才恢复为正常执行后边的任务。
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("dezi.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:3]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier栅栏函数
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
// 打印
test[4592:1385282] 2---<NSThread: 0x600002ce7100>{number = 6, name = (null)}
test[4592:1385288] 1---<NSThread: 0x600002cd45c0>{number = 5, name = (null)}
test[4592:1385288] barrier---<NSThread: 0x600002cd45c0>{number = 5, name = (null)}
test[4592:1385282] 3---<NSThread: 0x600002ce7100>{number = 6, name = (null)}
test[4592:1385288] 4---<NSThread: 0x600002cd45c0>{number = 5, name = (null)}
10. 对二叉树是否了解?
由n (n>=0)个结点所构成的集合,它可以为空树(n=0)。
对于⾮空树T:有且仅有⼀个称之为根结点,除了根结点以外的其余结点分为2个互不相交的⼦集T1、T2。分别称为T的左⼦树和右⼦树,且T1和T2本身 都是⼆叉树。
详细介绍可参考这篇文章:二叉树。
二面
1. ARC和MRC的区别,iOS是如何管理引用计数的,什么情况下引用计数加1,什么情况下引用计数减1?
内存管理相关可参考 iOS内存管理
MRC:开发者手动地进行retain和release操作,对每个对象的retainCount进行+1、-1操作,当retainCount为0时,系统会自动释放对象内存。
ARC:开发者通过声明对象的属性为strong,weak,assign来管理对象的引用计数,系统会自动对所修饰变量的引用计数进行自增自减操作,同样地,retainCount为0时,系统会释放对象内存。
alloc时候retainCount默认加1。
2. 在MRC下执行[object autorelease]会发生什么,autorelease是如何实现的?自动释放池中的autorelease对象是什么时候销毁的?
autorelease
是iOS开发的一种内存管理机制,调用autorelease
会将该对象添加进自动释放池中,它会在一个恰当的时刻自动给对象调用release
,所以autorelease
相当于延迟了对象的释放。。
- 对象调用
autorelease
时,会将对象压栈到AutoreleasePoolPage
中。- 进作用域空间调用
objc_autoreleasePoolPush
压栈,出作用域空间调用objc_autoreleasePoolPop
向栈中对象发送release
释放来出栈@autoreleasepool
与线程关联,一个@autoreleasepool
对应一个线程@autoreleasepool
嵌套只会创建一个page,但会有两个哨兵(POOL_BOUNDARY
)
AppKit 和 UIKit 框架在事件循环(RunLoop)的每次循环开始时,在主线程创建一个自动释放池,并在每次循环结束时销毁它,在销毁时释放自动释放池中的所有autorelease对象。
3. CoreAnimation是如何绘制图像的,动画过程中的frame能否获取到?CALayer与UIView的关系?
图像绘制是按图层数一层一层绘制的。CoreAnimation在
Runloop
中注册一个Observer
监听视图变化的事件,有需要时会进行更新。
动画过程中的
frame
是无法获取到的,只能知道起始和终点位置信息。
CALayer与UIView的关系
- UIView本身不具备显示的功能,CALayer才有显示功能。当UIView需要显示到屏幕上时,会调用
drawRect:
方法进行绘图,并且会将所有内容绘制在自己的图层上,绘图完毕后, 系统会将图层拷贝到屏幕上,于是就完成了UIView的显示。- 对比CALayer,UIView多了一个事件处理的功能。也就是说,CALayer不能处理用户的触摸事件,而UIView可以。
- 如果显示出来的东西需要跟用户进行交互的话,用UIView;如果不需要跟用户进行交互,用UIView或者CALayer都可以。当然,CALayer的性能会高一些,因为它少了事件处理的功能,更加轻量级。
4. 谈一下Runloop的了解
详细分析可参考 Runloop
- Runloop实际上就是一个do...while循环,有任务时执行,无任务时休眠。
- 每条线程都有唯一的一个与之对应的RunLoop对象
RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value。- 主线程的RunLoop会在App运行的时自动运行,子线程需要手动获取运行,第一次获取时,才会去创建。
Runloop的作用:保持程序的持续运行,处理APP中的各种事件(触摸、定时器、performSelector),节省cpu资源、提供程序的性能,该做事就做事,该休息就休息。
Runloop Mode的主要Mode
- NSDefaultRunLoopMode:默认的运行模式,除了NSConnection对象的事件。
- NSRunLoopCommonModes:是一组常用的模式集合,将一个input source关联到这个模式集合上,等于将input source关联到这个模式集合中的所有模式上。在iOS系统中NSRunLoopCommonModes包含NSDefaultRunLoopMode、NSTaskDeathCheckMode、UITrackingRunLoopMode。
- UITrackingRunLoopMode:用于跟踪触摸事件触发的模式(例如UIScrollView上下滚动), 主线程当触摸事件触发会设置为这个模式,可以用来在控件事件触发过程中设置Timer。
- GSEventReceiveRunLoopMode:用于接受系统事件,属于内部的RunLoop模式。
- 自定义Mode:可以设置自定义的运行模式Mode,你也可以用CFRunLoopAddCommonMode添加到NSRUnLoopCommonModes中。
Run Loop 运行时只能以一种固定的模式运行,如果我们需要它切换模式,只有停掉它,再重新开其它。
运行时它只会监控这个模式下添加的Timer Source和Input Source,如果这个模式下没有相应的事件源,RunLoop的运行也会立刻返回的。
5. OC如何实现多继承?
- 消息的转发
- delegate和protocol
- 分类Category
6. 对设计模式有什么了解,讲一下其中一种是如何是用的?
MVC:通过数据模型,控制器逻辑,视图展示将应用程序进行逻辑划分。
MVVM:它其实是一个MVC的增强版,并将逻辑从Controller移出放到一个新的对象里,即View Model在iOS上使用MVVM的动机,就是让它能减少ViewController的复杂性并使得表示逻辑更易于测试。
单例:确保程序运行期某个类,只有一份实例,用于进行资源共享控制。
确保使用者只能通过getInstance方法才能获取,保证单例类的唯一性。重写allocWithZone方法,保证即使用户用alloc方法直接创建单例类的实例,返回的也只是此单例类的唯一静态变量。
Notification:一般为model层对,controller和view进行的通知方式,不关心谁去接收,只负责发布信息。
通知
代理:当一个类的某些功能需要由别的类来实现,但是又不确定具体是哪个类实现。
7. 有没有哪个开源库让你用得很舒服,讲一下让你舒服的地方。
** MJExtension+FMDB+JKDBModel**
SDWebImage 的图片加载过程:
- 查看缓存
- 缓存命中
- 返回图片
- 更新 UIImageView
- 缓存未命中
- 异步下载图片
- 加入缓存
- 更新 UIImageView
8. 一张100*100 RGBA的png图像,解压之后占多大内存空间
套用公式,RGBA是32位
100*100*32/8 = 40000(字节)
40000/1024 = 39KB
9. 给定一个数组arr,判断数组arr中是否所有的数字都只出现过一次。
我的想法是,取第一个数从下一位开始遍历,有return true,没有则取第二位从第三位开始遍历,以此类推,遍历到倒数第二位。
10. Swift和OC的区别,优缺点?
- Swift容易阅读,语法和文件结构简易化。
- Swift更易于维护,文件分离后结构更清晰。
- Swift更加安全,它是类型安全的语言。
- Swift代码更少,简洁的语法,可以省去大量冗余代码
- Swift速度更快,运算性能更高
但是版本不够稳定,更新迭代还会有
11. Category(类别/分类)和Extension(扩展)的区别:
Category
- 是运行期决议的
- 可以在不修改原来类的基础上,为一个类扩展方法。如:给系统自带的类扩展方法。
- 分类不能添加实例变量,但类扩展可以
- 原因:因为在运行期,对象的内存布局已经确定,如果添加实例变量会破坏类的内部布局,这对编译性语言是灾难性的。
Extension
- 在编译期决议,是类的一部分
- 伴随着类的产生而产生,也随着类的消失而消失。
- 类扩展即可以声明成员变量又可以声明方法
- Extension一般用来隐藏类的私有消息,你必须有一个类的源码才能添加一个类的Extension,所以对于系统一些类,如NSString,就无法添加类扩展
12. 为什么离职,有什么职业规划。
个人发挥。
网友评论