第十章:管理iOS应用的运行

一、自动旋转机制

大部分iOS应用通常都可以同时支持纵屏(长而窄)和横屏(短而宽)两种运行模式,而且应用程序会随着不同的运行模式自动调整界面,保证应用程序可以同时在两种运行模式下良好的运行,这种机制被称为自动旋转机制。

1、配置应用支持的方向

  • iPhone4、iPhone4s:320 * 480;Retina屏640 * 960;
  • iPhone5、iPhone5s:320 * 568;Retina屏640 * 1136;
  • iPad1、iPad:1024 * 768;Retina屏2048 * 1536;
    获取屏幕大小:CGRect screenRect = [[UIScreen mainScreen] bounds];
    第十章:管理iOS应用的运行_第1张图片
    • ①、Portrait:代表最常见的纵向屏幕,且Home键位于下方;
    • ②、Upside Down:代表纵向屏幕,且Home键位于上方;
    • ③、Landscape Left:代表横向屏幕,但Home键位于左边;
    • ④、Landscape Right:代表横向屏幕,但Home键位于右边;
      配置好之后,一个应用程序可能由多个视图控制器组成,因此,还需要制定视图控制器支持的方向,视图控制器所支持的屏幕方向必须是此处所选方向的子集。

2、指定视图控制器支持的方向

指定视图控制器支持的方向通过重写如下方法来控制:

  • (1)、- (BOOL)shouldAutorotate;重写该方法控制该视图控制器是否支持自动旋转。如果不希望该视图控制器自动旋转,重写该方法返回NO;否则应该返回YES.
  • (2)、- (NSUInteger)supportedInterfaceOrientations;重写该方法控制该视图控制器所能支持的屏幕方向。该方法返回多个UIInterfaceOrientations枚举值或运算结果。
  • (3)、- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;重写该方法指定该视图控制器初始显示时默认的屏幕方向;
    除此之外,视图控制器提供了如下属性来判断当前的屏幕方向。
    interfaceOrientations:设置一个只读属性,该属性返回一个UIInterfaceOrientations枚举值,用于代表当前的屏幕方向。枚举值如下:

    • ①、UIInterfaceOrientationPortrait:代表最常见的纵向屏幕,且Home键位于下方;
    • ②、UIInterfaceOrientationPortraitUpsideDown:代表纵向屏幕,且Home键位于上方;
    • ③、UIInterfaceOrientationLandscapeLeft:代表横向屏幕,且Home键位于左方;
    • ④、UIInterfaceOrientationLandscapeRight代表横向屏幕,且Home键位于右方;
  • (1)、当iOS视图控制器初始显示出来时,系统会自动调用该视图控制器的如下方法,并根据该方法的返回值决定屏幕方向;
    例如://貌似不运行

    - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
        return UIInterfaceOrientationPortrait;
    }
    
  • (2)、当用户旋转iPhone时,系统会自动调用该视图控制器的如下方法,如果该方法的返回值中包含了当前设备希望的方向,那么该视图控制器将会旋转为对应的显示方式;
    例如:

    - (NSUInteger)supportedInterfaceOrientations{
        return UIInterfaceOrientationMaskAll;
    }
    
  • (3)、在屏幕旋转时调整应用界面,当视图控制器显示的屏幕方向发生改变时,程序将会依次触发如下方法:

    • ①、- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration NS_DEPRECATED_IOS(2_0,8_0, “Implement viewWillTransitionToSize:withTransitionCoordinator: instead”);当屏幕将要旋转到指定方向时,系统会自动调用视图控制器的这个方法。该方法的第一个参数代表将要旋转到的显示方向;
    • ②、- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration NS_DEPRECATED_IOS(3_0,8_0, “Implement viewWillTransitionToSize:withTransitionCoordinator: instead”);当将要执行旋转动画时,系统会自动调用视图控制器的这个方法。该方法的第一个参数代表将要旋转到的显示方向。重要
    • ③、- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation NS_DEPRECATED_IOS(2_0,8_0);当屏幕完成旋转到指定显示方向时,系统会自动调用视图控制器的这个方法。该方法的第一个参数代表将要旋转到的显示方向。

      提示:如果程序要在显示方向改变时对程序界面进行调整,都会重写- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;方法,并在该方法中对应用界面进行调整。调整方式有两种:旋转时重构用户界面;为不同的显示方式提供不同的界面设计文件,然后在显示方向旋转时切换视图。
      

