Objective-C原理系列(一)

Objective-C 简称OC(下面以此代称),是在C语言的基础上,增长了一层最小的面向对象语言。是一种静态输入的语言,即“必须先声明数据中每一个变量(或者容器)的数据类型”。但它是一个动态语言,代码中的某一部分能够在app运行的时候被扩展和修改(好比,在被编译以后)。OC彻底兼容C语言,在代码中,能够混用c,甚至是c++代码。c++

面向对象三原则(封装,继承,多态)

面向对象具备四个基本特征:抽象,封装,继承和多态。数组

C语言是面向过程的语言(关注的是函数),OC,C++,JAVA,C#,PHP,Swift是面向对象的,面向过程关注的是解决问题涉及的步骤,而面向对象关注的是设计可以实现解决问题所需功能的类。抽象是面向对象的思想基础。xcode

抽象包括两个方面,一是过程抽象,二是数据抽象。过程抽象是指任何一个明肯定义功能的操做均可被使用者看做单个的实体看待,尽管这个操做实际上可能由一系列更低级的操做来完成。数据抽象定义了数据类型和施加于该类型对象上的操做,并限定了对象的值只能经过使用这些操做修改和观察。抽象是一种思想,封装继承和多态是这种思想的实现。安全

封装数据结构

封装是把过程和数据包围起来(即函数和数据结构,函数是行为,数据结构是描述),有限制的对数据的访问。面向对象基于这个基本概念开始的(由于面向对象更注重的是类),即现实世界能够被描绘成一系列彻底自治、封装的对象,这些对象经过一个受保护的接口访问其余对象。一旦定义了一个对象的特性,则有必要决定这些特性的可见性,封装保证了模块具备较好的独立性,使得程序维护修改较为容易。对应用程序的修改仅限于类的内部,于是能够将应用程序修改带来的影响减小到最低限度。可是封装会致使并行效率问题,由于执行部分和数据部分被绑定在一块儿,制约了并行程度。面向对象思想将函数和数据绑在一块儿,扩大了代码重用时的粒度。并且封装下的拆箱装箱过程当中也会致使内存的浪费。多线程

继承app

继承是一种层次模型,容许和鼓励类的重用,并提供了一种明确表述共性的方法。新类继承了原始类的特性,新类称为原始类的派生类(子类和父类)。派生类能够从它的基类那里继承方法和实例变量,而且类能够修改或增长新的方法使之更适合特殊的须要。继承性很好的解决了软件的可重用性问题。可是,不恰当地使用继承致使的最大的一个缺陷特征就是高耦合(即“牵一发而动全身”,是设计类时层次没分清致使的)。解决方案是用组合替代继承。将模块拆开,而后经过定义好的接口进行交互,通常来讲能够选择Delegate模式来交互。使用继承实际上是如何给一类对象划分层次的问题。在正确的继承方式中,父类应当扮演的是底层的角色,子类是上层的业务。父类只是给子类提供服务,并不涉及子类的业务逻辑;层级关系明显,功能划分清晰;父类的全部变化,都须要在子类中体现,此时耦合已经成为需求。框架

多态ide

多态性是指容许不一样类的对象对同一消息做出响应。多态性包括参数化多态性和包含多态性。很好的解决了应用程序函数同名问题,多态通常都要跟继承结合起来讲,其本质是子类经过覆盖或重载父类的方法,来使得对同一类对象同一方法的调用产生不一样的结果。覆盖是对接口方法的实现,继承中也可能会在子类覆盖父类中的方法。重载,是指咱们能够定义一些名称相同的方法,经过定义不一样的输入参数来区分这些方法,而后再调用时,VM就会根据不一样的参数样式,来选择合适的方法执行。在使用重载时只能经过不一样的参数样式。例如,不一样的参数类型,不一样的参数个数,不一样的参数顺序(固然,同一方法内的几个参数类型必须不同); 但继承会引入多态使用混乱的境况并产生耦合,更好的方法是使用接口。经过IOP将子类与可能被子类引入的不相关逻辑剥离开来,提升了子类的可重用性,下降了迁移时可能的耦合。接口规范了子类哪些必须实现,哪些可选实现。那些不在接口定义的方法列表里的父类方法,事实上就是不建议覆重的方法。若是引入多态以后致使对象角色不够单纯,那就不该当引入多态,若是引入多态以后依旧是单纯角色,那就能够引入多态;若是要覆重的方法是角色业务的其中一个组成部分,那么就最好不要用多态的方案,用IOP,由于在外界调用的时候其实并不须要经过多态来知足定制化的需求。函数

