iOS程序员面试分类模拟28_第1页
iOS程序员面试分类模拟28_第2页
iOS程序员面试分类模拟28_第3页
iOS程序员面试分类模拟28_第4页
iOS程序员面试分类模拟28_第5页
已阅读5页,还剩21页未读, 继续免费阅读

付费下载

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

iOS程序员面试分类模拟28简答题1.

用户态和核心态有什么区别?正确答案:核心态与用户态是操作系统的两种运行级别,用于区分不同程序的不同权利。核心态就是拥有资源多的状态,或者说访问资源多的状态,(江南博哥)也称之为特权态。相对来说,用户态就是非特权态,在此种状态下访问的资源将受到限制。如果一个程序运行在特权态,则该程序就可以访问计算机的任何资源,即它的资源访问权限不受限制。如果一个程序运行在用户态,则其资源需求将受到各种限制。例如,如果要访问操作系统的内核数据结构,如进程表,则需要在特权态下才能办到。如果要访问用户程序里的数据,则在用户态下就可以了。

IntelCPU提供Ring0~Ring3共4种级别的运行模式。Ring0级别最高,Ring3级别最低。

用户态:Ring3运行于用户态的代码要受到处理器的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TTS)中I/O许可位图(I/OPermissionBitmap)中规定的可访问端口进行直接访问。

核心态:Ring0在处理器的存储保护中,核心态或者特权态是操作系统内核所运行的模式。运行在该模式的代码可以无限制地对系统存储、外部设备进行访问。

当一个任务(进程)执行系统调用而陷入内核代码中执行时,就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(或简称为用户态)。此时处理器在特权级最低的(3级)用户代码中运行。

在核心态下CPU可执行任何指令,在用户态下CPU只能执行非特权指令。当CPU处于核心态时,可以随意进入用户态;而当CPU处于用户态时,用户从用户态切换到核心态只有在系统调用和中断两种情况下发生。一般程序一开始都是运行于用户态,当程序需要使用系统资源时,就必须通过调用软中断进入核心态。

核心态和用户态各有优势:运行在核心态的程序可以访问的资源多,但可靠性、安全性要求高,维护管理都较复杂;用户态程序访问的资源受限,但可靠性、安全性要求低,自然编写维护起来都较简单。一个程序到底应该运行在核心态还是用户态取决于其对资源和效率的需求。

那么什么样的功能应该在核心态下实现呢?

首先,CPU管理和内存管理都应该在核心态实现。这些功能可不可以在用户态下实现呢?当然能,但是不太安全。就像一个国家的军队(CPU和内存在计算机里的地位就相当于一个国家军队的地位)交给老百姓来管一样,是非常危险的。因此,从保障计算机安全的角度来说,CPU和内存的管理必须在核心态下实现。

诊断与测试程序也需要在核心态下实现,因为诊断和测试需要访问计算机的所有资源。输入输出管理也一样,因为要访问各种设备和底层数据结构,也必须在核心态下实现。

对于文件系统来说,可以一部分放在用户态,另一部分放在核心态。文件系统本身的管理,即文件系统的宏数据部分的管理,必须放在核心态,不然任何人都可能破坏文件系统的结构:而用户数据的管理,可以放在用户态。编译器、网络管理的部分功能和编辑器用户程序都可以放在用户态下执行。

2.

什么是“懒加载”?正确答案:“懒加载”(Lazyloading)也被叫作“延迟加载”,它的核心思想是把对象的实例化尽量延迟,直到真正用到的时候才将其实例化,这样做的好处是可以减轻大量对象在实例化时对资源的消耗,而不是在程序初始化的时候就预先将对象实例化。另外,“懒加载”可以将对象的实例化代码从初始化方法中独立出来,从而提高代码的可读性,以便于代码能够更好地被组织。

最典型的一个应用“懒加载”的例子是在对象的getter方法中实例化对象的时候。例如getter方法被重写,使得在第一次调用getter方法时才实例化对象并将实例化的对象返回。判断是否是第一次调用getter方法可以通过判断对象是否为空来实现。“懒加载”的getter方法的实现模板如下:

/*getter*/

-(NSObject*)object{

if(!_bject){

_object=[[NSObjectalloc]init];

}

return_object;

}

这种实现方法的缺点是使得getter方法产生副作用,也就是破坏了getter方法的纯洁性。因为按照约定和习惯,getter方法就是作为接口简单地将需要的实例对象返回给外部,这里对getter方法的第一次调用添加了懒加载模式,在使用者不知情的情况下会有潜在的隐患。

3.

内存管理有哪几种方式?正确答案:常见的内存管理方式有块式管理、页式管理、段式管理和段页式管理。最常用的是段页式管理。

(1)块式管理把主存分为一大块一大块的,当所需的程序片断不在主存时就分配一块主存空间,把程序片断载入主存,就算所需的程序片段只有几个字节也只能把这一块分配给它。这样会造成很大的浪费,平均浪费了50%的内存空间,但是易于管理。

