长路漫漫,唯剑作伴--OC

时间:2022-07-01 05:33:57

一、什么是抽象?

  • C语言是面向过程的语言,OC是面向对象的语言。面向过程关注的是解决问题所涉及的具体步骤,而面向对象所关注的是设计出能够解决问题的类。
  • 抽象是面向对象的思想基础。封装、继承和多态是这种思想的实现。
  • 抽象包括两个方面,过程抽象和数据抽象。过程抽象是指任何一个明确了具体功能的操作都可以被当做一个实体对待。数据抽象是指定义数据类型以及施加于该数据类型上的操作,读取以及改变这些数据类型只能通过这些操作来实现,数据抽象限制了访问这些数据途径。

二、面向对象三原则:封装、继承、多态

  1. 封装

    • 封装是把过程和数据包围起来,形成有限制的数据访问。一旦定义了封装,就必须定义接口保证封装对象的可见性。
    • 封装使模块具有良好的独立性,对应用程序的修改限制在了封装对象的外部,更有利于代码的维护。
    • 然而封装虽然扩大了代码重用的粒度,但拆箱过程也会导致内存资源的浪费。
  2. 继承

    • 继承是一个层次模型,允许和鼓励类的重用,继承提供了一种表述共性的方式。
    • 新类继承了父类的方法和属性,并且可以新增方法或属性完成特殊需求,很好地解决了重用的问题。
    • 然而不恰当的继承容易导致高耦合,改变基类的属性或者方法,会使子类随之发生改变(牵一发动全身)。解决方法是组合代替继承,将模块拆开,通过定义好的接口进行交互,一般来说可以使用delegate进行交互。
    • 在正确的继承方式中,父类应该扮演底层的角色,子类完成上层业务,父类只向子类提供服务,不牵涉到子类业务逻辑,层次、功能划分明确,父类的所有变化,在子类中应该有所体现,此时耦合已经成为需要。
  3. 多态

    • 多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。很好的解决了应用程序函数同名问题。
    • 多态一般都要跟继承结合起来说,其本质是子类通过覆盖或重载父类的方法,来使得对同一类对象同一方法的调用产生不同的结果。覆盖是对接口方法的实现,继承中也可能会在子类覆盖父类中的方法。

三、什么是OC?

  1. 简介

    • OC是基于C语言的基础之上增加了一层小的面向对象的语言。同时也是一种动态语言。它将很多静态语言在编译和链接时期所做的事放到了运行时来处理。这样做的优势在于我们写的代码更具有灵活性,例如可以把消息转发给我们想要的对象,随意交换两个方法的实现等等。
    • OC的动态性决定了它不仅需要一个编译器,而且需要一个运行时系统执行编译的代码。这个运行时系统可以让所有的工作可以正常执行,这个运行时系统就是Runtime。OC Runtime其实是一个库,它基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力(后面会详细解释这个库)。
  2. 特点

    • 不支持多继承。
    • 动态。