动态性(Runtime)

Objective-C 是面相运行时的语言,它会尽量的把编译和连接时要执行的逻辑延迟到运行时。使用Runtime能够按须要把消息重定向给合适的对象,交换方法的实现等等。

Runtime简称运行时,其中最主要的是消息机制,是一个主要使用 C 和汇编写的库,为 C 添加了面相对象的能力并创造了 Objective-C。。OC的函数调用称为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪一个函数(在编 译阶段,OC能够调用任何函数,即便这个函数并未实现,只要声明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。

如:

[obj makeText];
==》
objc_msgSend(obj,@selector(makeText));

编译器执行上述转换。在objc_msgSend函数中,首先经过obj的isa指针找到obj对应的class。每一个对象内部都默认有一个isa指针指向这个对象所使用的类。isa是对象中的隐藏指针,指向建立这个对象的类。在Class中先去cache中经过SEL查找对应函数method(cache中method列表是以SEL为key经过hash表来存储的,这样能提升函数查找速度),若cache中未找到,再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加入到cache中,以方便下次查找,并经过method中的函数指针跳转到对应的函数中去执行。

动态性的三方面

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步:

一、为 class pair分配存储空间 ,使用 objc_allocateClassPair函数

二、增长须要的方法使用class_addMethod函数,增长实例变量用class_addIvar

3 、用objc_registerClassPair函数注册这个类,以便它能被别人使用。

Method Swizzling

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

用 method_exchangeImplementations 来交换2个方法中的IMP,
用 class_replaceMethod 来修改类,
用 method_setImplementation 来直接设置某个方法的IMP,归根结底,都是偷换了selector的IMP。

RunLoop

RunLoop是一让线程能随时处理事件但不退出的机制。RunLoop其实是一个对象,这个对象管理了其须要处理的事件和消息,并提供了一个入口函数来执行Event Loop的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(好比传入 quit 的消息),函数返回。让线程在没有处理消息时休眠以免资源占用、在有消息到来时马上被唤醒。一个runloop就是一个事件处理循环,用来不停的监听和处理输入事件并将其分配到对应的目标上进行处理。

RunLoop的四个做用为:使程序一直运行接受用户输入;决定程序在什么时候应该处理哪些Event;调用解耦;节省CPU时间。

线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚建立时并无 RunLoop,若是你不主动获取,那它一直都不会有。RunLoop 的建立是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其RunLoop(主线程除外)。

主线程的runloop默认是启动的。

OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop和CFRunLoopRef。CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,全部这些 API 都是线程安全的。NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,可是这些 API 不是线程安全的。

NSRunLoop是一种更加高明的消息处理模式,在对消息处理过程进行了更好的抽象和封装,不用处理一些很琐碎很低层次的具体消息的处理,在NSRunLoop中每个消息就被打包在input source或者是timer source中了。使用run loop可使你的线程在有工做的时候工做,没有工做的时候休眠,能够大大节省系统资源。

对其它线程来讲,runloop默认是没有启动的,若是你须要更多的线程交互则能够手动配置和启动,若是线程只是去执行一个长时间的已肯定的任务则不须要。在任何一个Cocoa程序的线程中,均可以经过:

NSRunLoop   *runloop = [NSRunLoop currentRunLoop];

获取到当前线程的runloop。

Cocoa中的NSRunLoop类并非线程安全的
咱们不能在一个线程中去操做另一个线程的runloop对象,那极可能会形成意想不到的后果。可是CoreFundation中的不透明类CFRunLoopRef是线程安全的,并且两种类型的runloop彻底能够混合使用。Cocoa中的NSRunLoop类能够经过实例方法:

- (CFRunLoopRef)getCFRunLoop;

获取对应的CFRunLoopRef类,来达到线程安全的目的。

Runloop的管理并不彻底是自动的。咱们仍必须设计线程代码以在适当的时候启动runloop并正确响应输入事件,固然前提是线程中须要用到runloop。并且,咱们还须要使用while/for语句来驱动runloop可以循环运行,下面的代码就成功驱动了一个run loop:

BOOL isRunning = NO;
do {
 isRunning = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDatedistantFuture]];
} while (isRunning);

