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

下载本文档

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

文档简介

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

union和unionall有什么区别?正确答案:union在进行表求并集后会去掉重复的元素,所以会对所产生的结果集进行排序运算,删除重复的记录再返回结果(江南博哥)。

而unionall只是简单地将两个结果合并后就返回。因此,如果返回的两个结果集中有重复的数据,那么返回的结果集就会包含重复的数据了。

从上面的对比可以看出,在执行查询操作的时候,unionall要比union快很多。所以,如果可以确认合并的两个结果集中不包含重复的数据,那么最好使用unionall。例如,有两个学生表table1和table2。Table1C1C2112233Table2C1C2334411select*fromTable1unionselect*fromTable2的查询结果为:C1C211223344select*fromTablelunionallselect*fromTable2的查询结果为:C1C2112233334411

2.

UIDevice如何获取设备信息?正确答案:iOS开发中获取设备的信息指像设备号、应用名称以及国家语言等非用户隐私信息,这些信息多数都可以在Xcode工程中看到,同时开发者可以利用框架提供的UIDevice、NSBundle、NSLocale3个类来获取一些开发中常用到的信息。

1.UIDevice

UIDevice可以提供给开发者很多移动设备的基本信息,如设备名称、设备模式、系统名称、系统版本、设备唯一标识符以及设备方向和设备电量等。

其中的设备方向是一个枚举变量UIDeviceOrientation(TV中禁止使用),源代码中的定义如下:

typedefNS_ENUM(NSInteger,UIDeviceOrientation){

UIDeviceOrientationUnknown,

UIDeviceOrientationPortrait,

//设备垂直,home键在下

UIDeviceOrientationPortraitUpsideDown,

//设备翻转,home键在上

UIDeviceOrientationLandscapeLeft,

//设备水平,home件在左

UIDeviceOrientationLandsoareRight,

//设备水平,home件在右

UIDeviceOrientationFaceUp,

//设备平放,面朝上

UIDeviceOrientationFaceDown

//设备平放,面朝下

}_TVOS_PROHIBITED;

在SDK源代码中定义如下:

@property(nonatomic,readonly,strong)NSString*name;//e.g."MyiPhone"

@property(nonatomic,readonly,strong)NSString*model;//e.g.@"iPhone",@"iPodtouch"

@property(nonatomic,readonly,strong)NSString*localizedModel;//本地化的设备模式

@property(nonatomic,readonly,strong)NSString*systemName;//e.g.@"iOS"

@property(nonatomic,readonly,strong)NSString*systemVersion;//e.g@"4.0"

@property(nonatomic,readonly)UIDeviceOrientationorientation__TVOS_PROHIBITED;//返回当前的设备方向,如果还没有产生设备方向通知(设备方向未发生改变),那么返回UIDeviceOrientationUnknown

@property(nullable,nonatomic,readonly,strong)NSUUID

*identifierForVendorNS_AVAILABLE_IOS(6_0);

//可用于唯一标识设备的一个UUID

测试示例代码如下:

/*1.UIDevice*/

UIDevice*deviee=[UIDevicecurrentDevice];

//获取当前的device

/**设备基本信息**/

NSString*name=[devicename];

//@"iPhone5S"

NSString*model=[devicemodel];//@"iPhone"

NSString*localizedModel=[devicelocalizedModel];//@"iPhone"

NSString*systemName=[devicesystemName];//@"iPhoneOS"

NSString*systemVersion=[devicesystemVersion];//@"9.3.2"

/**设备方向**/

UIDeviceOrientationorientation=[deviceorientation];

//UIDeviceOrientationUnknown

/*设备类型:iPad或iPhone(iPod)等*/

if([[UIDevicecurrentDevice]userInterfaceIdiom]==UIUserInterfaceIdiomPad|UIUserInterfaceIdiomPhone){}

/**设备电量**/

device.batteryMonitoringEnabled=YES;

floatbatteryLevel=[UIDevicecurrentDevice].batteryLevel;//0~1.0对应电量0~100%,-1.0表示电量未知

device.batteryMonitoringEnabled=YES;

/**设备ID**/

