黑马程序员技术交流社区
标题:
面试时如何优雅的谈论OC
[打印本页]
作者:
斯芬克斯
时间:
2016-9-15 11:21
标题:
面试时如何优雅的谈论OC
Objective-C 简称OC(下面以此代称),是在C语言的基础上,增加了一层最小的面向对象语言。是一种静态输入的语言,即“必须先声明数据中每个变量(或者容器)的数据类型”。但它是一个动态语言,代码中的某一部分可以在app运行的时候被扩展和修改(比如,在被编译之后)。OC完全兼容C语言,在代码中,可以混用c,甚至是c++代码。
面向对象三原则(封装,继承,多态)
面向对象具有四个基本特征:抽象,封装,继承和多态。
C语言是面向过程的语言(关注的是函数),OC,C++,JAVA,C#,PHP,Swift是面向对象的,面向过程关注的是解决问题涉及的步骤,而面向对象关注的是设计能够实现解决问题所需功能的类。抽象是面向对象的思想基础。
抽象包括两个方面,一是过程抽象,二是数据抽象。过程抽象是指任何一个明确定义功能的操作都可被使用者看作单个的实体看待,尽管这个操作实际上可能由一系列更低级的操作来完成。数据抽象定义了数据类型和施加于该类型对象上的操作,并限定了对象的值只能通过使用这些操作修改和观察。抽象是一种思想,封装继承和多态是这种思想的实现。
封装
封装是把过程和数据包围起来(即函数和数据结构,函数是行为,数据结构是描述),有限制的对数据的访问。面向对象基于这个基本概念开始的(因为面向对象更注重的是类),即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。一旦定义了一个对象的特性,则有必要决定这些特性的可见性,封装保证了模块具有较好的独立性,使得程序维护修改较为容易。对应用程序的修改仅限于类的内部,因而可以将应用程序修改带来的影响减少到最低限度。但是封装会导致并行效率问题,因为执行部分和数据部分被绑定在一起,制约了并行程度。面向对象思想将函数和数据绑在一起,扩大了代码重用时的粒度。而且封装下的拆箱装箱过程中也会导致内存的浪费。
继承
继承是一种层次模型,允许和鼓励类的重用,并提供了一种明确表述共性的方法。新类继承了原始类的特性,新类称为原始类的派生类(子类和父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。继承性很好的解决了软件的可重用性问题。但是,不恰当地使用继承导致的最大的一个缺陷特征就是高耦合(即“牵一发而动全身”,是设计类时层次没分清导致的)。解决方案是用组合替代继承。将模块拆开,然后通过定义好的接口进行交互,一般来说可以选择Delegate模式来交互。使用继承其实是如何给一类对象划分层次的问题。在正确的继承方式中,父类应当扮演的是底层的角色,子类是上层的业务。父类只是给子类提供服务,并不涉及子类的业务逻辑;层级关系明显,功能划分清晰;父类的所有变化,都需要在子类中体现,此时耦合已经成为需求。
多态
多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。很好的解决了应用程序函数同名问题,多态一般都要跟继承结合起来说,其本质是子类通过覆盖或重载父类的方法,来使得对同一类对象同一方法的调用产生不同的结果。覆盖是对接口方法的实现,继承中也可能会在子类覆盖父类中的方法。重载,是指我们可以定义一些名称相同的方法,通过定义不同的输入参数来区分这些方法,然后再调用时,VM就会根据不同的参数样式,来选择合适的方法执行。在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样); 但继承会引入多态使用混乱的境况并产生耦合,更好的方法是使用接口。通过IOP将子类与可能被子类引入的不相关逻辑剥离开来,提高了子类的可重用性,降低了迁移时可能的耦合。接口规范了子类哪些必须实现,哪些可选实现。那些不在接口定义的方法列表里的父类方法,事实上就是不建议覆重的方法。如果引入多态之后导致对象角色不够单纯,那就不应当引入多态,如果引入多态之后依旧是单纯角色,那就可以引入多态;如果要覆重的方法是角色业务的其中一个组成部分,那么就最好不要用多态的方案,用IOP,因为在外界调用的时候其实并不需要通过多态来满足定制化的需求。
动态性(Runtime)
Objective-C 是面相运行时的语言,它会尽可能的把编译和链接时要执行的逻辑延迟到运行时。使用Runtime可以按需要把消息重定向给合适的对象,交换方法的实现等等。
Runtime简称运行时,其中最主要的是消息机制,是一个主要使用 C 和汇编写的库,为 C 添加了面相对象的能力并创造了 Objective-C。。OC的函数调用称为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。
动态性的三方面
OC的动态特性表现为了三个方面:动态类型、动态绑定、动态加载。之所以叫做动态,是因为必须到运行时(runtime)才会做一些事情。
动态类型,就是id类型。动态类型是跟静态类型相对的。内置的基本类型都属于静态类型(int、NSString等)。静态类型在编译的时候就能被识别出来(即前面说的静态输入)。所以,若程序发生了类型不对应,编译器就会发出警告。而动态类型就编译器编译的时候是不能被识别的,要等到运行时(runtime),即程序运行的时候才会根据语境来识别。所以这里面就有两个概念要分清:编译时跟运行时。
动态语言和静态语言的一个区别是静态语言提前编译好文件,即所有的逻辑已在编译时确定,运行时直接加载编译后的文件;而动态语言是在运行时才确定实现。典型的静态语言是C++,动态语言包括OC,JAVA,C#等;因为静态语言提前编译好了执行文件,也就是通常所说的静态语言效率较高的原因。
动态绑定(dynamic binding)需要用到@selector/SEL。先来看看“函数”,对于其他一些静态语言,比如c++,一般在编译的时候就已经将要调用的函数的函数签名都告诉编译器了。静态的,不能改变。而在OC中,其实是没有函数的概念的,我们叫“消息机制”,所谓的函数调用就是给对象发送一条消息。这时,动态绑定的特性就来了。OC可以先跳过编译,到运行的时候才动态地添加函数调用,在运行时才决定要调用什么方法,需要传什么参数进去,这就是动态绑定。要实现他就必须用SEL变量绑定一个方法。最终形成的这个SEL变量就代表一个方法的引用。这里要注意一点:SEL并不是C里面的函数指针,虽然很像,但真心不是函数指针。SEL变量只是一个整数,他是该方法的ID。以前的函数调用,是根据函数名,也就是字符串去查找函数体。但现在,我们是根据一个ID整数来查找方法,整数的查找自然要比字符串的查找快得多!所以,动态绑定的特定不仅方便,而且效率更高。
动态加载就是根据需求动态地加载资源,在运行时加载新类。在运行时创建一个新类,只需要3步:
1、为 class pair分配存储空间 ,使用 objc_allocateClassPair函数
2、增加需要的方法使用class_addMethod函数,增加实例变量用class_addIvar
3 、用objc_registerClassPair函数注册这个类,以便它能被别人使用。
事件响应链
对于IOS设备用户来说,操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种:
1、触屏事件(Touch Event)
2、运动事件(Motion Event)
3、远端控制事件(Remote-Control Event)
事件的传递和响应分两个链:
传递链:由系统向离用户最近的view传递。
UIKit –> active app’s event queue –> window –> root view –>……–>lowest view
响应链:由离用户最近的view向系统传递。
initial view –> super view –> …..–> view controller –> window –> Application
响应者链(Responder Chain):由多个响应者对象连接起来的链条,作用是能很清楚的看见每个响应者之间的联系,并且可以让一个事件多个对象处理。
响应者对象(Responder Object),指的是有响应和处理事件能力的对象。响应者链就是由一系列的响应者对象构成的一个层次结构。
UIResponder是所有响应对象的基类,在UIResponder类中定义了处理上述各种事件的接口。我们熟悉的UIApplication、 UIViewController、UIWindow和所有继承自UIView的UIKit类都直接或间接的继承自UIResponder,所以它们的实例都是可以构成响应者链的响应者对象。
响应者链有以下特点:
1、响应者链通常是由视图(UIView)构成的;
2、一个视图的下一个响应者是它视图控制器(UIViewController)(如果有的话),然后再转给它的父视图(Super View);
3、视图控制器(如果有的话)的下一个响应者为其管理的视图的父视图;
4、单例的窗口(UIWindow)的内容视图将指向窗口本身作为它的下一个响应者,Cocoa Touch应用不像Cocoa应用,它只有一个UIWindow对象,因此整个响应者链要简单一点;
5、单例的应用(UIApplication)是一个响应者链的终点,它的下一个响应者指向nil,以结束整个循环。
iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队列,单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理,UIWindow对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为hit-test view。
UIWindow实例对象会首先在它的内容视图上调用hitTest:withEvent:,此方法会在其视图层级结构中的每个视图上调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是要找的hit-test view。
hitTest:withEvent:方法的处理流程如下:
首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;若返回NO,则hitTest:withEvent:返回nil;若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。
生命周期
app应用程序有5种状态:
Not running未运行:程序没启动。
Inactive未激活:程序在前台运行,不过没有接收到事件。在没有事件处理情况下程序通常停留在这个状态。
Active激活:程序在前台运行而且接收到了事件。这也是前台的一个正常的模式。
Backgroud后台:程序在后台而且能执行代码,大多数程序进入这个状态后会在在这个状态上停留一会。时间到之后会进入挂起状态(Suspended)。有的程序经过特殊的请求后可以长期处于Backgroud状态。
Suspended挂起:程序在后台不能执行代码。系统会自动把程序变成这个状态而且不会发出通知。当挂起时,程序还是停留在内存中的,当系统内存低时,系统就把挂起的程序清除掉,为前台程序提供更多的内存。
iOS的入口在main.m文件的main函数,根据UIApplicationMain函数,程序将进入AppDelegate.m,这个文件是xcode新建工程时自动生成的。AppDelegate.m文件,关乎着应用程序的生命周期。
1、application didFinishLaunchingWithOptions:当应用程序启动时执行,应用程序启动入口,只在应用程序启动时执行一次。若用户直接启动,lauchOptions内无数据,若通过其他方式启动应用,lauchOptions包含对应方式的内容。
2、applicationWillResignActive:在应用程序将要由活动状态切换到非活动状态时候,要执行的委托调用,如 按下 home 按钮,返回主屏幕,或全屏之间切换应用程序等。
3、applicationDidEnterBackground:在应用程序已进入后台程序时,要执行的委托调用。
4、applicationWillEnterForeground:在应用程序将要进入前台时(被激活),要执行的委托调用,刚好与applicationWillResignActive 方法相对应。
5、applicationDidBecomeActive:在应用程序已被激活后,要执行的委托调用,刚好与applicationDidEnterBackground 方法相对应。
6、applicationWillTerminate:在应用程序要完全推出的时候,要执行的委托调用,这个需要要设置UIApplicationExitsOnSuspend的键值。
初次启动:
iOS_didFinishLaunchingWithOptions
iOS_applicationDidBecomeActive
按下home键:
iOS_applicationWillResignActive
iOS_applicationDidEnterBackground
点击程序图标进入:
iOS_applicationWillEnterForeground
iOS_applicationDidBecomeActive
当应用程序进入后台时,应该保存用户数据或状态信息,所有没写到磁盘的文件或信息,在进入后台时,最后都写到磁盘去,因为程序可能在后台被杀死。释放尽可能释放的内存。
1
- (void)applicationDidEnterBackground:(UIApplication *)application
方法有大概5秒的时间让你完成这些任务。如果超过时间还有未完成的任务,你的程序就会被终止而且从内存中清除。
如果还需要长时间的运行任务,可以在该方法中调用
1
2
3
[application beginBackgroundTaskWithExpirationHandler:^{
NSLog(@"begin Background Task With Expiration Handler");
}];
程序终止
程序只要符合以下情况之一,只要进入后台或挂起状态就会终止:
①iOS4.0以前的系统
②app是基于iOS4.0之前系统开发的。
③设备不支持多任务
④在Info.plist文件中,程序包含了 UIApplicationExitsOnSuspend 键。
系统常常是为其他app启动时由于内存不足而回收内存最后需要终止应用程序,但有时也会是由于app很长时间才响应而终止。如果app当时运行在后台并且没有暂停,系统会在应用程序终止之前调用app的代理的方法 - (void)applicationWillTerminate:(UIApplication *)application,这样可以让你可以做一些清理工作。你可以保存一些数据或app的状态。这个方法也有5秒钟的限制。超时后方法会返回程序从内存中清除。用户可以手工关闭应用程序。
和其他动态语言的区别
OC中方法的实现只能写在@implementation··@end中,对象方法的声明只能写在@interface···@end中间;对象方法都以-号开头,类方法都以+号开头;函数属于整个文件,可以写在文件中的任何位置,包括@interface··@end中,但写在@interface···@end会无法识别;
对象方法只能由对象来调用,类方法只能由类来调用,不能当做函数一样调用,对象方法归类\\对象所有;类方法调用不依赖于对象;类方法内部不能直接通过成员变量名访问对象的成员变量。OC只支持单继承,没有接口,但可以用delegate代替。
Objective-C与其他语言最大的区别是其运行时的动态性,它能让你在运行时为类添加方法或者去除方法以及使用反射。极大的方便了程序的扩展。
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/)
黑马程序员IT技术论坛 X3.2