(2)页式管理用户程序的地址空间被划分成若干个固定大小的区域,这个区域被称为“页”。相应地,内存空间也被划分为若干个物理块,页和块的大小相等。可将用户程序的任意一页放在内存的任意一块中,从而实现了离散分配。这种方式的优点是页的大小是固定的,因此便于管理;缺点是页长与程序的逻辑大小没有任何关系。这就导致在某个时刻一个程序可能只有一部分在主存中,而另一部分在辅存中。这不利于编程时的独立性,并给换入换出处理、存储保护和存储共享等操作造成麻烦。

(3)段式管理段是按照程序的自然分界划分的并且长度可以动态改变的区域。使用这种方式,程序员可以把子程序、操作数和不同类型的数据和函数划分到不同的段中。这种方式将用户程序地址空间分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息。在存储分配时,以段为单位,段与段在内存中可以不相邻接,也实现了离散分配。

分页对程序员而言是不可见的,而分段通常对程序员而言是可见的,因而分段为组织程序和数据提供了方便,但是对程序员的要求也比较高。

分段存储主要有如下优点:

1)段的逻辑独立性不仅使其易于编译、管理、修改和保护,还便于多道程序共享。

2)段长可以根据需要动态改变,允许自由调度,以便有效利用主存空间。

3)方便分段共享,分段保护,动态链接,动态增长。

分段存储的缺点为:

1)由于段的大小不固定,所以存储管理比较麻烦。

2)会生成段内碎片,这会造成存储空间利用率降低。而且段式存储管理比页式存储管理方式需要更多的硬件支持。

正是由于页式管理和段式管理都有各种各样的缺点,所以,为了把这两种存储方式的优点结合起来,新引入了段页式管理。

(4)段页式管理段页式存储组织是分段式和分页式结合的存储组织方法,这样可充分利用分段管理和分页管理的优点。

1)用分段方法来分配和管理虚拟存储器。程序的地址空间按逻辑单位分成基本独立的段,而每一段有自己的段名,再把每段分成固定大小的若干页。

2)用分页方法来分配和管理内存。即把整个主存分成与上述页大小相等的存储块,可装入作业的任何一页。程序对内存的调入或调出是按页进行的,但它又可按段实现共享和保护。

4.

KVO的背后原理是什么?正确答案:通过前面的问题可以看出KVO的强大,开发者只要在需要的时候注册成为观察者和实现KVO提供的回调方法,就可以获得键值观察的能力,这是很多iOS高级开发技巧的基础,但是系统在中间环节帮开发者做了哪些事情呢?答案就是Objective-C中强大的运行时(runtime)机制。

当某个类的对象第一次被观察时,系统就会在运行时动态地创建对应于该类的一个派生类,在这个派生类中系统会重写父类中被观察属性的setter方法。在重写的setter方法中实现了真正的通知机制,类似下面手动实现键值观察的方式。

-(void)setAge:(NSInteger)age{

[selfwillChangeValueForKey;@"age"];

_age=age;

[selfdidChangeValueForKey:@"age"];

}

+(BOOL)automaticallyNotifiesObserversForKey:(NSString*)key{

if([keyisEqualToString:@"age"]){

returnNO;

}

return[superautomaticallyNotifiesObserversForKey:key];

}

当调用age属性的stter方法时,在属性值改变的前后会分别调用willChangeValueForKey:方法和didChangeValueForKey:方法,这两个方法分别用于通知系统该属性值即将发生变化和已经发生变化。实现了automaticallyNotifiesObserversForKey:方法后,在这个方法中取消了属性变更发送通知的操作。要通过KVO来实现观察者模式,必须遵循KVO的属性设置方式(KVC或者setter方法)来变更属性值,如果仅是直接修改属性对应的成员变量,那么是无法实现KVO的。

当系统在运行时创建派生类的同时,派生类还重写了class方法来“欺骗”开发者,让开发者以为正在调用的类还是原始类。然后系统会将这个对象的isa指针指向新创建的派生类,因此这个对象就成为该派生类的对象了,因而当开发者调用属性的setter方法时,实际上是调用了重写之后的setter方法,从而实现了键值通知机制。此外,派生类还重写了dealloc方法来释放内存资源。

5.

说出你所知道的3种以上开源数据库的名称?正确答案:MySQL、PostgreSQL、Ingresr3、MaxDB、InterBase(即Firebird)等。