NSUUID*identifierForVendor=[deviceidentifierForVendor];

2.NSBundle

开发者经常用到类NSBundle的mainbundle,一个包含了nib文件、编译代码等资源的目录,这个目录下有一个infoDictionary,通过这个信息字典开发者可以获取有关当前应用的一些基本信息,像应用名称、应用版本等。

测试示例代码如下:

/*2.NSBundle*/

/*获取当前设备的信息字典*/

NSDictionary*info_dic=[[NSBundlemainBundle]infoDictionary];

/*本地化的信息字典*/

NSDictionary*localized_info_dic=[[NSBundlemainBundle]localizedlnfoDictionary];

NSString*app_name=[info_dicobjectForKey:@"CFBundleDisplayName"];

//应用名称@"Demo"

NSString*app_version=[info_dicobjectForKey:@"CFBundleShortVersionString"];//应用版本@"1.0"

NSString*app_build_version=[info_dicobjectForKey:@"CFBundleVersion"];//应用build版本@"1"

3.NSLocale

顾名思义,NSLocale类是用于获取一些本地化信息的,如国家、时间日期和语言等。这里主要展示常用到的获取国家和语言代码的方法。

测试示例代码如下:

/*3.NSLocale*/

/*3.1获取偏好语言*/

NSArray*languageArray=[NSLocalepreferredLanguages];

NSString*language=[languageArrayobjectAtIndex0];//@"en_HK"

/*3.2获取本地化国家或地区代号*/

NSLocale*locale=[NSLocalecurrentLocale];//当前本地化对象

NSString*country=[localelocaleIdentifier];//@"en_HK"

3.

以下每行代码执行后,person对象的retaincount分别是多少?

Person*person=[[Personalloc]init];

[personretain];

[personrelease];

[personrelease];正确答案:开始alloc创建对象并持有对象,初始引用计数为1,retain一次引用计数加1变为2,之后release对象两次,引用计数减1两次,先后变为1、0,所以分别是1-2-1-0。

4.

如何使用runtime进行方法交换?正确答案:runtime中的MethodSwizzling被称为黑魔法,它可以将两个方法的实现交换。类的方法列表会将selector的名称映射到相关的方法实现上,使得“消息传递系统”能够据此找到应该调用的方法。这些方法均是以函数指针的形式来表示的,这种函数指针叫作IMP。例如NSString类的selector映射表,如图所示。

NSString类的selector映射表

runtime中提供了几个函数用来操作selector映射表,其中就有动态交换两个方法实现的函数,代码如下:

voidmethod_exchangeImplementations(Methodm1,Methodm2)

下面在类的Category中定义description方法,当使用NSLog()函数输出对象时,就会调用这个方法。示例代码如下:

/*load方法会在类第一次被加载的时候调用*/

+(void)load{

Methoddescription=class_getInstanceMethod([selfclass],@selector(description));

MethodmyDescription=class_getInstanceMethod([selfclass],@selector(myDescription));

method_exchangeImplementations(description,myDescription);

}

/*替换的方法*/

-(NSString*)myDescription{

return[NSStringstringWithFormat:@"这是自定义的打印方法"];

}

/*在控制器输出*/

aperson=[[Personalloc]init];

NSLog(@"%@",aperson);

程序的打印结果如下:

2016-11-0320:02:59.18401[2990:170401]这是自定义的打印方法

通过MethodSwizzling可以为一些完全不知道其具体实现的黑盒方法增加日志记录功能,利于开发者调试程序。并且开发者可以将某些系统类的具体实现换成自定义的方法,以达到某些目的。

5.

进程与线程有什么区别?正确答案:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,它是系统进行资源分配和调度的一个独立单位。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等,然后该进程被放入到进程的就绪队列,进程调度程序选中它,为它分配CPU及其他相关资源,该进程就被运行起来。

线程是进程的一个实体,是CPU调度和分配的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但是它可以与同属一个进程的其他的线程共享进程所拥有的全部资源。在没有实现线程的操作系统中,进程既是资源分配的基本单位,又是调度的基本单位,它是系统中并发执行的单元。而在实现了线程的操作系统中,进程是资源分配的基本单位,而线程是调度的基本单位,是系统中并发执行的单元。