Runloop同时也负责autorelease pool的建立和释放
在使用手动的内存管理方式的项目中,会常常用到不少自动释放的对象,若是这些对象不可以被即时释放掉,会形成内存占用量急剧增大。Runloop就为咱们作了这样的工做,每当一个运行循环结束的时候,它都会释放一次autorelease pool,同时pool中的全部自动释放类型变量都会被释放掉。

系统默认注册了5个Mode:

kCFRunLoopDefaultMode: App的默认 Mode,一般主线程是在这个 Mode 下运行的。

UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其余 Mode 影响。

UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就再也不使用。

GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,一般用不到。

kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际做用。

轮播图中的NSTimer问题
建立定时器:

1:NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(changeImage) userInfo:nil repeats:YES];

此方法建立的定时器,必须加到NSRunLoop中。

NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 
[runLoop addTimer:timer forMode: NSRunLoopCommonModes];

forMode的参数有两种类型可供选择: NSDefaultRunLoopMode , NSRunLoopCommonModes,第一个参数为默认参数,当下面有textView,textfield等控件时,拖拽控件,此时轮播器会中止轮播,是由于NSRunLoop的缘由,NSRunLoop为一个死循环,实时监测有无事件响应,若是当前线程就是主线程,也就是UI线程时,某些UI事件,好比UIScrollView的拖动操做,会将Run Loop切换成NSEventTrackingRunLoopMode模式,在这个过程当中,默认的NSDefaultRunLoopMode模式中注册的事件是不会被执行的。NSRunLoopCommonModes 可以在多线程中起做用,这个模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的结合,这也是将modes换为NSRunLoopCommonModes即可解决的缘由。

2: self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(changeImage) userInfo:nil repeats:YES];

此种建立定时器的方式,默认加到了runloop,且默认为第二个参数。

main函数的运行

在main.m中:

int main(int argc, char *argv[])
 {
    @autoreleasepool {
      return UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));
   }
 }

UIApplicationMain() 函数会为main thread 设置一个NSRunLoop 对象,这就解释了app应用能够在无人操做的时候休息,须要让它干活的时候又能立马响应。

仅当在为你的程序建立辅助线程的时候,你才须要显式运行一个runloop。Runloop是程序主线程基础设施的关键部分,因此,Cocoa和Carbon程序提供了代码运行主程序的循环并自动启动runloop。IOS程序中UIApplication的run方法(或Mac OS X中的NSApplication)做为程序启动步骤的一部分,它在程序正常启动的时候就会启动程序的主循环。若是你使用xcode提供的模板建立你的程序,那你永远不须要本身去显式的调用这些例程。

对于辅助线程,你须要判断一个runloop是不是必须的。若是是必须的,那么你要本身配置并启动它。你不须要在任何状况下都去启动一个线程的runloop。好比,你使用线程来处理一个预先定义的长时间运行的任务时,你应该避免启动runloop。Runloop在你要和线程有更多的交互时才须要,好比如下状况:

1.使用端口或自定义输入源来和其余线程通讯;

2.使用线程的定时器;

3.Cocoa中使用任何performSelector...的方法;

4.使线程周期性工做;

事件响应链

对于IOS设备用户来讲,操做设备的方式主要有三种:触摸屏幕、晃动设备、经过遥控设施控制设备。对应的事件类型有如下三种:

一、触屏事件(Touch Event)

二、运动事件(Motion Event)