针对以上5个主流开源数据库进行简要的介绍(见表)。主流开源数据库的概要介绍数据库概要介绍主要优点MySQLMySQL是一个多线程、结构化查询语言(SQL)数据库服务器。MySQL的执行性能高,运行速度快,容易使用①可靠的性能和服务②易于使用和部署,MySQL的结构体系易于定制,运行速度快,其独特的多存储引擎结构为企业客户提供了灵活性③跨平台多,MySQL可用于20多种不同平台PostgreSQL一个功能齐全、开放源代码的对象一关系型数据库管理系统(ORDBMS)①PostgreSQL包括了丰富的数据类型支持,例如IP类型和几何类型等②PostgreSQL是全功能的开源软件数据库,全面支持事务、子查询、多版本并行控制系统和数据完整性检查等特性③PostgreSQL支持几乎所有类型的数据库客户端接口④支持多平台使用Ingresr3Ingresr3按照CATrustedOpenSourceicense(CA可信开放源代码许可)授权,取得此授权的人可以查看Ingresr3数据库的源代码,并免费下载该软件①具备高可用性、可扩展性和可靠性等②是第一个以ZopeRDBMSPersistence引擎为基础的初始数据库(InitialDatabase),其表分区和索引功能满足超大型数据库部署的需求③可以在异构环境中与其他应用程序和数据进行无缝集成MaxDBMaxDB是一个适应繁重任务、经过SAP认证的开源OLTP数据库,OLTP的使用为其提供了可靠性、可用性、扩展性和高性能①降低企业SAP运行的费用成本②配置简单,管理维护成本低廉③完善的备份和恢复功能④数据库容量可达TB级⑤提供集群和热备份支持,带来高可用性InterBaseInterBase是一个易于开发者使用的数据库,可以支持复杂商业应用的快速开发与部署①占用很少的空间意味着数据库消耗的系统资源很少,能够运行在一个并不昂贵的系统之上②自动崩溃恢复功能、自动崩溃恢复机制的调优功能使得系统维护量很小,并且没有日志文件蔓延(LogFileCreep)现象③在线备份功能进一步降低系统维护量,并提升生产率,因为在备份数据时并不需要停止数据库

6.

iOS应用有哪几种不同状态?分别表示什么含义?正确答案:iOS应用在整个生命周期中会有不同的状态,各状态可能会发生相互的转换,主要状态有非运行状态、不活跃状态、活跃状态、后台状态和挂起状态等。具体含义见表。iOS应用的状态及其含义状态名称含义非运行状态(NotRunningState)应用还没有被运行起来(用户没有单击图标打开应用)或者正在运行的过程中被系统中断了的状态不活跃状态(InactiveState)应用在前台运行但是不接受事件交互(可能在执行一些其他的代码任务但不需要事件交互)。应用通常什么时候处于这种状态哟?例如,在运行某个应用时,用户突然锁屏了,或者突然强制转去处理一些像来电或短信等系统紧急事件。通常认为应用在没有事件处理的情况下都是处于这个状态的活跃状态(ActiveState)应用的正常使用状态,这时候应用在前台运行且接受用户交互事件,是前台应用的正常状态后台状态(BackgroundState)应用被放到后台但还可能执行一些后台代码任务,通常进入到这个后台状态一会儿之后就会进入下面的挂起状态,不再执行代码,也就是应用先进入后台状态然后进入挂起状态挂起状态(SuspendedState)在挂起状态,虽然应用放在内存中,但是不执行任何代码,处于“睡眠”状态。当挂起的应用太多导致内存低时,系统可能会自行清理掉挂起的应用来腾出空间给前台活跃的应用

应用状态转换流程如图所示。

应用状态转换流程

7.

iOS应用的生命周期回调方法主要有哪些?正确答案:iOS应用的生命周期主要的回调方法包括程序开始启动和启动完毕、程序进入前台、程序进入后台、程序进入非活动状态和程序将要终止等,在这些状态转换时系统会以事件的方式通知开发者,以便开发者在需要时进行一些自定义操作。具体的回调方法及其含义见表。iOS应用的生命周期具体的回调方法及其含义回调方法含义-(BOOL)application:(UIApplication*)applicationwillFinishLaunchingWithOptions:(NSDictionary*)launchOptions告诉代理进程开始启动,这里是第一个可以开始执行代码的地方-(BOOL)application:(UIApplication*)applicationdidFinishLaunchingWithOptions:(NSDictionary*)launchOptions告诉代理启动基本完成,程序准备运行,可以进行一些必要的应用初始化操作-(void)applicationWillResignActive:(UIApplication*)application应用程序将要退出前台进入非活动状态,在此期间,应用程序不接收消息或事件,例如来电-(void)applicationDidBecomeActive:(UIApplication*)application当应用程序进入前台活动状态后调用,这个方法的功能正好跟上面那个方法的功能相反-(void)applicationDidEnterBackground:(UIApplication*)application程序开始在后台运行,这里可以设置需要在后台执行的代码,注意当程序在后台运行时随时有可能被系统挂起-(void)applicationWillEnterForeground:(UIApplication*)application当程序从后台将要重新回到前台时候调用,但应用还未处于活跃状态,这个刚好跟上而的那个方法相反-(void)applicationWillTerminate:(UIApplication*)application当程序将要终止时被调用,通常是因为内存不足被系统终止。如果此时应用在挂起状态,则不会调用此方法,一般是用于保存数据和做一些退出前的清理工作

8.

什么是整数运算规则和浮点运算规则?正确答案:整数运算规则指在表达式中的两个运算数都是整型的情况下(不仅仅指int类型,还适用于short、unsigned和long整型),该运算结果中产生的任何小数部分都将被删除,即使该运算结果赋值给一个浮点型变量。相反,浮点运算规则指对于任何处理两个值的运算(由二目中置运算符连接的运算),如果其中有任何一个值是浮点变量或者浮点常量,那么这一运算都将作为浮点运算来处理。实例代码如下:

