iOS面试题整理笔记

OC语法篇

面向对象

1. 一个NSObject对象占用多少内存?

系统分配了16个字节给NSobject对象(通过malloc_size函数获得),但NSobject对象内部只是用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)

2. 对象的isa指针指向哪里?

instance对象的isa指针指向class对象;class对象的isa指针指向meta-class对象;meta-class对象的isa指针指向基类的meta-class对象

3. OC的类信息存放在哪里?

对象方法、属性、成员变量、协议信息,存放在class对象中;类方法存放在meta-class对象中;成员变量的具体值,存放在instance对象中。
OC对象分为三种对象
instance对象:实例对象(包含isa指针、成员变量)
class对象:类对象(包含isa指针、superclass、属性、对象方法、协议、成员变量···)
meta-class对象:元类对象(包含isa指针、superclass、类方法)

4. isa、superclass总结

instance的isa指向class
class的isa指向meta-class
meta-class的isa指向基类的meta-class

class的superclass指向父类的class
如果没有父类,superclass指针为nil
meta-class的superclass指向父类的meta-class
基类的meta-class的superclass指向基类的class

instance调用对象方法的轨迹
isa找到class,方法不存在,就通过superclass找父类

class调用类方法的轨迹
isa找meta-class,方法不存在,就通过superclass找父类

KVO and KVC

1. iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)

利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:
内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)

2. 如何手动触发KVO?

手动调用willChangeValueForKey:和didChangeValueForKey:

3. 通过KVC修改属性会触发KVO么?

会触发KVO

4. KVC的赋值和取值过程是怎样的?原理是什么?

KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性
常见的API有
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
赋值过程:
按照setKey:、_setKey顺序查找方法,找到了方法传递参数,调用方法;若没找到,查看accessInstanceVariablesDirectly方法的返回值,若为NO则调用setValue:forUndefinedKey:并抛出异常NSUnknownKeyException,若返回YES则按照_key、_isKey、key、isKey顺序查找成员变量,找到直接赋值,找不到则调用setValue:forUndefinedKey:并抛出异常NSUnknownKeyException
取值过程
按照getKey:、key、_isKey、key顺序查找方法,找到了方法,调用方法;若没找到,查看accessInstanceVariablesDirectly方法的返回值,若为NO则调用valueForUndefinedKey:并抛出异常NSUnknownKeyException,若返回YES则按照_key、_isKey、key、isKey顺序查找成员变量,找到直接取值,找不到则调用valueForUndefinedKey:并抛出异常NSUnknownKeyException

Category

1. Category的使用场合是什么?
2. Category的实现原理

Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

3. Category和Class Extension的区别是什么?

Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中

4. Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?

有load方法
load方法在runtime加载类、分类的时候调用
load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用

5. load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?

+load方法会在runtime加载类、分类时调用
每个类、分类的+load,在程序运行过程中只调用一次
调用顺序:先调用类的+load,按照编译先后顺序调用(先编译,先调用),调用子类的+load之前会先调用父类的+load;再调用分类的+load,按照编译先后顺序调用(先编译,先调用)

+initialize方法会在类第一次接收到消息时调用,调用顺序:先调用父类的+initialize,再调用子类的+initialize,(先初始化父类,再初始化子类,每个类只会初始化1次)
+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点:如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次);如果分类实现了+initialize,就覆盖类本身的+initialize调用

6. Category能否添加成员变量?如果可以,如何给Category添加成员变量?

不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果
添加关联对象: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)

block

1. block的原理是怎样的?本质是什么?

block本质上也是一个OC对象,它内部也有个isa指针,封装了函数调用以及调用环境的OC对象

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
         __NSGlobalBlock__(_NSConcreteGlobalBlock):存储在数据区,没有访问auto变量
         __NSStackBlock__(_NSConcreteStackBlock):存储在栈区,访问auto变量
         __NSMallocBlock__(_NSConcreteMallocBlock):存储在堆区,__NSStackBlock__调用了copy
2. __block的作用是什么?有什么使用注意点?