引入线程主要有以下4个方面的优点:

1)易于调度。

2)提高并发性。通过线程可以方便有效地实现并发。

3)开销小。创建线程比创建进程要快,所需要的开销也更少。

4)有利于发挥多处理器的功能。通过创建多线程,每个线程都在一个处理器上运行,从而实现应用程序的并行,使每个处理器都得到充分运行。

需要注意的是,尽管线程与进程很相似,但两者也存在着很大的不同,区别如下:

1)一个线程必定属于也只能属于一个进程;而一个进程可以拥有多个线程并且至少拥有一个线程。

2)属于一个进程的所有线程共享该进程的所有资源,包括打开的文件、创建的Socket等。不同的进程互相独立。

3)线程又被称为轻量级进程。进程有进程控制块,线程也有线程控制块。但线程控制块比进程控制块小得多。线程间切换代价小,进程间切换代价大。

4)进程是程序的一次执行,线程可以理解为程序中一个程序片段的执行。

5)每个进程都有独立的内存空间,而线程共享其所属进程的内存空间。

6.

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

7.

UIViewController的生命周期方法有哪些?正确答案:在开发过程中,大量的逻辑代码主要会写在UIViewController的生命周期方法中,了解这些方法的含义和调用时机十分重要。UIViewController的主要生命周期方法及其含义见表。UIViewController的主要生命周期方法及其含义方法含义[UIViewControlleralloc]创建对象并分配内存空间[UIViewControllerinitWithNibName:bundle:]视图控制器nib初始化,初始化对象以及数据[ViewControllerinit]视图控制器初始化,初始化对象以及数据-(void)loadView首次创建view调用该方法(例如首次调用view的getter方法:self.view,controller.view),如果使用了xib创建视图,那么从nib加载视图-(void)loadViewIfNeeded如果视图控制器的视图还没有加载,那么加载-(void)viewDidLoad视图加载完成,可以动态添加其他UI控件-(void)viewWillAppear:(BOOL)animated视图即将出现到屏幕上之前(每一次要显示该视图都会重复调用,不仅仅是第一次创建初始化之后,下面另外3个时机亦然)-(void)viewDidAppear:(BOOL)animated视图已经显示到屏幕上之后-(void)viewWillDisappear:(BOOL)animated视图即将从屏幕上消失(消失包括被销毁、被隐藏、被覆盖等情况)-(void)viewDidDisappear:(BOOL)animated视图已经从屏幕上消失-(void)viewWillLayoutSubviews在视图的layoutSubviews方法激活(View自身的frame发生改变)前调用-(void)viewDidLayoutSubviews在视图的layoutSubviews方法激活后立刻调用-(void)didReceiveMemoryWarning应用收到内存警告导致视图无法加载时调用,需要手动清理因为iOS6以后不会默认自动清理,通常在应用占用太大内存时被系统进行内存警告,需要在下面的viewWillUnload方法中将UI手动置nil-(void)viewWillUnload视图将不会继续加载,对应于下面的viewDidUnload,iOS6后不会再对其进行调用-(void)viewDidUnload在视图控制器被释放或者被设置为nil时调用,例如收到内存警告无法加载时,即非正常加载,而不是dealloc时的正常销毁,这里要手动清理界面元素资源;iOS6以后该回调方法被弃用了,任何情况下都不会再被调用,因为内存低时系统不再在这里清理视图元素了,官方建议之前该回调内进行的清理工作应转移到didReceiveMemoryWarning回调方法中,但实际上也并不需要再进行之前在viewDidUnload中进行的视图清理工作,系统会独立于视图单独进行处理-[UIViewControllerdealloc]视图销毁,可以释放初始化时的对象等资源

8.

静态链接与动态链接有什么区别?正确答案:静态链接指把要调用的函数或者过程直接链接到可执行文件中,成为可执行文件的一部分。换句话说,函数和过程的代码就在程序的.exe文件中,该文件包含了运行时所需的全部代码。静态链接的缺点是当多个程序都调用相同函数时,内存中就会存在这个函数的多个副本,这样就浪费了内存资源。