inti1=3,i2=7;

floatf1,f2;

f1=i2/i1;

f2=i2/f1;

NSLog(@"f1=%f,f2=%f",f1,f2);

以上代码段输出的结果是:

f1=2.000000,f2=3.500000

在实例中,f1的值中没有小数部分是因为程序隐式地执行了整数运算规则,当整数变量i2除以整数变量i1时,系统将该除法作为整数除法来执行。因此,7除以3的结果是2,将2存储到浮点型变量f1中。f2的值包含了小数部分是因为在表达式中f1是浮点型变量,系统自动执行了浮点运算规则,所以,当i2的值除以f1时,保留了结果的小数位,将结果3.5赋值给浮点型变量f2。

9.

NSNumber与NSInteger有什么区别?正确答案:前面讲到Objective-C中的数据类型大体分为3类:基本数据类型、对象类型和id类型。其中,基本数据类型如int类型、char类型等都不是对象,也就是说,在面向对象的编程环境中不能够向它们发送消息。然而,有时需要将这些值作为对象使用。为了存储基本数据类型的需要,可以使用NSNumber类,它会依据这些数据的类型创建对象。其基本构造方法如下:

+numberWithType:

-initWithType:

示例代码如下:

NSNumber*intNumb,*charNumber;

intNumber=[NSNumbernumberWithInteger:100];

charNumber=[NSNumbernumberWithChar:"M"];

如示例所示,NSInteger和NSNumber最本质的区别是:NSInteger不是一个对象,而是基本数据类型的typedef(也就是基本数据类型另外的一个名称)。它实际上是64位的long或者32位的int。如果需要存储一个数值(如将一个int类型的值存入数组),那么直接使用NSInterger是不行的,必须使用NSNumber类将基本数据类型包装成对象。

10.

类别和类扩展如何使用?正确答案:类别和类扩展的区别已经讨论过了,最明显的是类别中不可以添加新属性而类扩展中可以添加属性。类别的使用方法很简单,就是新建某个类的类别扩展文件,然后添加新的方法。而类扩展并不常用,只是常用在.m文件中的头部进行头文件的私有属性变量补充,也就是所谓的类的Continuous区域,是将不想暴露给外部的一些变量定义在类扩展中。

1.创建类别或类扩展文件

首先为工程添加新文件,并选择文件类型为Objective-CFile,如图所示。

选择Objective-CFile

然后填写自定义的扩展名,然后选择文件类型,这里选择Category或者Extension,最后选择要扩展的已有类,如图所示。

设置新建的类别文件

创建之后就可以得到对应的类别文件了,如图所示。

类别文件

2.添加新的扩展方法

这里以扩展NSString类的方法为例展示类别扩展的具体用法,分别添加一个类方法和实例方法,并调用。

类别头文件方法声明:

/*NSString+Category.h*/

#import<Foundation/Foundation.h>

@interfaceNSString(Category){

/*不可以添加实例变量,编译器会直接报错!*/

}

/*实例变量被禁止,此处添加属性变量足无意义的,定义的新属性,编译器没有实现存取方法,自己也无法手动实现存取方法,因为无法获取加下画线的实例变量,除非利用运行时强行实现存取方法才可以成功为类别添加属性,代价较高*/

//@property(nonatomic,copy)NSString*newStving;

/*扩展一个类方法*/

+(void)categoryClassMethodOtString;

/*扩展一个实例方法*/

-(void)categoryInstanceMethodOfString;

@end

类别方法的实现(类别的方法可以不实现,不实现则不可调用,否则会崩溃):

/*NSString+Category.m*/

#import"NSSuing+Category.h"

@implementationNSString(Category)

+(void)categoryClassMethodofString{

NSLog(@"categoryClassMethodOfString");

}

-(void)categoryInstanceMethodOfString{

NSLog(@"categoryInstanceMethodofString");

}

@end

在需要的类中引入类别头文件NSString+Category.h,然后即可调用新方法:

#import"NSString+Category.h"

/*1.调用类别扩展的类方法*/

[NSStringcategoryClassMethodOfString];

/*2.调用类别扩展的实例方法*/

NSString*string=[NSStringstringWithFormat:@""];

[stringcategoryInstanceMethodOfString];

3.类扩展的一般用法

类扩展即类的.m文件中@implementation之前开始的部分,所谓的类的continuous区域。

@interfaceclassname()

//...

@end

类扩展的作用本来是用于私有方法的前向声明,但最新编译器无须声明也有相同的效果,因此私有方法可在.m文件中任意位置直接写实现而无须在此处进行前向声明。如果在此处声明方法,那么一定要在后面进行实现,否则编译器会给出警告。现在类扩展区域的作用主要是快速定义类的私有属性,即将暴露给外部的属性变量定义在头文件中,而不想暴露给外部的属性则直接定义在类扩展区域。

注意这里提到的私有属性和私有方法并不是绝对私有的,Objective-C中没有绝对的私有方法和私有变量,因为即使它们隐藏在.m实现文件里不暴露在头文件中,开发者仍然可以利用runtime运行时机制对其暴力访问,只是一般情况下可以达到私有化的效果。