三、远端控制事件(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,因此它们的实例都是能够构成响应者链的响应者对象。

响应者链有如下特色:

一、响应者链一般是由视图(UIView)构成的;

二、一个视图的下一个响应者是它视图控制器(UIViewController)(若是有的话),而后再转给它的父视图(Super View);

三、视图控制器(若是有的话)的下一个响应者为其管理的视图的父视图;

四、单例的窗口(UIWindow)的内容视图将指向窗口自己做为它的下一个响应者,Cocoa Touch应用不像Cocoa应用,它只有一个UIWindow对象,所以整个响应者链要简单一点;

五、单例的应用(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)。

引用计数器(ARC 和 MRC)

ARC:自动引用计数器(Automatic Reference Counting)

MRC:手动引用计算器(因为如今几乎不用了,不作过多解说)

Objective-c中提供了两种内存管理机制MRC(MannulReference Counting)和ARC(Automatic Reference Counting),分别提供对内存的手动和自动管理,来知足不一样的需求。Xcode 4.1及其之前版本没有ARC。

在MRC的内存管理模式下,与对变量的管理相关的方法有:retain,release和autorelease。retain和release方法操做的是引用记数,当引用记数为零时,便自动释放内存。而且能够用NSAutoreleasePool对象,对加入自动释放池(autorelease调用)的变量进行管理,当内存紧张时回收内存。
(1) retain,该方法的做用是将内存数据的全部权附给另外一指针变量,引用数加1,即retainCount+= 1;
(2) release,该方法是释放指针变量对内存数据的全部权,引用数减1,即retainCount-= 1;
(3) autorelease,该方法是将该对象内存的管理放到autoreleasepool中。

在ARC中与内存管理有关的标识符,能够分为变量标识符和属性标识符,对于变量默认为__strong,而对于属性默认为unsafe_unretained。也存在autoreleasepool。

其中assign/retain/copy与MRC下property的标识符意义相同,strong相似与retain,assign相似于unsafe_unretainedstrong/weak/unsafe_unretained与ARC下变量标识符意义相同,只是一个用于属性的标识,一个用于变量的标识(带两个下划短线__)。

生命周期

app应用程序有5种状态:

Not running未运行:程序没启动。

Inactive未激活:程序在前台运行,不过没有接收到事件。在没有事件处理状况下程序一般停留在这个状态。

Active激活:程序在前台运行并且接收到了事件。这也是前台的一个正常的模式。

Backgroud后台:程序在后台并且能执行代码,大多数程序进入这个状态后会在在这个状态上停留一会。时间到以后会进入挂起状态(Suspended)。有的程序通过特殊的请求后能够长期处于Backgroud状态。

Suspended挂起:程序在后台不能执行代码。系统会自动把程序变成这个状态并且不会发出通知。当挂起时,程序仍是停留在内存中的,当系统内存低时,系统就把挂起的程序清除掉,为前台程序提供更多的内存。

iOS的入口在main.m文件的main函数,根据UIApplicationMain函数,程序将进入AppDelegate.m,这个文件是xcode新建工程时自动生成的。AppDelegate.m文件,关乎着应用程序的生命周期。

一、application didFinishLaunchingWithOptions:当应用程序启动时执行,应用程序启动入口,只在应用程序启动时执行一次。若用户直接启动,lauchOptions内无数据,若经过其余方式启动应用,lauchOptions包含对应方式的内容。

二、applicationWillResignActive:在应用程序将要由活动状态切换到非活动状态时候,要执行的委托调用,如 按下 home 按钮,返回主屏幕,或全屏之间切换应用程序等。

三、applicationDidEnterBackground:在应用程序已进入后台程序时,要执行的委托调用。

四、applicationWillEnterForeground:在应用程序将要进入前台时(被激活),要执行的委托调用,恰好与applicationWillResignActive 方法相对应。

五、applicationDidBecomeActive:在应用程序已被激活后,要执行的委托调用,恰好与applicationDidEnterBackground 方法相对应。

六、applicationWillTerminate:在应用程序要彻底推出的时候,要执行的委托调用,这个须要要设置UIApplicationExitsOnSuspend的键值。

初次启动:

iOS_didFinishLaunchingWithOptions

iOS_applicationDidBecomeActive

按下home键:

iOS_applicationWillResignActive

iOS_applicationDidEnterBackground

点击程序图标进入:

iOS_applicationWillEnterForeground

iOS_applicationDidBecomeActive

当应用程序进入后台时,应该保存用户数据或状态信息,全部没写到磁盘的文件或信息,在进入后台时,最后都写到磁盘去,由于程序可能在后台被杀死。释放尽量释放的内存。

- (void)applicationDidEnterBackground:(UIApplication *)application

方法有大概5秒的时间让你完成这些任务。若是超过期间还有未完成的任务,你的程序就会被终止并且从内存中清除。

若是还须要长时间的运行任务,能够在该方法中调用

[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://www.jianshu.com/p/7f3c78dcd3b5 著做权归做者全部,转载请联系做者得到受权,并标注“简书做者”。

相关文章
相关标签/搜索