动态链接是相对于静态链接而言的,动态链接所调用的函数代码并没有被复制到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在操作系统的管理下,才在应用程序与相应的动态链接库(DynamicLinkLibrary,DLL)之间建立链接关系。当要执行所调用.dll文件中的函数时,根据链接产生的重定位信息,操作系统转去执行.dll文件中相应的函数代码。

静态链接的执行程序能够在其他同类操作系统的机器上直接运行。例如,一个.exe文件是在Windows2000系统上静态链接的,那么将该文件直接复制到另一台Windows2000的机器上,是可以运行的。而动态链接的执行程序不可以,除非把该.exe文件所需的.dll文件都复制过去,或者对方机器上也有所需的相同版本的.dll文件,否则是不能保证正常运行的。

9.

库函数调用与系统调用有什么不同?正确答案:库函数是语言或应用程序的一部分,它是高层的、完全运行在用户空间、为程序员提供调用真正在幕后完成实际事务的系统调用接口。系统函数是内核提供给应用程序的接口,属于系统的一部分。简单地说,函数库调用是语言或应用程序的一部分,而系统调用是操作系统的一部分。

库函数调用与系统调用的区别见表。库函数调用与系统调用的区别库函数调用系统调用在所有的ANSIC编译器版本中,C语言库函数是棚同的各个操作系统的系统调用是不同的它调用函数库中的一段程序(或函数)它调用系统内核的服务与用户程序相联系足操作系统的一个入口点在用户地址空间执行在内核地址空间执行它的运行时间属于“用户时间”它的运行属于“系统时间”属于过程调用,调用开销较小需要在用户空间和内核上下文环境间切换,开销较大在C函数库libc中有大约300个函数在UNIX中有大约90个系统调用典型的C函数库调用:system、fprintf和malloc等典型的系统调用:chdir、fork、write和brk等

库函数调用通常比行内展开的代码慢,因为它需要付出函数调用的开销。但系统调用比库函数调用还要慢很多,因为它需要把上下文环境切换到内核模式。

10.

Objective-C和Swift中有重载吗?正确答案:Swift中有重载,但Objective-C中基本不支持重载,事实上Objective-C支持参数个数不同的函数重载。

Swift是基于C语言和Objective-C语言优化后更加完善的新型语言,摆脱了C的兼容性限制,采用安全的编程模式并且增加了一些新的特性使编程更加有趣、友好,适应语言发展的趋势和期望。函数重载作为多态性的一个部分在Swift中是支持的,可能也是考虑到要弥补Objective-C中不完全支持函数重载的这一缺陷。Objective-C不完全支持重载,因为Objective-C学习者应该会发现同一个类中不允许定义函数名相同且参数个数相同的两个函数,无论参数类型和返回值类型相同与否。但是说完全不支持也太绝对,因为Objective-C中允许定义函数名相同但参数个数不同的两个函数,也就是说Objective-C支持参数个数不同的函数重载。例如,可以在一个类中定义两个参数个数不同的函数,调用时通过参数个数进行区分。

重载函数定义如下:

-(void)test:(int)one;

-(void)test:(int)oneandTwo:(int)two;

重载函数实现如下:

-(void)test:(int)one{

NSLog(@"oneparameter!");

}

-(void)test:(int)oneandTwo:(int)two{

NSLog(@"twoparameters!");

}

多态调用:

[selftest;1];

//output:oneparameter!

[selftest:1andTwo:2];

//output:twoparameter!

可以看出Objective-C可以通过参数个数实现函数重载,但是如果参数相同,那么无论参数和返回值类型相同与否,都无法编译通过。下面的定义是无法通过Xcode编译的。

-(void)test:(int)one;

-(int)test:(float)one;

//Duplicatedeclarationofmethod'test'

11.