/*类扩展区域*/

@interfaceViewController()

/*类扩展属性(默认为private,类外部不可访问)*/

@property(nonatomic,copy)NSString*extensionVariable;

/*类扩展方法声明,可省略,要在立刻在后面进行函数实现*/

-(void)extensionInstanceMethod;

+(void)extensionClassMethod;

@end

类扩展区域如图中加框部分。

类扩展区域

11.

在Objective-C的数组或字典中,添加nil对象会有什么问题?正确答案:数组或字典如果通过addObject方法添加nil,那么程序会崩溃,但如果使用initWithObjects方法来初始化数组,其中的nil会被编译器过滤去掉,不会出现崩溃问题。另外,如果使用糖衣语法初始化数组或字典,那么也不可以有nil,此时nil不会被过滤掉也会导致程序崩溃。

/*1.糖衣语法*/

NSArray*array=@[@1,@2,@3,nil];//错误,不可有ni1,会编译不通过:void*不是Objective-C对象

NSDictionary*dic=@{

@"KEY":@"VALUE",

@"KEY1":@"VALUE1",

@"KEY2":nil

};//语法就是错误的,编译不通过

/*2.原用法*/

NSMutableArray*mulArray=[[NSMutableArrayalloc]initWithObjects:@1,@2,@3,nil];//正确

NSMutableDictionary*mulDic=[[NSMutableDictionaryalloc]initWithObjectsAndKeys:

@"VALUE",@"KEY",

@"VALUE1",@"KEY1",nil];//正确

/*下面添加nil都会编译警告,运行起来会崩溃*/

[mulArrayaddObject:nil];

[mulDicsetObject:nilforKey:@"KEY2"];

12.

iOS中如何触发定时任务或延时任务?正确答案:定时任务指周期性地调用某个方法,实现任务的反复执行,如倒计时等;延时任务指等待一定的时间后再执行某个任务,如页面的延时跳转等。iOS中控制任务的延时或定时执行的方法有很多,使用中要注意是同步还是异步,是否会阻塞主线程等问题。延时和定时的实现方法依次如下。

1.performSelector实现延时任务

延时任务可以通过当前UIViewController的perfonnSelector隐式创建子线程实现,不会阻塞主线程。

/*延迟10s执行任务*/

[selfperformSelector:@selector(task)withObject:nilafterDelay:10];

-(void)task

{

//delaytask

}

2.利用sleep实现后面任务的等待

慎用,会阻塞主线程NSThreadsleepForTimeInterval:10.0];

3.GCD实现延时或定时任务

通过GCD实现block代码块的延时执行。

dispatch_time_tdelay=dispatch_time(DiSPATCH_TIME_NOW,10*NSEC_PER_SEC);

dispatch_after(delay,dispatch_get_main_queue0,^{

//delaytask

});

GCD还可以用来实现定时器功能,还能设置延时开启计时器,使用中注意一定要定义强引用指针来指向计时器对象才可让计时器生效。

/*必须要用强引用指针,计时器才会生效*/

@property(nonatomic,strong)dispatch_source_ttimer;

/*在指定线程上定义计时器*/

dispatch_queue_tqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

dispatc_souree_t_timer=dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0,0,queue);

/*开始的时间*/

dispatch_time_twhen=dispatch_time(DISPATCH_TIME_NOW,(int64_t)(1.0*NSEC_PER_SEC));

/*设置计时器*/

dispatch_source_set_timer(_timer,when,1.0*NSEC_PER_SEC,0);

/*计时器回调block*/

dispatch_source_set_event_handler(_timer,^{

NSLog(@"dispatch_source_set_timerisworking!");

});

/*开启计时器*/

dispatch_resume(_timer);

/*强引用计时器对象*/

self.timer=_timer;

4.NSTimer实现定时任务

NSTimer主要用于开启定时任务,但要正确使用才能保证它能够正常有效地运行。尤其要注意以下两点:

1)确保NSTimer已经添加到当前RunLoop。

2)确保当前RunLoop已经启动。

创建NSTimer有两种方法,代码如下:

+(NSTimer*)timerWithTimeInterval:(NSTimeInterval)titarget:(id)aTargetselector:(SEL)aSelectoruserInfo:(nullableid)userInforepeats:(BOOL)yesOrNo;

+(NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)titarget:(id)aTargetselector:(SEL)aSelectoruserInfo:(nullableid)userInforepeats:(BOOL)yesOrNo;

这两种方法的主要区别为,使用timerWithTimeInterval创建的timer不会自动添加到当前RunLoop中,需要手动添加并指定RunLoop的模式:[[NSRunLoopcurrentRunLoop]addTimer:mainThreadTimerforMode:NSDefaultRtmLoopMode];而使用scheduledTimerWithTimeInterval创建的RunLoop会默认添加到当前RunLoop中。

NSTimer可能在主线程中创建,也可能在子线程中创建。主线程中的RunLoop默认是启动的,所以timer只要添加到主线程RunLoop中就会被执行;而子线程中的RunLoop默认是不启动的,所以timer添加到子线程RunLoop中后,还要手动启动RunLoop才能使timer被执行。

