[转]那些著名或非著名的iOS面试题(下)

1. Runtime

Objective-C 是面相运行时的语言(runtime oriented language),就是说它会尽可能的把编译和链接时要执行的逻辑延迟到运行时。这就给了你很大的灵活性,你可以按需要把消息重定向给合适的对象,你甚 至可以交换方法的实现,等等。

RunTime简称运行时。就是系统在运行的时候的一些机制,其中最主要的是消息机制。OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。

以下面的代码为例:


1

[obj makeText];

其中obj是一个对象,makeText是一个函数名称。对于这样一个简单的调用。在编译时RunTime会将上述代码转化成


1

objc_msgSend(obj,@selector(makeText));

首先,编译器将代码[obj makeText];转化为objc_msgSend(obj, @selector (makeText));,在objc_msgSend函数中。首先通过obj的isa指针找到obj对应的class。在Class中先去cache中 通过SEL查找对应函数method(猜测cache中method列表是以SEL为key通过hash表来存储的,这样能提高函数查找速度),若 cache中未找到。再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加 入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。

Objective-C Runtime 是什么?

Objective-C 的 Runtime 是一个运行时库(Runtime Library),它是一个主要使用 C 和汇编写的库,为 C 添加了面相对象的能力并创造了 Objective-C。这就是说它在类信息(Class information) 中被加载,完成所有的方法分发,方法转发,等等。Objective-C runtime 创建了所有需要的结构体,让 Objective-C 的面相对象编程变为可能。

Method Swizzling 原理

在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。

我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,

我们可以利用 class_replaceMethod 来修改类,

我们可以利用 method_setImplementation 来直接设置某个方法的IMP,……

归根结底,都是偷换了selector的IMP。

2. GCD实现1,2并行和3串行和45串行,4,5是并行。即3依赖1,2的执行,45依赖3的执行。

关系

队列组的方式


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

- (void) methodone{

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    NSLog(@"%d",1);

});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    NSLog(@"%d",2);

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

    NSLog(@"3");

    dispatch_group_t group1 = dispatch_group_create();

    dispatch_group_async(group1, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSLog(@"%d",4);

    });

    dispatch_group_async(group1, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSLog(@"%d",5);

    });

});

}

串行队列:队列中的任务只会顺序执行


1

dispatch_queue_t q = dispatch_queue_create(“....”, dispatch_queue_serial);

并行队列: 队列中的任务通常会并发执行。


1

dispatch_queue_t q = dispatch_queue_create("......", dispatch_queue_concurrent);

全局队列:是系统开发的,直接拿过来用就可以;与并行队列类似,但调试时,无法确认操作所在队列 。


1

dispatch_queue_t q = dispatch_get_global_queue(dispatch_queue_priority_default, 0);

主队列:每一个应用开发程序对应唯一一个主队列,直接get即可;在多线程开发中,使用主队列更新UI。


1

dispatch_queue_t q = dispatch_get_main_queue();

主队列是GCD自带的串行队列,会在主线程中执行。异步全局并发队列 开启新线程,并发执行。

并行队列里开启同步任务是有执行顺序的,只有异步才没有顺序。

串行队列开启异步任务,是有顺序的。

串行队列开启异步任务后嵌套同步任务造成死锁。

3. 深浅复制和属性为copy,strong值的变化问题

浅复制:只复制指向对象的指针,而不复制引用对象本身。对于浅复制来说,A和A_copy指向的是同一个内存资源,复制的只不个是一个指针,对象本身资源还是只有一份,那如果我们对A_copy执行了修改操作,那么发现A引用的对象同样被修改了。深复制就好理解了,内存中存在了两份独立对象本身。

在Objective-C中并不是所有的对象都支持Copy,MutableCopy,遵守NSCopying协议的类才可以发送Copy消息,遵守NSMutableCopying协议的类才可以发送MutableCopy消息。


1

2

3

4

[immutableObject copy] // 浅拷贝

[immutableObject mutableCopy] //深拷贝

[mutableObject copy] //深拷贝

[mutableObject mutableCopy] //深拷贝

属性设为copy,指定此属性的值不可更改,防止可变字符串更改自身的值的时候不会影响到对象属性(如NSString,NSArray,NSDictionary)的值。strong此属性的指会随着变化而变化。copy是内容拷贝,strong是指针拷贝。

4.NSTimer创建后,会在哪个线程运行。

用scheduledTimerWithTimeInterval创建的,在哪个线程创建就会被加入哪个线程的RunLoop中就运行在哪个线程。

自己创建的Timer,加入到哪个线程的RunLoop中就运行在哪个线程。