二、旋转时重构用户界面

思路:重写视图控制器的- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;方法,在该方法中检测当前的屏幕方向,并根据屏幕方向来计算用户界面上各控件的大小和位置。
例如:

- (void)viewDidLoad {
    [super viewDidLoad];
    for (int i = 0; i < 2; i ++) {
        for (int j = 0; j < 3; j ++) {
            UIImageView* imageView = [[UIImageView alloc]initWithFrame:CGRectMake(i * 140 + 40, j * 160 + 84, 100, 120)];
            imageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"%d.png",i * 3 + j + 1]];
            [self.view addSubview:imageView];
            imageView.tag = i * 3 + j + 100;
            NSLog(@"%ld",imageView.tag);
            [self.view addSubview:imageView];
        }
    }
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return UIInterfaceOrientationPortrait;
}
- (NSUInteger)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskAllButUpsideDown;
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
    if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
        //如果当前处于横屏状态
        for (int i = 0; i < 3; i ++) {
            for (int j = 0; j < 2; j ++) {
                UIImageView* imageView = (UIImageView *)[self.view viewWithTag:i * 2 + j + 100];
                NSLog(@"%ld",imageView.tag);
                imageView.frame = CGRectMake(i * 160 + 40, j * 140 + 44, 100, 120);
            }
        }
    }else{
        //如果当前处于竖屏状态
        for (int i = 0; i < 2; i ++) {
            for (int j = 0; j < 3; j ++) {
                UIImageView* imageView = (UIImageView *)[self.view viewWithTag:i * 3 + j + 100];
                imageView.frame = CGRectMake(i * 140 + 40, j * 160 + 84, 100, 120);
            }
        }
    }
}

三、旋转时切换视图

例如:不太常用

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
    switch (toInterfaceOrientation) {
        case UIInterfaceOrientationPortrait:
            self.view.transform = CGAffineTransformIdentity;
            self.view.bounds = CGRectMake(0, 0, screenSize.width, screenSize.height);
            for (int i = 0; i < 2; i ++) {
                for (int j = 0; j < 3; j ++) {
                    UIImageView* imageView = (UIImageView *)[self.view viewWithTag:i * 3 + j + 100];
                    imageView.transform = CGAffineTransformIdentity;
                }
            }
            break;
        case UIInterfaceOrientationPortraitUpsideDown:
            self.view.transform = CGAffineTransformMakeRotation(M_PI);
            self.view.bounds = CGRectMake(0, 0, screenSize.width, screenSize.height);
            for (int i = 0; i < 2; i ++) {
                for (int j = 0; j < 3; j ++) {
                    UIImageView* imageView = (UIImageView *)[self.view viewWithTag:i * 3 + j + 100];
                    imageView.transform = CGAffineTransformMakeRotation(M_PI);
                }
            }
            break;
        case UIInterfaceOrientationLandscapeLeft:
            self.view.transform = CGAffineTransformMakeRotation(M_PI* (-90)/180);
            self.view.bounds = CGRectMake(0, 0, screenSize.width, screenSize.height);
            for (int i = 0; i < 2; i ++) {
                for (int j = 0; j < 3; j ++) {
                    UIImageView* imageView = (UIImageView *)[self.view viewWithTag:i * 3 + j + 100];
                    imageView.transform = CGAffineTransformMakeRotation(M_PI* (90)/180);
                }
            }
            break;
        case UIInterfaceOrientationLandscapeRight:
            self.view.transform = CGAffineTransformMakeRotation(M_PI * 90/180);
            self.view.bounds = CGRectMake(0, 0, screenSize.width, screenSize.height);
            for (int i = 0; i < 2; i ++) {
                for (int j = 0; j < 3; j ++) {
                    UIImageView* imageView = (UIImageView *)[self.view viewWithTag:i * 3 + j + 100];
                    imageView.transform = CGAffineTransformMakeRotation(M_PI * -90/180);
                }
            }
            break;
        default:
            break;
    }
}

四、iOS应用的生命周期

对于iOS应用而言,必须区分应用程序在前台运行和后台运行的差异,因为iOS设备的系统资源是有限的,应用程序在后台和前台必须以不同的行为运行。iOS系统也会限制应用程序在后台的运行,从而提高电池寿命,并“让出”更多系统给前台的应用程序,从而提高用户的体验。