NSTimer只有添加到启动起来的RunLoop中才会正常运行。NSTimer通常不建议添加到主线程中执行,因为界面的更新在主线程中进行,这会影响NSTimer的准确性。

以下代码为4种情形下NSTimer的正确使用方法。

-(void)viewDidLoad{

[superviewDidLoad];

/*第一种,主线程中创建timer,需要手动添加到RunLoop中*/

NSTimer*mainThreadTimer=[NSTimertimerWithTimeInterval:10.0target:selfselector:@selector(mainThreadTimer_SEL)userInfo:nilrepeats:YES];

/*第二种,主线程中创建timer,不需要手动添加到RunLoop中*/

NSTimer*mainThreadSchduledTimer=[NSTimer

scheduledTimerWithTimeInterval:10.0target:selfselector:@selector(mainThreadSchduledTimer_SEL)userInfo:nilrepeats:YES];

/*将mainThreadTimer添加到主线程runloop*/

[[NSRunLoopcurrentRunLoop]addTimer:mainThreadTimer

forMode:NSDefaultRunLoopMode];

dispatch_asyne(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{

/*第三种,子线程中创建timer,需要手动添加到RunLoop中*/

NSTimer*subThreadTimer=[NSTimertimerWithTimeInterval:10.0target:selfselector:@selector(subThreadTimer_SEL)userInfo:nilrepeats:YES];

/*第三种,子线程中创建timer,不需要手动添加到RunLoop中*/

NSTimer*subThreadSchduledTimer=[NSTimerscheduledTimerWithTimeInterval:10.0target:selfselector:@selector(subThreadSchduledTimer_SEL)userInfo:nilrepeats:YES];

/*将subThreadTimer添加到子线程runloop*/

[[NSRunLoopcurrentRunLoop]addTimer:subThreadTimerforMode:NSDefaultRunLoopMode];

/*启动子线程mnloop*/

[[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopModebeforeDate:[NSDatedistantFuture]];

});

}

-(void)mainThreadTimer_SEL{

NSLog(@"mainThreadTimerisworking!");

}

-(void)mainThreadSchduledTimer_SEL{

NSLog(@"mainThreadSchduledTimerisworking!");

}

-(void)subThreadTimer_SEL{

NSLog(@"subThreadTimerisworking!");

}

-(void)subThreadSchduledTimer_SEL{

NSLog(@"subThreadSchduledTimerisworking!");

}

上述代码的打印结果为:

2018-01-3120:24:10.729259+0800IOSDemo[41891:4382661]mainThreadSchduledTimerisworking!

2018-01-3120:24:20.728351+0800IOSDemo[41891:4382661]mainThreadTimerisworking!

2018-01-3120:24:20;728409+0800IOSDemo[41891:4382720]subThreadTimerisworking!

2018-01-3120:24:20.728693+0800IOSDemo[41891:4382720]subThreadSchduledTimerisworking!

NSTimer的释放方法:[timerinvalidate];

5.CADisplayLink实现定时任务

CADisplayLink实现的定时器与屏幕刷新频率绑定在一起,是一种帧率刷新,适用于界面的不断重绘(例如流畅动画和视频播放等)。CADisplayLink以特定模式注册到RunLoop后,每当屏幕显示内容刷新结束后就会向CADisplayLink指定的target发送一次消息,实现target的每帧调用。根据需求也可以设置每几帧调用一次,默认每帧都调用。另外,通过CADisplayLink还可以获取帧率和时间等信息。

CADisplayLink实现的定时器精度非常高,但如果调用的方法十分耗时,超过一帧的时间间隔,那么会导致跳帧,跳帧次数取决于CPU的忙碌程度。

下面是CADisplayLink定时器的实现:

-(void)viewDidLoad{

[superviewDidLoad];

CADisplayLink*displayLink=[CADisplayLinkdisplayLinkWithTarget:selfselector:@selector(displayLink_SEL)];

/*添加到当前运行的RunLooP中启动*/

[displayLinkaddToRunLoop:[NSRunLoopcurrentRunLoop]

forMode:NSDefaultRunLoopMode];

/*暂停、继续对selector的调用*/

//[displayLinksetPaused:YES];

//[displayLinksetPaused:NO];

/*设置每几帧调用一次selector,默认为1*/

//[displayLinksetPreferredFramesPerSecond:2];

/*移除,不再使用*/

//[displayLinkinvalidate];

//displayLink=nil;

}

-(void)displayLink_SEL{

NSLog(@"displayLinkisworking!");

}

打印结果如下,每一帧都调用selector。

2018-02-0111:48:02.707283+0800IOSDemo[42328:4522489]displayLinkisworking!

2018-02-0111:48:02.724375+0800IOSDemo[42328:4522489]displayLinkisworking!

2018-02-0111:48:02.742995+0800IOSDemo[42328:4522489]displayLinkisworking!

13.

masksToBounds属性是什么?它有什么作用?正确答案:masksToBounds指在设置子layer在超出父layer时是否被裁剪,YES表示裁剪,NO表示不裁剪,默认是NO;通常在通过设置layer.cornerRadius属性实现圆角效果时要设置masksToBounds为YES,以保证圆角效果的实现,但这种方法是一种很低效的实现方式,也是最简单直接的。

masksToBounds和clipsToBounds是不同的,前者指子layer层在超出父layer时是否被裁剪(masksToBounds是CALayer的属性),而后者指子view在超出父view时是否被裁剪(clipsToBounds是UIView的属性)。

14.

如何理解消息传递机制?正确答案:对Objective-C对象调用方法的操作通常称为“消息传递”。在Objective-C中,如果向某对象传递消息,那么就会使用动态绑定机制来决定需要调用的方法。在底层,所有方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全由runtime来决定,有时甚至可以在运行期改变方法的实现。

在代码中给对象发送消息可以这样来编写:

[receivermessageName:parameter];

消息的接收者(receiver)和参数(parameter)合起来称为“消息”(message)。在编译阶段,会将上面的代码转换成C语言函数调用,所调用的函数是消息传递机制中的核心函数,叫作objc_megSend,其在objc/message.h文件下的定义如下:

voidobjc_msgSend(void/*idself,SELop,...*/)

objc_msgSend函数将消息接收者和方法名作为其基础参数。其中,参数self代表消息接收者,参数SEL代表方法名(selector,又称选择器)。每个类中的方法列表(methodLists)类似于一张表格,其中的指针都会指向方法的实现,而方法名是查表时所用的键。objc_msgSend函数正是通过这张表格来寻找应该执行的方法并跳转至其实现的。

objc_msgSend函数会根据接收者和方法来调用适当的方法。基本流程如下:

1)objc_msgSend函数通过isa指针在接收者所属的类中搜寻方法缓存列表(cache)和方法列表,如果能找到与方法名相符合的方法,那么就跳转至其实现代码。