__block可以用于解决block内部无法修改auto变量值的问题
__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象

3. block的属性修饰词为什么是copy?使用block有哪些使用注意?

block一旦没有进行copy操作,就不会在堆上
使用注意:循环引用问题
__weak typeof(self)weakSelf = self;
__unsafe _unretained id weakSelf = self;
__block id weakSelf = self;

Runtime

1. 讲一下 OC 的消息机制

OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有3大阶段
消息发送(当前类、父类中查找)、动态方法解析、消息转发

2. 什么是Runtime?平时项目中有用过么?

OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
平时编写的OC代码,底层都是转换成了Runtime API进行调用

具体应用
利用关联对象(AssociatedObject)给分类添加属性;遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档); 交换方法实现(交换系统的方法);利用消息转发机制解决方法找不到的异常问题

RunLoop

1. 讲讲 RunLoop,项目中有用到吗?

运行循环,在程序运行过程中循环做一些事情
应用范畴:定时器(Timer)、PerformSelector;GCD Async Main Queue;事件响应、手势识别、界面刷新;网络请求;AutoreleasePool
RunLoop的基本作用:保持程序的持续运行;处理App中的各种事件(比如触摸事件、定时器事件等);节省CPU资源,提高程序性能:该做事时做事,该休息时休息

2. runloop内部实现逻辑?
3. runloop和线程的关系?

每条线程都有唯一的一个与之对应的RunLoop对象
RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
RunLoop会在线程结束时销毁
主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

4. timer 与 runloop 的关系?
5. 程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?
6. runloop 是怎么响应用户操作的, 具体流程是什么样的?
7. 说说runLoop的几种状态

eg:添加Observer监听RunLoop的所有状态

CFRunLoopObserverRef observe = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, yearMask, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch (activity) {
                case kCFRunLoopEntry:
                    NSLog(@"kCFRunLoopEntry");//即将进入Loop
                    break;
                case kCFRunLoopBeforeTimers:
                    NSLog(@"kCFRunLoopBeforeTimers");//即将处理Timers
                    break;
                case kCFRunLoopBeforeSources:
                    NSLog(@"kCFRunLoopBeforeSources");//即将处理Sources
                    break;
                case kCFRunLoopBeforeWaiting:
                    NSLog(@"kCFRunLoopBeforeWaiting");//即将进入休眠
                    break;
                case kCFRunLoopAfterWaiting:
                    NSLog(@"kCFRunLoopAfterWaiting");//刚从休眠中唤醒
                    break;
                case kCFRunLoopExit:
                    NSLog(@"kCFRunLoopExit");//即将退出Loop
                    break;
                default:
                    break;
            }
        });
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observe, kCFRunLoopCommonModes);
        CFRelease(observe);
8. runloop的mode作用是什么?

CFRunLoopModeRef代表RunLoop的运行模式
一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
RunLoop启动时只能选择其中一个Mode,作为currentMode
如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
常见的2种Mode
kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响

多线程

1. iOS中的线程同步方案

性能从高到低排序

OSSpinLock

OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源
目前已经不再安全,可能会出现优先级反转问题
如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁
需要导入头文件#import <libkern/OSAtomic.h>

//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//尝试加锁(如果需要等待就不加锁,直接返回false;如果不需要等待就加锁,返回true)
bool result = OSSpinLockTry(&lock);
//加锁
OSSpinLockLock(&lock);
//解锁
OSSpinLockUnLock(&lock);
os_unfair_lock

os_unfair_lock
os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持
从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
需要导入头文件#import <os/lock.h>

//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//尝试加锁
os_unfair_lock_trylock(&lock);
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);
pthread_mutex

mutex叫做”互斥锁”,等待锁的线程会处于休眠状态
需要导入头文件#import <pthread.h>

dispatch_semaphore:

semaphore叫做”信号量”
信号量的初始值,可以用来控制线程并发访问的最大数量
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步