四、运行时

  1. Runtime所做的事情

    • 封装:在这个库中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,另外再加上了一些额外的特性。这些结构体和函数被Runtime函数封装后,我们就可以在程序运行时创建,检查,修改类、对象和它们的方法了。
    • 找出方法最终执行的代码:当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。
  2. OC中函数调用过程

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

    1. 什么是消息转发

      • 如果在函数调用阶段无法找到方法的实现,可以利用消息转发机制进行处理。
    2. 消息转发的实现过程

      // 默认方法都有两个隐式参数,
      void eat(id self,SEL sel) {
          NSLog(@"%@ %@",self,NSStringFromSelector(sel));
      }
      // 第一步:动态方法解析
      // 对象在收到无法解读的消息后,首先将调用其所属类的下列类方法:
      // resolve : [rɪˈzɑ:lv] 决定
      // instance : [ˈɪnstəns] 实例
      + (BOOL)resolveInstanceMethod:(SEL)sel {
          if (sel  == @selector(eat)) {
              // 第一个参数 : 方法接收者
              // 第二个参数 : 方法名
              // 第三个参数 : 方法指针
              // 第四个参数 : 方法签名,V代表方法返回值为void,@表示方法接受者,:表示_cmd,即方法名
              class_addMethod(self, @selector(eat), (IMP)eat, "v@:");
          }
          return [super resolveInstanceMethod:sel];
      }
      
      + (BOOL)resolveClassMethod:(SEL)sel {
          return [super resolveClassMethod:sel];
      }
      // 第二步:备援接收者
      // 如果动态方法解析无法处理消息,实现此方法可以把这条消息转给其他接收者来处理
      // forwarding : ['fɔ:wədɪŋ] 转发
      // Target 目标
      - (id)forwardingTargetForSelector:(SEL)aSelector {
          return [[Drink alloc] init];
      }
      // 第三步:完整的消息转发
      - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSString *sel = NSStringFromSelector(aSelector); if ([sel isEqualToString:@"drink"]) { return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { SEL sel = anInvocation.selector; Drink *drink = [[Drink alloc] init]; if ([drink respondsToSelector:sel]) { [anInvocation invokeWithTarget:drink]; } }
  4. initialize和load

    • initialize会在第一次初始化这个类之前被调用,我们用它来初始化静态变量
    • load当类被加载到runtime的时候就会运行
    • 二者的先后顺序是initialize------load
    • 没有继承关系时二者在同一个类中只能被调用一次
    • +load():通常用来进行Method Swizzle,尽量避免过于复杂以及不必要的代码
    • +initialize():一般用于初始化全局变量或静态变量
  5. 最终解释

    • Runtime 是一套比较底层的纯C语言API, 它是OC的幕后工作者。
    • 我们平时写的OC代码在运行时都会编译器转为runtime的C语言代码。
    • 其中最主要的是消息机制OC的函数调用成为消息发送 属于动态调用过程 在编译的时候并不能决定真正调用哪个函数。
    • 事实证明, 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。
    • 而C语言在编译阶段就会报错 只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
  6. 动态性三方面:动态类型、动态绑定、动态加载

    1. 动态类型

      • 即id类型。动态类型和静态类型是相对的,如NSString、int等就属于静态类型。
      • 在静态类型中,如果数据类型不对应,就会提示警告。而动态类型则不会,动态类型会在运行时决定其属于哪一种数据类型。
    2. 动态绑定

      • 动态语言和静态语言一个区别就是,静态语言在编译时就已经确定了其逻辑,而动态语言则会在运行时决定。这是静态语言运行效率搞的原因之一。
      • 其实在OC中并没有函数调用这一概念,函数调用被称为消息发送,所谓消息发送就是给对象发送一条消息。
    3. 动态加载

  7. 使用

    1. 遍历

      #pragma mark--实例变量
      - (void)queryProperty{
          unsigned int count;
          objc_property_t *propertyList = class_copyPropertyList([Person class], &count);
          for (unsigned int i = 0; i < count; i++) {
              objc_property_t pro = propertyList[i];
              const char *key = property_getName(pro);
              NSString *keyString = [NSString stringWithUTF8String:key];
              NSLog(@"%@",keyString);
          }
      }
      #pragma mark--成员变量
      - (void)qyeryVar{
          unsigned int count;
          Ivar *ivar = class_copyIvarList([Person class], &count);
          for (unsigned int i = 0; i < count; i++) {
              Ivar var = ivar[i];
              const char *key = ivar_getName(var);
              NSString *keyString = [NSString stringWithUTF8String:key];
              NSLog(@"%@",keyString);
          }
      }
      #pragma mark--方法遍历
      - (void)querymethod{
          unsigned int count;
          Method *methodList = class_copyMethodList([Person class], &count);
          for (unsigned int i = 0; i < count; i++) {
              Method method = methodList[i];
              SEL select = method_getName(method);
              NSString *selectString = NSStringFromSelector(select);
              NSLog(@"%@",selectString);
          }
      }
    2. 动态添加方法

      @implementation ViewController
      
      - (void)viewDidLoad {
          [super viewDidLoad];
          // Do any additional setup after loading the view, typically from a nib.
      
          Person *p = [[Person alloc] init];
          // 默认person,没有实现eat方法,可以通过performSelector调用,但是会报错。
          // 动态添加方法就不会报错
          [p performSelector:@selector(eat)];
      }
      @end
      
      
      @implementation Person
      // void(*)()
      // 默认方法都有两个隐式参数,
      void eat(id self,SEL sel)
      {
          NSLog(@"%@ %@",self,NSStringFromSelector(sel));
      }
      
      // 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
      // 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
      + (BOOL)resolveInstanceMethod:(SEL)sel
      {
      
          if (sel == @selector(eat)) {
              // 动态添加eat方法
      
              // 第一个参数:给哪个类添加方法
              // 第二个参数:添加方法的方法编号
              // 第三个参数:添加方法的函数实现(函数地址)
              // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
              class_addMethod(self, @selector(eat), eat, "v@:");
      
          }
      
          return [super resolveInstanceMethod:sel];
      }
      @end
    3. 动态添加属性

      // 定义关联的key
      static const char *key = "name";
      
      @implementation NSObject (Property)
      
      - (NSString *)name
      {
          // 根据关联的key,获取关联的值。
          return objc_getAssociatedObject(self, key);
      }
      
      - (void)setName:(NSString *)name
      {
          // 第一个参数:给哪个对象添加关联
          // 第二个参数:关联的key,通过这个key获取
          // 第三个参数:关联的value
          // 第四个参数:关联的策略
          objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
      }
    4. Method Swizzling

      //load方法会在类第一次加载的时候被调用
      //调用的时间比较靠前,适合在这个方法里做方法交换
      + (void)load{
          //方法交换应该被保证,在程序中只会执行一次
          static dispatch_once_t onceToken;
          dispatch_once(&onceToken, ^{
              
              //获得viewController的生命周期方法的selector
              SEL systemSel = @selector(viewWillAppear:);
              //自己实现的将要被交换的方法的selector
              SEL swizzSel = @selector(swiz_viewWillAppear:);
              //两个方法的Method
              Method systemMethod = class_getInstanceMethod([self class], systemSel);
              Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
              
              //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
      //        class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)
              BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
              if (isAdd) {
                  //如果成功,说明类中不存在这个方法的实现
                  //将被交换方法的实现替换到这个并不存在的实现
                  class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
              }else{
                  //否则,交换两个方法的实现
                  method_exchangeImplementations(systemMethod, swizzMethod);
              }
              
          });
      }
      
      - (void)swiz_viewWillAppear:(BOOL)animated{
          //这时候调用自己,看起来像是死循环
          //但是其实自己的实现已经被替换了
          [self swiz_viewWillAppear:animated];
          NSLog(@"swizzle");
      }
    5. 字典转模型

      #import "NSObject+Model.h"
      #import <objc/runtime.h>
      
      @implementation NSObject (Model)
      
      + (NSArray *)propertList
      {
          unsigned int count = 0;
          //获取模型属性, 返回值是所有属性的数组 objc_property_t
          objc_property_t *propertyList = class_copyPropertyList([self class], &count);
          NSMutableArray *arr = [NSMutableArray array];
          //便利数组
          for (int i = 0; i< count; i++) {
              //获取属性
              objc_property_t property = propertyList[i];
              //获取属性名称
              const char *cName = property_getName(property);
              NSString *name = [[NSString alloc]initWithUTF8String:cName];
              //添加到数组中
              [arr addObject:name];
          }
          //释放属性组
          free(propertyList);
          return arr.copy;
      }
      
      + (instancetype)modelWithDict:(NSDictionary *)dict
      {
          id obj = [self new];
          // 遍历属性数组
          for (NSString *property in [self propertList]) {
              // 判断字典中是否包含这个key
              if (dict[property]) {
                  // 使用 KVC 赋值
                  [obj setValue:dict[property] forKey:property];
              }
          }
          return obj;
      }
      
      @end

五、Runloop

  1. 简介

    • 一般来讲,一个线程只能执行一个任务,执行完之后就会退出。Runloop提供了一个入口函数,实现让事件随时处理消息而不退出的机制。一旦执行了这个函数之后,线程就会处于接受消息-处理-等待的循环当中。
    • eventLoop的逻辑,伪代码演示
      function loop() {
          initialize();
          do {
              var message = get_next_message();
              process_message(message);
          } while (message != quit);
      }
  2. 作用

    • 使程序一直运行接受用户输入;
    • 决定程序在何时应该处理哪些Event;
    • 调用解耦;
    • 节省CPU时间。
  3. 系统提供的两种Runloop:NSRunLoop、CFRunLoopRef

    1. CFRunLoopRef

      • CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
    2. NSRunLoop

      • NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
      • NSRunLoop是一种更加高明的消息处理模式,在对消息处理过程进行了更好的抽象和封装,不用处理一些很琐碎很低层次的具体消息的处理,在NSRunLoop中每一个消息就被打包在input source或者是timer source中了。使用run loop可以使你的线程在有工作的时候工作,没有工作的时候休眠,可以大大节省系统资源。     
  4. Runloop的对外接口

    1. Core Foundation中关于Runloop的接口有5个类

      • CFRunLoopRef
      • CFRunLoopModeRef
      • CFRunLoopSourceRef
      • CFRunLoopTimerRef
      • CFRunLoopObserverRef
    2. CFRunLoopModeRef类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:长路漫漫,唯剑作伴--OC

      • 一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
    3. CFRunLoopSourceRef是事件产生的地方。Source有两个版本:Source0 和 Source1。

      • Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
      • Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。
    4. CFRunLoopTimerRef 是基于时间的触发器

      • 它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。
      • 当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
    5. CFRunLoopObserverRef 是观察者,

      • 每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。
        - (void)observerRunLoop {
            // 获得当前thread的Run loop
            // 凡是corefoundation里面的ref 代表 指针
            CFRunLoopRef runloop = CFRunLoopGetCurrent();
            //设置Run loop observer的运行环境
            CFRunLoopObserverContext context = {
                0,
                (__bridge void*)self,//这里填写你要传递的信息。
                &CFRetain,
                &CFRelease,
                NULL
            };
            //创建Run loop observer对象
            //第一个参数用于分配observer对象的内存
            //第二个参数用以设置observer所要关注的事件,详见回调函数myRunLoopObserver中注释
            //第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行
            //第四个参数用于设置该observer的优先级
            //第五个参数用于设置该observer的回调函数
            //第六个参数用于设置该observer的运行环境
            CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
            
            
            //添加观察者
            /**
             第一个参数:(<#CFRunLoopRef rl#>)要监听哪个runloop
             第二个参数:(<#CFRunLoopObserverRef observer#>)监听者
             第三个参数:(<#CFStringRef mode#>)要监听runloop在哪种运行模式下的状态
             */
            CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
        }
        void myRunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
            switch (activity) {
                case kCFRunLoopEntry:
                    NSLog(@"即将进入runloop");
                    break;
                case kCFRunLoopBeforeTimers:
                    NSLog(@"即将处理 Timer");
                    break;
                case kCFRunLoopBeforeSources:
                    NSLog(@"即将处理 Sources");
                    break;
                case kCFRunLoopBeforeWaiting:
                    NSLog(@"即将进入休眠");
                    break;
                case kCFRunLoopAfterWaiting:
                    NSLog(@"从休眠中唤醒loop");
                    break;
                case kCFRunLoopExit:
                    NSLog(@"即将退出runloop");
                    break;
                    
                default:
                    break;
            }
        }
          
  5. Runloop的mode:系统默认注册了5个mode

    • kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
    • UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
    • UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
    • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
    • kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
  6. 线程和Runloop

    • 线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。
    • 线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。
    • RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其RunLoop(主线程除外)。
    • 主线程的runloop默认是启动的。
    • 对其它线程来说,runloop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。在任何一个Cocoa程序的线程中,都可以通过:NSRunLoop *runloop = [NSRunLoop currentRunLoop]获取到当前线程的runloop。
    • 我们不能在一个线程中去操作另外一个线程的runloop对象,那很可能会造成意想不到的后果。但是CoreFundation中的不透明类CFRunLoopRef是线程安全的,而且两种类型的runloop完全可以混合使用。Cocoa中的NSRunLoop类可以通过实例方法:- (CFRunLoopRef)getCFRunLoop获取对应的CFRunLoopRef类,来达到线程安全的目的。
  7. Runloop的管理并不完全是自动的。

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

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

    • 此方法创建的定时器,必须加到NSRunLoop中。
      NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(changeImage) userInfo:nil repeats:YES];
      NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
      [runLoop addTimer:timer forMode: NSRunLoopCommonModes];
    • 此种创建定时器的方式,默认加到了runloop,且默认为第二个参数。
      self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(changeImage) userInfo:nil repeats:YES];
  9. main函数的运行

    • UIApplicationMain() 函数会为main thread 设置一个NSRunLoop 对象,这就解释了app应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。
      int main(int argc, char *argv[])
      {
          @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));
         }
      }
  10. 辅助线程

    • 对于辅助线程,你需要判断一个runloop是否是必须的。如果是必须的,那么你要自己配置并启动它。你不需要在任何情况下都去启动一个线程的runloop。比如,你使用线程来处理一个预先定义的长时间运行的任务时,你应该避免启动runloop。Runloop在你要和线程有更多的交互时才需要,比如以下情况:
      • 使用端口或自定义输入源来和其他线程通信;
      • 使用线程的定时器;
      • Cocoa中使用任何performSelector...的方法;
      • 使线程周期性工作;           

 六、事件响应链

  1. 简介:由响应者对象组成的传递链。

  2. 事件传递链

    • 由系统向离用户最近的View传递,即最近的view----UIAplication
  3. 事件响应链

    • 由离用户最近的View向系统传递,即UIAplication----最近的view 
  4. 判定过程

    • 首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;
    • 若返回NO,则hitTest:withEvent:返回nil;若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,
    • 所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;
    • 若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。

七、引用计数

八、生命周期

  1. app应用程序有5种状态:

    • Not running未运行:程序没启动。
    • nactive未激活:程序在前台运行,不过没有接收到事件。在没有事件处理情况下程序通常停留在这个状态。
    • Active激活:程序在前台运行而且接收到了事件。这也是前台的一个正常的模式。
    • Backgroud后台:程序在后台而且能执行代码,大多数程序进入这个状态后会在在这个状态上停留一会。时间到之后会进入挂起状态(Suspended)。有的程序经过特殊的请求后可以长期处于Backgroud状态。
    • Suspended挂起:程序在后台不能执行代码。系统会自动把程序变成这个状态而且不会发出通知。当挂起时,程序还是停留在内存中的,当系统内存低时,系统就把挂起的程序清除掉,为前台程序提供更多的内存。
  2. AppDelegate

    • 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

      如果还需要长时间的运行任务,可以在该方法中调用

      [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秒钟的限制。超时后方法会返回程序从内存中清除。用户可以手工关闭应用程序。