如何使用RAC防止button短时间内重复单击?正确答案:很多时候,我们要防止按钮或cell的重复单击,特别是当单击按钮会触发网络请求的时候。如果在短时间内多次单击按钮,那么会使得网络在短时间内重复请求,上一个请求未结束,下一个请求又发出了,这样有可能会带来意想不到的后果,严重的话会造成程序崩溃。因此,防止按钮在短时间内重复单击是非常有必要的。如果是经验丰富的程序员,那么他们会想到AOP,利用iOS中的runtime机制,使用MethodSwizzling实现单击事件的hook,然后延时执行单击事件,从而避免短时间内重复单击。如果开发者会RAC,那么这个问题就很简单了,解决方法如下:

[[[self.buttonrac_signalForControlEvents:UIControlEventTouchUpInside]throttle:2]subscribeNext:^(__kindofUIControl*_Nullablex){

NSLog(@"buttonciick");

}];

以上代码的功能是使用throttle(节流)方法,实现2s内,多次单击按钮只响应最后一次单击的事件。

12.

自动释放池跟GC(垃圾回收)有什么区别?iPhone上有GC吗?[poolrelease]和[pooldrain]有什么区别?正确答案:[poolrelease]和[pooldrain]在作用上是一样的,它们都是清理自动释放池,区别是drain在支持GC的系统中(Mac系统)可以引起GC回收操作,而release不可以。对于自动释放池一般建议使用[pooldrain],原因是它的功能对系统兼容性更强,二者也是为了跟普通对象的release释放区别开。自动释放池的释放操作指向池内所有的对象发送release消息,以让系统及时释放池内的所有对象。

13.

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!

14.

如何理解并使用CAKeyframeAnimation?正确答案:CAKeyframeAnimation即关键帧动画,它是CAPropertyAnimation的一个子类,它作用于单一的属性,但和CABasicAnimation不一样的是,它不需要设置一个起始和结束的值,而是可以根据开发者提供的一连串随意的值来做动画。这些值就是关键帧,每帧之间的绘制将由系统自动完成。示例代码如下:

-(void)startKeyframeAnimation{

CAKeyframeAnimation*animation=[CAKeyframeAnimationanimation];

/*指定需要做动画的属性*/

animation.keyPath=@"backgroundColor";

/*指定关键帧*/

animation.values=@[(id)[UIColorwhiteColor].CGColor,(id)[taColorreaColor].CGColor,(id)(id)[UIColorblueColor].CGColor,(id)[UIColorblackColor].CGColor,(id)[UIColorwhiteColor].CGColor];

/*指定关键帧的过渡时间*/

animation.keyTimes=@[@0,@0.2,@0.5,@0.7,@1];

animation.duration=5;

/*指定关键帧的过渡缓冲方式*/

animation.timingFunctions=@[[CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseInEaseOut],[CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunetionEaseInEaseOut],[CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseInEaseOut],[CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseInEaseOut]];

[self.view.layeraddAnimation:animationforKey:nil];

}

使用关键帧动画需要注意下面3点:

1)注意到values数组中开始和结束的颜色都是白色,这是因为关键帧动画并不能将当前值作为第一帧。动画在执行后会立即跳转到第一帧的值,然后在动画结束的时候突然恢复到原始值。所以为了使动画更加平滑自然,需要使用开始和结束的关键帧来匹配当前属性的值。

2)除了使用values来指定关键帧以外,还可以使用其path属性。它是CGPath类型,它可以用一种非常直观的方式来定义动画的运动序列。在做一些基于位置改变的动画时,它非常有用。

@property(nullable)CGPathRefpath;

3)指定要做动画的属性并非只能是可动画属性(animatable),可以对子属性甚至是虚拟属性(不能直接使用)做动画。例如,指定keyPath为transform.rotaion来做旋转动画。

15.

MethodSwizzling的应用场景有哪些?正确答案:MethodSwizzling主要用于在运行时对编译器编译好的方法再次进行编辑,应用场景主要有以下4种。

1.替换类中某两个类方法或实例方法的实现

替换函数:method_exchangeImplementations(method1,method2)。

这里随便定义一个Test类,类中定义两个实例方法和类方法并在.m文件中实现,在运行时将两个实例方法的实现对调,以及将两个类方法的实现对调。注意运行时代码写在类的load方法内,该方法只会在该类第一次加载时调用一次,且编写运行时代码的地方需要引入运行时头文件:

#import<objc/runtime.h>

Test类定义:

/*Test.h*/

#import<Foundation/Foundation.h>

@interfaceTest:NSObject

/*定义两个公有实例方法*/

-(void)instanceMethod1;

-(void)instanceMethod2;

/*定义两个公有类方法*/

+(void)ClassMethod1;

+(void)classMethod2;

@end

/*Test.m*/

#import"Test.h"

#import<objc/runtime.11>

@implementationTest

/*实例方法的原实现*/

-(void)instanceMethod1{

NSLog(@"instanceMethod1...");

}

-(void)instanceMethod2{

NSLog(@"instanceMethod2...");

}

/*类方法的原实现*/

+(void)classMethod1{

NSLog(@"classMethod1...");

}

+(void)classMethod2{

NSLog(@"classMethod2...");

}

@end

/*runtime代码写在类第一次被调用加载的时候(load方法有且只有一次会被调用)*/

+(void)load{

/*1.获取当前类名*/

Classclass=[selfclass];

/*2.获取方法名(选择器)*/

SELselInsMethodl=@selector(instanceMethod1);

SELselInsMethod2=@selector(instanceMethod2);

SELselClassMethod1=@selector(classMethod1);

SELselClassMethod2=@selector(classMethod2);

/*3.根据方法名获取方法对象*/

MethodInsMethod1=class_getInstanceMethod(class,selInsMethod1);

MethodInsMethod2=class_getInstanceMethod(class,selInsMethod2);

MethodClassMethod1=class_getClassMethod(class,selClassMethod1);

MethodClassMethod2=class_getClassMethod(class,selClassMethod2);

/*4.交换实例方法的实现和类方法的实现*/

if(!InsMethod1||!InsMethod2){

NSLog(@"实例方法实现运行时交换失败!");

return;

}

/*交换实例方法的实现*/

method_exchangeImplementations(InsMethod1,InsMethod2);

if(!ClassMethod1||!ClassMethod2){

NSLog(@"类方法实珧运行时交换失败!");

return;

}

/*交换类方法的实现*/

method_exchangeImplementations(ClassMethod1,ClassMethod2);

}

测试代码:

#import"ViewController.h"

#import"Test.h"

@implementationViewController

-(void)viewDidLoad{

[superviewDidLoad];

/*测试类方法调用*/

[TestclassMethod1];

[TestclassMethod2];

Test*test=[[Testalloc]init];

/*测试实例方法调用*/

[testinstanceMethod1];

[testinstanceMethod2];

}

@end

通过下而的输出结果可知,两个实例方法和类方法的实现都被互换了。

2017-03-0617:47:13.684SingleView[41495:1196960]classMethod2...

2017-03-0617:47:13.684SingleView[41495:1196960]classMethod1...

2017-03-0617:47:13.685SingleView[41495:1196960]instanceMethod2...

2017-03-0617:47:13.685SingleView[41495:1196960]instanceMethod1...

2.重新设置类中某个方法的实现

设置函数:method_setImplementation(method,IMP)

理解了上面的例子,现在略微修改其中运行时代码,通过重新设置方法的实现来实现上面同样的效果。修改部分的运行时代码为:

/*获取方法的实现*/

IMPimpInsMethod1=method_getImplementation(InsMethod1);

IMPimpInsMethod2=method_getImplementation(InsMethod2);

IMPimpClassMethod1=method_getImplementation(ClassMethod1);

IMPimpClassMethod2=method_getImplementation(ClassMethod2);

/*重新设置方法的实现*/

/*重新设置instanceMethod1的实现为instanceMethod2的实现*/

method_setImplementation(InsMethod1,impInsMethod2);

/*重新设置instanceMethod2的实现为instanceMethod1的实现*/

method_setImplementation(InsMethod2,impInsMethod1):

/*重新设置classMethod1的实现为classMethod2的实现*/

method_setImplementation(ClassMethod1,impClassMethod2);

/*重新设置classMethod2的实现为classMethod1的实现*/

method_setImplementation(ClassMethod2,impClassMethod1);

运行后打印结果和上面方法实现交换的例子结果相同。

2017-03-0618:27:53.032SingleView[41879:1212691]classMethod2...