1、应用程序的状态

  • (1)、Not running(未运行):应用程序未启动或应用程序被系统终止;
  • (2)、Inactive(不活动):程序在前台运行,但不能接受事件处理。当应用要从一个状态切换到另一个不同的状态时,中途过渡会短暂停留在此状态;
  • (3)、Active(活动):程序在前台运行且能接受到事件。这是应用在前台运行时所处的正常状态;
  • (4)、Background(后台):应用处在后台运行,并且还在执行代码。大多数将要进入Suspended状态的应用,会短暂进入此状态。如果应用请求更多额外的执行时间,该应用会在此状态保持更长一段时间。另外,如果一个应用要求启动时直接进入后台运行,这样的应用会直接从Nor running状态进入Background状态,中途不会经过Inactive状态。
  • (5)、Suspended(挂起):应用处在后台,并且没有执行任何代码。系统会自动将应用转入该状态,并且不会发出任何通知。当处在该状态时,应用依然驻留内存,但不执行任何程序代码。当系统发生低内存警告时,系统会将处于Suspended状态的应用彻底移除内存,从而为前台应用释放更多的内存。

第十章:管理iOS应用的运行_第2张图片

大部分的时候,应用程序的状态改变都会激发应用程序委托类(AppDelegate)对应的方法,AppDelegate中各方法的回调实时机如下:

  • (1)、- (BOOL)application:(UIApplication )application willFinishLaunchingWithOptions:(NSDictionary )launchOptions;应用程序将要启动时自动调用该方法,该方法是应用程序启动的第一个执行自定义代码的机会;
  • (2)、- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions;应用程序启动时自动调用该方法,开发者可以在该方法中执行初始化相关的代码;
  • (3)、- (void)applicationDidBecomeActive:(UIApplication *)application;应用在转入前台,并进入活动状态时回调该方法(当应用从启动到进入前台,或从后台转入前台都会调用该方法),可重写该方法指定最后的准备工作;
  • (4)、- (void)applicationWillResignActive:(UIApplication *)application;应用程序正要从运行状态离
    开要进入非活动状态时将会调用该方法;在此期间,应用程序不接受消息或事件,比如来电话了;
  • (5)、- (void)applicationDidEnterBackground:(UIApplication *)application;应用程序正处于Background状态,且随时可能进入Suspended状态将会调用该方法;
  • (6)、- (void)applicationWillEnterForeground:(UIApplication *)application;应用正从后台转入前台运行装填,但暂时还没有到达Active状态时将会调用该方法;
  • (7)、- (void)applicationWillTerminate:(UIApplication *)application;该应用程序即将被终止时调用该方法,如果应用当前处在Suspended状态,此方法将不会被调用。

2、应用程序启动过程

  • (1)、当应用启动时,将从Not running状态进入Active状态时,在进入Active状态时的中间会先短暂进入Inactive状态。在应用启动时,系统会创建一个进程和一个主线程,并且在主线程中调用main()函数。Xcode在创建iOS项目时会自动生成main()函数,main()函数负责初始化UIApplication对象,并未UIApplication设置应用代理类等。当应用初始化并准备进到前台运行之间的大部分工作都由main()函数负责完成。

    用户点击应用图标—>执行main()函数—>UIApplicationMain()—>加载主界面设计文件—>第一步初始化—>Restore UI state—>最后初始化—>激活应用—>Event Loop—>切换其他应用。
    
  • (2)、如果应用程序启动后直接进入Background状态,则对应的启动过程与进入Active有区别,主要区别在于:直接进入Background的应用启动后会转入Background状态处理事件,并将很快进入Suspended状态。当启动进入后台时,系统依然会加载应用的用户界面设计文件,但不会显示在应用程序窗口上。
    第十章:管理iOS应用的运行_第3张图片

    用户点击应用图标—>执行main()函数—>UIApplicationMain()—>加载主界面设计文件—>第一步初始化—>Restore UI state—>最后初始化—>Enter Background—>是否需要继续运行—>Monitor events—>(不在处理事件时休眠)应用休眠。
    
    
    提示:可在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;方法中加入如下代码即可检测应用程序将要进入的状态
    
UIApplicationState state = [UIApplication sharedApplication] .applicationState; 枚举值(UIApplicationState)包括: UIApplicationStateActive; UIApplicationStateInactive; UIApplicationStateBackground。

3、程序入口:main函数