5. KVO,NSNotification,delegate及block区别

KVO就是cocoa框架实现的观察者模式,一般同KVC搭配使用,通过KVO可以监测一个值的变化,比如View的高度变化。是一对多的关系,一个值的变化会通知所有的观察者。

NSNotification是通知,也是一对多的使用场景。在某些情况下,KVO和NSNotification是一样的,都是状态变化之后告知对方。NSNotification的特点,就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比KVO多了发送通知的一步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。

delegate 是代理,就是我不想做的事情交给别人做。比如狗需要吃饭,就通过delegate通知主人,主人就会给他做饭、盛饭、倒水,这些操作,这些狗都不需要关心,只需要调用delegate(代理人)就可以了,由其他类完成所需要的操作。所以delegate是一对一关系。

block是delegate的另一种形式,是函数式编程的一种形式。使用场景跟delegate一样,相比delegate更灵活,而且代理的实现更直观。

KVO一般的使用场景是数据,需求是数据变化,比如股票价格变化,我们一般使用KVO(观察者模式)。delegate一般的使用场景是行为,需求是需要别人帮我做一件事情,比如买卖股票,我们一般使用delegate。Notification一般是进行全局通知,比如利好消息一出,通知大家去买入。delegate是强关联,就是委托和代理双方互相知道,你委托别人买股票你就需要知道经纪人,经纪人也不要知道自己的顾客。Notification是弱关联,利好消息发出,你不需要知道是谁发的也可以做出相应的反应,同理发消息的人也不需要知道接收的人也可以正常发出消息。

6. 如何让计时器调用一个类方法

计时器只能调用实例方法,但是可以在这个实例方法里面调用静态方法。

使用计时器需要注意,计时器一定要加入RunLoop中,并且选好model才能运行。scheduledTimerWithTimeInterval方法创建一个计时器并加入到RunLoop中所以可以直接使用。

如果计时器的repeats选择YES说明这个计时器会重复执行,一定要在合适的时机调用计时器的invalid。不能在dealloc中调用,因为一旦设置为repeats 为yes,计时器会强持有self,导致dealloc永远不会被调用,这个类就永远无法被释放。比如可以在viewDidDisappear中调用,这样当类需要被回收的时候就可以正常进入dealloc中了。

7. 调用一个类的静态方法需不需要release?

静态方法,就是类方法,不需要,类方法对象放在autorelease中

8. static作用?

(1)函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;

(2)在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;

(3)在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明

它的模块内;

(4)在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;

(5)在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。

9. NSObject的load和initialize方法

load和initialize的共同特点:

在不考虑开发者主动使用的情况下,系统最多会调用一次

如果父类和子类都被调用,父类的调用一定在子类之前

都是为了应用运行提前创建合适的运行环境

在使用时都不要过重地依赖于这两个方法,除非真正必要

load和initialize的区别:

  • load方法

调用时机比较早,运行环境有不确定因素。具体说来,在iOS上通常就是App启动时进行加载,但当load调用的时候,并不能保证所有类都加载完成且可用,必要时还要自己负责做auto release处理。对于有依赖关系的两个库中,被依赖的类的load会优先调用。但在一个库之内,调用顺序是不确定的。

对于一个类而言,没有load方法实现就不会调用,不会考虑对NSObject的继承。

一个类的load方法不用写明[super load],父类就会收到调用,并且在子类之前。

Category的load也会收到调用,但顺序上在主类的load调用之后。

不会直接触发initialize的调用。

  • initialize方法相关要点

initialize的自然调用是在第一次主动使用当前类的时候。

在initialize方法收到调用时,运行环境基本健全。

initialize的运行过程中是能保证线程安全的。

和load不同,即使子类不实现initialize方法,会把父类的实现继承过来调用一遍。注意的是在此之前,父类的方法已经被执行过一次了,同样不需要super调用。

由于initialize的这些特点,使得其应用比load要略微广泛一些。可用来做一些初始化工作,或者单例模式的一种实现方案。

10. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

不能向编译后得到的类中增加实例变量;

能向运行时创建的类中添加实例变量;

因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表 和 instance_size 实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量;

运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。

时间: 2024-10-08 18:09:41

[转]那些著名或非著名的iOS面试题(下)的相关文章

那些著名或非著名的iOS面试题-前编