2017-03-0618:27:53.032SingleView[41879:1212691]classMethod1...

2017-03-0618:27:53.033SingleView[41879:1212691]instanceMethod2...

2017-03-0618:27:53.033SingleView[41879:1212691]instanceMethod1...

3.替换类中某个方法的实现

替换方法实现函数:class_replaceMethod(Classcls,SELname,IMPimp,constchar*types)。

这种方法只能替换实例方法的实现,而不能替换类方法的实现,修改的代码如下:

/*获取方法编码类型*/

constchar*typeInsMethod1=method_getTypeEncoding(InsMethod1);

constchar*typeInsMethod2=method_getTypeEncoding(InsMethod2);

constchar*typeClassMethod1=method_getTypeEncoding(ClassMethod1);

constchar*typeClassMethod2=method_getTypeEncoding(ClassMethod2);

/*替换InsMethod1的实现为InsMethod2的实现*/

class_replaceMethod(class,selInsMethod1,impInsMethod2,typeInsMethod2);

/*替换InsMethod2的实现为InsMethod1的实现*/

class_replaceMethod(class,selInsMethod2,impInsMethod1,typeInsMethod1);

/*尝试替换类方法的实现*/

class_replaceMethod(class,selClassMethod1,impClassMethod2,typeClassMethod2);

class_replaceMethod(class,selClassMethod2,impClassMethod1,typeClassMethod1);

通过结果可见实例方法的实现成功被替换,而类方法的实现没有被替换。

2017-03-0618:47:03.598SingleView[42106:1221468]classMethod1...

2017-03-0618:47:03.599SingleView[42106:1221468]classMethod2...

2017-03-0618:47:03.600SingleView[42106:1221468]instanceMethod2...

2017-03-0618:47:03.600SingleView[42106:1221468]instanceMethod1...

以上介绍的是同一个类中方法实现的再改动,实际上也可以修改或交换不同类之间方法的实现。

4.在运行时为类添加新的方法

添加函数:class_addMethod()。

除了在编译期显式的定义方法,还可以在运行时补加新的实例方法,但不可以添加新的类方法。这里接上面的例子为Test类在运行时添加一个新的名为newInsMethod的方法,方法的实现设置为InsMethod1的实现,修改后的运行时代码如下:

/*6.为类添加新的实例方法和类方法*/

SELselNewInsMethod=@selector(newInsMethod);

BOOL

isInsAdded=class_addMethod(class,selNewInsMethod,impInsMethod1,typeInsMethod1);

if(!isInsAdded){

NSLog(@"新实例方法添加失败!");

}

测试新函数代码如下:

/*测试运行时新添加实例方法调用*/

Test*test=[[Testalloc]init];

[testnewInsMethod];

运行结果打印出“instanceMethod1...”证明新实例方法动态添加成功。

2017-03-0619:07:15.447SingleView[42354:1230571]instanceMethod1...

此外,除了在运行时为类添加新的方法,还可以通过其他运行时函数classaddIvar、classaddProperty、class_addProtocol等动态地为类添加新的变量、属性和协议等。

16.

在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"];

17.

在runtime中类与对象如何表示?正确答案:在runtime库中,对象是用C语言中的结构体表示,而方法用C语言中的函数来实现,另外也加入了一些额外的特性。这些结构体和函数被Runtime函数封装后,开发者就可以利用运行时特性在程序运行阶段动态地创建、查看、修改类、对象和它们的方法。在iOS的SDK中,文件夹usr/include/objc下的文件基本都是和运行时相关的文件,其中使用的函数主要定义在message.h和runtime.h两个文件中。在这两个文件中,包含了运行时对象结构体的描述和对运行时进行操作的函数。

1.类(Class)

在runtime中,Objective-C中的类是由Class类型来表示的,通过objc/objc.h可以查看到Class类型实际上是指向objc_class结构的指针。它的定义如下:

///AnopaquetypethatrepresentsallObjective-Cclass.

typedefstructobjc_class*Class;

通过查看objc/runtime.h文件,可以看到objc_class结构体的定义如下:

structobjc_class{

Classisa

OBJC_ISA_AVAILABILITY;

#if!__OBJC2__

Classsuper_classOBJC2_UNAVAILABLE;

constchar*name

OBJC2_UNAVAILABLE;

longversion

OBJC2_UNAVAILABLE;

longinfo

OBJC2_UNAVAILABLE;

longinstance_size

OBJC2_UNAVAILABLE;

structobjc_iVar_list*ivarsOBJC2_UNAVAILABLE;

structobjc_method_list**methodListsOBJC2_UNAVAILABLE;

structobjc_cache*cache

OBJC2_UNAVAILABLE;

structobjc_protocol_list*protocols

OBJC2_UNAVAILABLE;

#endif

}OBJC2_UNAVAILABLE;

/*Use'Class'insteadof'structobjc_elass*'*/

在这个结构体中,有几个重要的字段,它们的含义如下。

1)isa:在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也存在一个isa指针。对象需要通过isa指针找到它的类,类需要通过isa找到它的元类(MetaClass)。元类可以理解为类对象的类,每个类都会有一个单独的元类。事实上,元类也是一个类,它也存在isa指针,它的isa指针指向父类的元类。也就是说,任何NSObject继承体系下的元类都使用NSObject的元类作为自己的所属类,而基类的元类的isa指针是指向它自己。这样就形成了一个完美的闭环。isa指针在调用实例方法和类方法的时候会起到重要的作用。

2)super_class:指向该类的父类,如果该类已经是最顶层的类(RootClass),那么super_class就是nil。

3)cache:它主要用于缓存类中最近使用的方法。当开发者调用过一个方法后,runtime会将这个方法缓存到cache列表中,如果再次调用这个方法,那么runtime就会优先去cache中查找,如果cache没有,那么就会去methodLists中查找该方法。这样就大大提高了调用方法的效率。

4)ivars:指向该类的成员变量链表。

5)methodLists:指向方法定义的链表。

6)protocols:指向协议链表。

这些类型的定义将一个类进行了完全的分解,将类的定义或者对象的每一个部分都抽象为一个类型Type,这样对操作一个类属性和方法非常方便。开发者可以通过runtime提供的函数获取类结构体中的各个类型的值。例如,如果想要获取Class的name,那么可以通过如下的方法获取。

/*导入runtime头文件*/

#import<objc/runtime.h>

#import<objc/message.h>

/*获取ClassName*/

ClasspersonClass=Person.class;

constchar*className=class_getName(personClass);

NSLog(@"%s",className);

2.对象(objc_object)

在objc/objc.h中,同样可以找到关于类的实例定义。

///Representsaninstanceofaclass.

structobjc_object{

Classisa

OBJC_ISA_AVAILABILITY;

};

从以上代码可以看到,在objc_object结构体中只有一个字段,即指向其类的isa指针。通过这个指针,runtime会找到这个实例对象所属的类。当创建一个特定类的实例对象时,分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据。NSObject类的alloc和allocWithZone:方法使用方法class_createInstance来创建objc_object数据结构。

在objc.h中还可以看到id类型的定义。

///Apointertoaninstanceofaclass.

typedefstructobjc_object*id:

显然,它也是一个objc_object结构类型的指针。它的存在可以让开发者实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一个种对象,有点类似C语言中void*指针类型的作用。

18.

如何使用CAShapeLayer绘制图层?正确答案:CAShapeLayer是一个通过矢量图形来进行绘制的图层子类。开发者通过指定诸如颜色和线宽等属性,用CGPath来定义指定形状的图形,最后CAShapeLayer对象就会自动渲染出来了。与直接使用CoreGraphics在原始的CALayer对象中绘制的方式相比,使用CAShapeLayer有以下优点:

1)渲染速度更快。CAShapeLayer使用了硬件加速的方式绘制图形,绘制速度比用CoreGraphics快很多。

2)内存使用更加高效。普通的CALayer对象需要创建一个寄宿图,而CAShapeLayer不需要,这样就节约了内存。

3)不会被边界图层裁剪掉。一个CAShapeLayer可以在图层边界绘制。CAShapeLayer的图层路径不会

温馨提示

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

评论

0/150

提交评论