在iOS应用开发中,main函数的功能被最小化了,它的主要工作都交给了UIKit框架去完成。函数代码通常如下:
int main(int argc, char * argv[])

{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
  • (1)、UIApplicationMain函数有四个参数,argc和argv参数包含了系统带过来的启动时间,argc代表用户传入的参数个数,argv封装了用户传入的多个参数。第三个参数确定了主要应用程序类的名称,这个参数定义为nil,这样UIKit就会使用默认的程序类UIApplication——除非需要使用自定义UIApplication子类来创建应用程序,否则无须修改该参数。第四个参数用于指定UIApplication的应用程序委托类,该应用程序委托负责处理应用程序状态切换过程中产生的各种事件。
  • (2)、UIApplicationMain()函数还加载了程序主界面的设计文件,但并不会把用户界面显示在窗口中,因此必须在willFinishLaunchingWithOptions:文件中将界面设计文件对应的用户界面显示出来;

4、响应中断

    当一个警告框方式的中断(比如电话来电)发生时,应用会暂时从Active状态切换到Inactive状态,从而让用户决定如何处理。在用户决定如何处理该中断之前,应用将一直处于Inactive状态。在用户做出选择后,当前应用可以返回Active状态继续运行,也可切换到Background状态,从而让其他应用开发运行。

  • (1)、通知:通知通常会显示在状态栏上,如果用户拉下状态栏,程序会变成Inactive状态;用户把状态栏拉上去时,程序又会变成Active状态。
  • (2)、按键锁屏:当用户按下“锁屏”键时,系统屏蔽所有的触碰事件,并把应用程序转入Background状态,然对象的applicationState属性设为UIApplicationStateInactive,最后锁屏。

中断发生时,程序应该做什么
警告框方式的中断会导致用户暂时失去对应用的控制。虽然应用依然在前台运行,但是不在接受任何触碰事件(应用知识不在接受触碰事件,它依然会接受通知和其他类型的事件,如accelerometer事件),为了应对这些变化,应用需要在applicationWillResignActive:方法中完成以下工作。
①、停止timers并终止其他周期性的任务;
②、停止任何正在运行的元数据查询;
③、不在初始化任何新的任务;
④、暂停电影播放(在AirPlay上播放的除外);
⑤、游戏进入暂停状态;
⑥、减少OpenGL ES帧率;
⑦、暂停所有执行非关键代码的调度队列和操作队列(即使处于Inactive状态,依然可以继续处理网络请求和其他耗时的后台任务)。
当应用恢复Active状态时,需要在applicationDidBecomeActive:方法中恢复刚刚上述方法中做的所有工作。比如:重启timers,恢复所有的分发队列,恢复OpenGL ES帧率,游戏回到暂停状态。
当用户按下“锁屏”键时,如果该设备设置了密码保护,该设备内的所有文件将处于被保护状态,任何尝试访问这些文件的行为都会导致失败。因此,如果应用持有这些被保护文件的引用,应该在applicationWillResignActive:方法中关闭所有对这些文件的引用,并在applicationDidBecomeActive:方法中重新打开对这些文件的引用。

在通话过程中调整应用的UI
当用户正在通电话,并且返回应用继续保持通话时,状态栏的高度将会增加以反映用户正在通话。类似的,当用户结束通话时,状态栏的高度将会缩减恢复到常规的高度。处理状态栏高度变化的最好方法是使用视图控制器去管理应用程序界面。当状态栏的大小发生改变时,视图控制器可以自动调整它管理的所有内部视图。
如果应用由于某些原因二没有使用视图控制器,则应该手动响应状态栏大小的变化,即通过注册UIApplicationDidChangeStatusBarFrameNotification通知来实现。该通知的处理函数应该获取状态栏的高度,并且使用这些数据来适度调整当前应用的用户界面的高度。

5、进入后台

   当用户按下Home键、锁屏键,或者系统启动另一个应用时,前台应用首先切换到Inactive状态,然后切换到Background状态,系统将会自动调用应用委托的applicationWillResignActive:和applicationDidEnterBackground:方法。当applicationDidEnterBackground方法执行完成后,大部分应用将会在不久之后转入Suspended状态。对于请求特定后台任务的应用(如播放音乐)或者那些请求需要额外执行时间的应用,可能会继续执行更长的一段时间。

应用进入后台的执行流程如下图:
第十章:管理iOS应用的运行_第4张图片

用户切换另一个程序—>应用程序失去活动—>进入后台—>允许执行—>监视事件—>后台程序睡眠—>切换到其他程序。

  • 转入后台时应该做什么

    • (1)、保存用户数据或状态信息。所有没写入磁盘的文件或信息在进入后台之前,都应该写入磁盘,应为程序可能在后台被杀死;
    • (2)、释放所有可以释放的内存。
      applicationDidEnterBackground:方法有大概5秒的时间来完成这些任务。如果超过该时间还有未完成的任务,程序就会被终止,而且从内存中清除。如果需要更长时间来运行任务,可以调用beginBackgroundTaskWithExpirationHandler方法请求后台执行时间,然后请求一个能长期执行任务的线程。无论是否启动执行后台任务的线程,applicationDidEnterBackground:方法必须在5秒内退出。
  • 后台应用的内存使用
    当应用转入后台时,该应用应该尽可能多的释放它所占用的内存。当系统内存紧张时,系统会优先终止那些占用大量内存且处于后台的应用。
    事实上,应用程序应该的对象如果不在使用了,那就应该近况的去掉强应用,这样编辑器可以回收这些内存。如果你想缓存一些对象提升程序的性能,你可以在进入后台时,把这些对象去掉强引用。

    • ①、缓存的图像对象;
    • ②、比较大的多媒体文件或数据文件,这些文件可以从磁盘重新装载;
    • ③、任何当前不在需要的对象,并且这些对象后面又可以很容易重新创建;

    与此同时,系统为了减少应用程序所占用的内存,会自动执行如下操作释放内存:

    • ①、系统回收Core Animation的后备存储;
    • ②、去掉任何系统引用的缓存图片;
    • ③、去掉系统管理缓存中的强引用。

6、返回前台运行

  如果应用曾被转入后台,相应地任务被停止,则可以在返回前台时重启任务继续执行。应用的applicationWillEnterForeground:方法应该恢复所有在applicationDidEnterBackground:方法所做的工作。同时,applicationDidBecomeActive:方法应该执行在应用启动时所做的操作。

应用从后台进入前台的程序流程如下图:
第十章:管理iOS应用的运行_第5张图片
切换回本应用—>唤醒应用—>激活应用—>Event Loop。
当应用处于Suspended状态时,它不能执行任何代码。因此,它不能处理在Suspended期间发过来的通知,比如方向的改变、时间的改变、设置的改变,以及其他影响程序展现的或状态的通知。在程序返回后台或前台时,都要正确处理这些通知。

7、应用程序终止

程序在以下情况下进入后台或者挂起状态会终止:

  • (1)、应用运行于iOS4.0以及更早的版本;
  • (2)、应用部署在运行iOS4.0以及更早的版本操作系统的设备上;
  • (3)、当前设备不支持多任务;
  • (4)、应用在Info.plist文件中包含UIApplicationExitOnSuspend key。

如果应用将被终止时正在前台或者后台运行,系统将会调用应用委托对象的applicationWillTerminate:方法,从而保证应用程序可以正常的回收资源。通常来说,开发者可以在该方法中保存用户数据或应用状态信息,保证应用重新启动可以恢复之前的运行状态。该方法的运行事件被限制为5秒,超过该时间后,该进程会被自动结束并移除内存。

8、Main Run Loop

    Main Run Loop负责处理用户相关的事件。UIApplication对象在程序启动时启动main run Loop,它处理事件和更新视图的界面。看Main Run Loop就知道,它是运行在程序的主线程上的。这样保证了接收到用户相关操作的事件是按顺序处理的。

第十章:管理iOS应用的运行_第6张图片
用户操作设备,相关的操作事件被系统生成并通过UIKit的指定端口分发。事件在内部排成队列,一个个的分发到Main run loop 去做处理。UIApplication对象是第一个接收到时间的对象,它决定事件如何被处理。触摸事件分发到主窗口,窗口再分发到对应出发触摸事件的View。其他的事件通过其他途径分发给其他对象变量做处理。
大部分的事件可以在你的应用里分发,类似于触摸事件,远程操控事件(线控耳机等)都是由app的 responder objects 对象处理的。Responder objects 在你的app里到处都是,比如:UIApplication 对象。view对象,view controller 对象,都是resopnder objects。大部分事件的目标都指定了resopnder object,不过事件也可以传递给其他对象。比如,如果view对象不处理事件,可以传给父类view或者view controller。

你可能感兴趣的:(ios,管理,应用)