//信号量的初始值
        int value = 1;
        //初始化信号量
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
        //如果信号量的值<=0,当前线程就会进入休眠等待(直到信号量的值>0)
        //如果信号量的值>0,就减1,然后往下执行后面的代码
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        //让信号量的值加1
        dispatch_semaphore_signal(semaphore);
dispatch_queue(DISPATCH_QUEUE_SERIAL)

直接使用GCD的串行队列,也是可以实现线程同步的

dispatch_queue_t queue = dispatch_queue_create("lock_queue", DISPATCH_QUEUE_SERIAL);
        dispatch_sync(queue, ^{
            //任务
        });
NSLock:对mutex普通锁的封装

NSRecursiveLock

NSCondition:对mutex和cond的封装
NSConditionLock:对NSCondition的进一步封装,可以设置具体的条件值
@synchronized:是对mutex递归锁的封装
什么情况使用自旋锁比较划算?

预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张
多核处理器

什么情况使用互斥锁比较划算?

预计线程等待锁的时间较长
单核处理器
临界区有IO操作
临界区代码复杂或者循环量大
临界区竞争非常激烈

内存管理

1. 使用CADisplayLink、NSTimer有什么注意点?

CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用
解决方案
使用block 弱引用weakSelf
使用代理对象(NSProxy)
NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时,而GCD的定时器会更加准时

//创建一个定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
//设置时间(start是几秒后开始执行,interval是时间间隔)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, (int64_t)(start * NSEC_PER_SEC)), (uint64_t)(interval ) * NSEC_PER_SEC);
//设置回调
dispatch_source_set_event_handler(timer, ^{
});
//启动定时器
dispatch_resume(timer);
2. 介绍下内存的几大区域

代码段:编译之后的代码

数据段
字符串常量:比如NSString *str = @"123"
已初始化数据:已初始化的全局变量、静态变量等
未初始化数据:未初始化的全局变量、静态变量等

栈:函数调用开销,比如局部变量。分配的内存空间地址越来越小

堆:通过alloc、malloc、calloc等动态分配的空间,分配的内存空间地址越来越大

3. 讲一下你对 iOS 内存管理的理解

在iOS中,使用引用计数来管理OC对象的内存
一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1

内存管理的经验总结
当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1

可以通过以下私有函数来查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void);

性能优化

1. 列表卡顿的原因可能有哪些?你平时是怎么优化的?

CPU

  • 尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView
  • 不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改
  • 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
  • Autolayout会比直接设置frame消耗更多的CPU资源
  • 图片的size最好刚好跟UIImageView的size保持一致
  • 控制一下线程的最大并发数量
  • 尽量把耗时的操作放到子线程: 文本处理(尺寸计算、绘制);图片处理(解码、绘制)

GPU

  • 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
  • GPU能处理的最大纹理尺寸是4096x4096,一旦超过这个尺寸,就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸
  • 尽量减少视图数量和层次
  • 减少透明的视图(alpha<1),不透明的就设置opaque为YES
  • 尽量避免出现离屏渲染
2. 什么是离屏渲染?

在OpenGL中,GPU有2种渲染方式
On-Screen Rendering:当前屏幕渲染,在当前用于显示的屏幕缓冲区进行渲染操作
Off-Screen Rendering:离屏渲染,在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作

3. 离屏渲染消耗性能的原因

需要创建新的缓冲区
离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕

4. 哪些操作会触发离屏渲染?
  • 光栅化,layer.shouldRasterize = YES
  • 遮罩,layer.mask
  • 圆角,同时设置layer.masksToBounds = YES、layer.cornerRadius大于0(考虑通过CoreGraphics绘制裁剪圆角,或者叫美工提供圆角图片)
  • 阴影,layer.shadowXXX(如果设置了layer.shadowPath就不会产生离屏渲染)
5. 怎么检测卡顿?

平时所说的“卡顿”主要是因为在主线程执行了比较耗时的操作
可以添加Observer到主线程RunLoop中,通过监听RunLoop状态切换的耗时,以达到监控卡顿的目的

暂时先整理这些,如果有错误,感谢各位大佬指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342