2)若是找不到,那么就会通过super_class指针沿着类的继承体系继续向上查找,如果能够找到合适的方法,那么就会跳转。

3)如果上面两步都无法找到相符的方法,那么就会执行消息转发(messageforwarding)机制。

15.

iOS中有哪几种从其他线程回到主线程的方法?正确答案:iOS中只有主线程才能立即刷新UI。如果是通过侦听异步消息,触发回调函数,或者调用异步方法,请求刷新UI,那么都会产生线程阻塞和延迟的问题。如果要在其他线程中执行刷新UI的操作,那么就必须切换回主线程。主要由以下3种方式。

1.NSThreadPerformAdditions协议

这个协议提供了两个切换到主线程的API。

-(void)performSeleetorOnMainThread:(SEL)aSelectorwithObject:

(nullableid)argwaitUntilDone:(BOOL)waitmodes:(nullableNSArray<NSString*>*)array;

-(void)performSelectorOnMainThread:(SEL)aSelectorwithObject:

(nullableid)argwaitUntilDone:(BOOL)wait;

2.GCD

使用GCD的dispatch_get_main_queue()方法可以获取主队列,主队列中的任务必定是在主线程中执行的。

dispacch_async(dispatch_get_main_queue(),^{

});

3.NSOperationQueue

和GCD一样,使用NSOperationQueue提供+mainQueue方法可以获取主队列,再将刷新UI的任务添加到主队列中。

[[NSOperationQueuemainQueue]addOperationWithBlock:^{

}];

示例代码如下:

-(void)goToMainThread{

/*开启子线程下载图片*/

dispatch_async(dispatch_get_global_queue(0,0),^{

NSData*imageData=[[NSDataalloc]initWithContentsOfURL:[NSURLURLWithString:@"/pe0060886.jpg"]];

_image=[UIImageimageWithData:imageData];

/*切换到主线程显示*/

//1.NSThread

//[selfperformSeteetorOnMainThread:@selector(changeBg)withObject:nilwaitUntilDone:NO];

//2.GCD

//dispatch_async(dispatch_get_main_queue(),^{

//[selfehangeBg];

//});

//3.NSOperationQueue

[[NSOperationQueuemainQueue]addOperationWithBlock:^{

[selfchangeBg];

}];

});

}

16.

Objective-C中有二维数组吗?如何实现?正确答案:Objective-C中是没有二维数组的。二维数组是通过一维数组的嵌套实现的,但是别忘了还有字面量用法,实际上可以像C/C++一样简洁地创建和使用二维数组。这里总结了创建二维数组的两种方法以及数组的访问方式。

1)通过字面量创建和使用二维数组(推荐)方法如下:

/*字面量创建二维数组并访问(推荐)*/

NSArray*array2d=@[

@[@11,@12,@13],

@[@21,@22,@23],

@[@31,@32,@33]

];

/*字面量访问方式(推荐)*/

NSLog(@"array2d[2][2]:%@",array2d[2][2]);

/*数组对象访问*/

NSLog(@"array2d[2][2]:%@",[[array2dobjectAtIndex:2]objectAtIndex:2]);

打印结果如下:

2017-01-0521:59:49.694SingleView[10483:506166]array2d[2][2]:33

2017-01-0521:59:49.695SingleView[10483:506166]array2d[2][2]:33