1.如何追踪app崩溃率,如何解决线上闪退 当iOS设备上的App应用闪退时,操作系统会生成一个crash日志,保存在设备上.crash日志上有很多有用的信息,比如每个正在执行线程的完整堆栈跟踪信息和内存映像,这样就能够通过解析这些信息进而定位crash发生时的代码逻辑,从而找到App闪退的原因.通常来说,crash产生来源于两种问题:违反iOS系统规则导致的crash和App代码逻辑BUG导致的crash,下面分别对他们进行分析. 违反iOS系统规则产生crash的三种类型 (1) 内存报警闪

那些著名或非著名的iOS面试题(上)

1.如何追踪app崩溃率,如何解决线上闪退 当iOS设备上的App应用闪退时,操作系统会生成一个crash日志,保存在设备上.crash日志上有很多有用的信息,比如每个正在执行线程的完整堆栈跟踪信息和内存映像,这样就能够通过解析这些信息进而定位crash发生时的代码逻辑,从而找到App闪退的原因.通常来说,crash产生来源于两种问题:违反iOS系统规则导致的crash和App代码逻辑BUG导致的crash,下面分别对他们进行分析. 违反iOS系统规则产生crash的三种类型: (1) 内存报警

那些著名或非著名的iOS面试题-后编

转自:http://www.jianshu.com/p/5178204a58d6?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io 1. Runtime Objective-C 是面相运行时的语言(runtime oriented language),就是说它会尽可能的把编译和链接时要执行的逻辑延迟到运行时.这就给了你很大的灵活性,你可以按需要把消息重定向给合适的对象,你甚 至可以交换方法的实现,等等. RunTime简

如何运营非著名IP?阴阳师与大圣归来背后有个成功密码

2016年以前,常常有做游戏的朋友向我"吐槽":"买不到好IP(具有知识产权的原创作品),太贵了!" 2016年以后,这些朋友则长吁一口气地告诉我:"还好没有买到好IP,不然亏得更厉害." 文/张书乐 原载于<人民邮电报>2017年1月6日<乐游记>专栏128期 IP情怀的落败,似乎是2016年游戏.影视以及其他泛娱乐领域的常态.这样的问题能解决吗? 源自IP的手机游戏<阴阳师>异常火爆,其实给出了一个答案.但

iOS: ARC &amp; MRC下string内存管理策略探究

ARC & MRC下string内存管理策略探究 前两天跟同事争论一个关于NSString执行copy操作以后是否会发生变化,两个人整了半天,最后写代码验证了一下,发现原来NSString操作没我们想的那么简单,下面就让我们一起看看NSString和NSMutableString在MRC下执行retain,copy,mutableCopy,以及ARC下不同的修饰__weak, __strong修饰赋值究竟发生了什么. 一.验证代码如下: - (void)testStringAddress { i

最全的iOS面试题及答案-转载

1. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么? 答: Object-c的类不可以多重继承:可以实现多个接口,通过实现多个接口可以完成C++的多重继承:Category是类别,一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系. 2. #import 跟#include 又什么区别,@class呢, #import<> 跟 #import””又什么区别?

iOS面试题 第一天

今天上午,下午分别面试了两家公司.上午是一家互联网公司,气氛还比较好,是我比较喜欢的.技术这块是直接机试,主要是给了些BUG让我修复,整个过程还算顺利.下午去了一家大型的证券公司.整理技术问题如下: 1. UIView的生命周期是什么样的,执行顺序是怎么样的?init -- loadView -- viewDidLoad -- viewWillAppear -- viewWillDisappear -- viewDidUnload -- dealloc. 2. UIViewController在

IOS-4-面试题1:黑马程序猿IOS面试题大全

一.多线程网络 1. 多线程的底层实现? 1> 首先搞清楚什么是线程.什么是多线程 2> Mach是第一个以多线程方式处理任务的系统.因此多线程的底层实现机制是基于Mach的线程 3> 开发中非常少用Mach级的线程,由于Mach级的线程没有提供多线程的基本特征,线程之间是独立的 4> 开发中实现多线程的方案 l C语言的POSIX接口:#include <pthread.h> l OC的NSThread l C语言的GCD接口(性能最好.代码更精简) l OC的NSO

iOS面试题之一

1.进程和线程的区别,说说线程管理. 进程:是活动的程序,是一个容器:是系统资源管理的最小单位:切换代价较高 线程:是在进程容器中运行,实际工作的代码:是程序执行的最小单位:切换代价低 单线程:一个进程内只有一个线程:一个进程的数据通常加载在同一内存中! 多线程:一个进程内有多个线程:多个线程通常共享同一内存中的数据! 线程与进程切换的区别:线程完全共享相同的地址空间,切换代价低:进程的地址空间是独立的,切换代价高 进程与线程之间的区别: 简而言之,一个程序至少有一个进程,一个进程至少有一个线程