2)通过嵌套原本的数组对象使用二维数组方式如下:

/*另外一种循环嵌套创建二维数组的方式*/

NSMutableArray*mulArrayD1=[[NSMutableArrayalloc]init];

//第一维数组

/*添加第二维*/

for(NSUIntegeri=1;i<=3;i++){

NSArray*arrayD2=@[@(i*10+1),@(i*10+2),@(i*10+3)];

[mulArrayD1addObject:arrayD2];

}

/*字面量访问方式(推荐)*/

NSLog(@"array2d[2][2]:%@",mulArrayD1[2][2]);

/*数组对象访问*/

NSLog(@"array2d[2][2]:%@",[[mulArrayD1objectAtIndex:2]objectAtIndex:2]);

打印结果如下:

2017-01-0521:59:49.695SingleView[10483:506166]array2d[2][2]:33

1:59:49.695SingleView[10483:506166]array2d[2][2]:33

17.

对于语句NSString*testObject=[[NSDataalloc]init];,testObject在编译时和运行时分别是什么类型的对象?正确答案:testObject是一个指向某个对象的指针,不论何时指针的空间大小都是固定的。

编译时:指针的类型为NSString,即编译时会被当成一个NSString实例来处理,编译器在类型检查的时候,如果发现类型不匹配则会给出黄色警告,由于该语句给指针赋值用的是一个NSData对象,所以编译时会给出类型不匹配警告。但编译时如果testObject调用NSString的方法,那么编译器会认为是正确的,既不会警告也不会报错。

运行时:运行时指针指向的实际是一个NSData对象,因此如果指针调用了NSString的方法,那么虽然编译时通过了,但运行时会崩溃,因为NSData对象没有该方法。另外,虽然运行时指针实际指向的是NSData,但编译时编译器并不知道(前面说了编译器会把指针当成NSString对象处理),因此如果试图用这个指针调用NSData的方法,那么会直接编译不通过,给出红色报错,程序也运行不起来。

18.

如何使用KVO设置键值观察依赖键?正确答案:很多情况下对象的属性之间是相互关联的,对象的一个属性值依赖于另一个对象的一个或多个属性,如果这些属性中的任意一个值发生改变,那么被依赖属性的值也会相应发生改变。如果要观察被依赖属性值的变更,那么就需要使用KVO中的依赖键。实现KVO中的依赖键最重要的部分是设置依赖关系。示例代码如下:

/*新建一个Student类,其中的preson属性是Person类的实例*/

#import<Foundation/Foundation.h>

#import"Person.h"

@interfaceStudent:NSObject

@property(nonatomic,strong)NSString*infomation;

@property(nonatomic,strong)Person*preson;

@end

#import"Student.h"

@implementationStudent

@end

此时需要观察Student类实例中infomation属性值的变化,而infomation属性的值依赖于preson属性的值,那么如何确立这种依赖关系呢?开发者要手动实现infomation的setter和getter方法,还要实现keyPathsForValuesAffectingInfomation或者keyPathsForValuesAffectingValueForKey:方法,这两个方法的作用都是用来告诉系统infomation属性依赖于其他哪些属性,而且这两个方法都返回包含着依赖key-path的集合。在例子中,infomation属性依赖于person中的age和name属性,当person中age或者name属性发生改变时,infomation的观察者都会得到通知。在Student.m文件中添加如下代码:

-(NSString*)infomation{

return[NSStringstringWithFormat:@"student_name=%@,student_age=%zd",,self.preson.age];

}

-(void)setInfomation:(NSString*)infomation{

NSArray*array=[infomationcomponentsSeparatedByString:@"#"];

[self.presonsetName:[arrayobjectAtIndex:0]];

[self.presonsetAge:[[arrayobjectAtIndex:1]integerValue]];

}

+(NSSet*)keyPathsForValuesAffectingInfomation{

NSSet*set=[NSSetsetWithObjects:@"person.age",@"",nil];

returnset;

}

现在已经建立了依赖关系,还需要在控制器中添加观察者和实现KVO的回调方法。

-(void)viewDidLoad{

astudent=[[Studentalloc]init];

astudent.person=[[Personalloc]init];

[astudentaddObserver:selfforKeyPath:@"infomation"options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOldcontext:nil];

}

-(void)touchesBegan:(NSSet<UITouch*>*)toucheswithEvent:(UIEvent*)event{

astudent.person.age=20;

=@"jack";

}

-(void)observeValueForKeyPath:(NSString*)keyPathofObject:(id)objectchange:(NSDictionary<NSKeyValueChangeKey,id>*)changecontext:(void*)context{

if([keyPathisEqualToString:@"infornation"]){

NSLog(@"old_infomation:%@new_infomation:%@",change[NSKeyValueChangeOldKey],change[NSKeyValueChangeNewKey]);

}

}

程序的输出结果如下:

2016-10-3119:01:19.34601[53615:2109010]old_infomation:student_name=(null),student_age=0new_infomation:student_name=(null),student_age=20

2016-10-3119:01:19.34601[53615:2109010]old_infomation:student_name=(null),student_age=20new_infomation:student_name=jack,student_age=20

当

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论