• iOS支持arm64


    Apple要求2015/2/1之后提交的包必须包含arm64,否则要被拒。因此,对于64-bit的支持可谓迫在眉睫,尤其是对于有很多遗留代码的项目,更要提早开工。

    如何支持arm64

    为了支持arm64结构,需要满足一下几个条件:

    • 在Architectures设置项里添加arm64条目,如果使用的Xcode是6.0以上的版本,使用默认的配置项即可。
    • 在Valid Architectures设置项里添加arm64条目。
    • 讲Deployment Target改为大于等于5.1.1即可,因为arm64最低支持5.1.1系统。
    • 支持64-bit的运行时环境,也即打开针对64-bit的编译器警告和错误,可以帮助你顺利地迁移到arm64。如果设置完成后没有明显地提示升级Xcode编译配置项,可以先build一下code,第一条警告就是建议支持64-bit的运行时环境,enable该设置项即可。

    完成这些步骤之后,就可以build一个同时包含32-bit和64-bit的IPA文件。build完成之后你会发现有成千上百的警告和错误,这时候才是真正工作的开始。

    另外,经过以上步骤之后,会发现当前的项目无法通过Xcode在iOS6以下的系统上联调,或者iTunes等直接安装打包的IPA文件。不过将打包的IPA文件通过AppStore发布是可以安装在iOS5.1.1上的,有人推断AppStore会对提交的IPA文件做一些magic的事情。如果想在iOS6以下的系统上联调,则需要在Valid Architectures里去掉arm64。

    arm64适配

    根据Apple官方介绍,arm64会带来以下的改变:

    • Data Type.
    • Function Calling.
    • ARM Instruction.

    下面分别介绍这三方面的具体变化。

    Data Type

    arm64带来的最大改变就是寻址空间和寄存器从32-bit增长为64-bit,系统可以提供更多的内存和更大的寄存器空间。下面两张图列出了转移到64-bit后Data Type的变化。

     

    ILP32表示32-bit系统,LP64表示64-bit系统。图中的黑体表示64-bit相对于32-bit的不同,其改变可以概括为以下几点:

    • 指针的大小从4Byte增长为8Byte。
    • long,NSIteger,size_t,time_t,CFIndex,CGFloat都从4Byte增长为8Byte。
    • long long,fpos_t,off_t,double的对齐都从4Byte增长为8Byte。

    从以上总结可知,如果同时为32-bit和64-bit的系统开发软件,不可避免地会在32-bit和64-bit的数据之间产生运算和赋值等操作。面临的风险主要有以下:

    • 32-bit和64-bit数据之间的操作。

      这里的操作包括数学运算和赋值等运算,运算过程中可能会遇到数据截断,数据的溢出以及一些独特的边界情况。主要有以下几种情况:

      • 32-bit和64-bit数据之间的赋值。将一个64-bit的数据赋给一个32-bit的变量,比如:

        int intValue = NSItegerMax;
        

        这将会导致数据截断,并不会得到期望的结果。同理如果将一个32-bit的数据赋给一个64-bit的变量,将会获得意想不到的结果,比如:

        NSUInteger biggerIntValue = -1;
        
      • 指向32-bit数据的指针变量和指向64-bit数据的指针变量互相赋值。如下操作:

        int *pointerToInt = pointerToLong;
        pointerToLong = pointerToInt;
        

        由于pointerToInt + 1实际上是+4,而pointerToLong + 1实际上是+8,所以转换之后再进行指针运算所得结果是错的。

      • 指针和变量之间的赋值。如下:

        int currentAddress = pointerToLong;
        NSInteger *pointerToNSInteger = currentAddress + 1;
        

        这里的指针地址不但被截断了,并且+1操作也不会得到期望的结果。

      • 隐式的枚举类型转换。编译器会为每个枚举类型分配一个合适的存储类型,可能是int也可能是NSInteger,根据其需要分配。因此,隐式地将枚举类型赋给其他类型的变量时,可能导致数据被截断,或者被错误地提升。
      • 与系统相关的数据类型。NSIntegerMax在32-bit和64-bit所表示的值是不一样大地。另外代码中常见的hardCode也存在问题,比如左移操作中常见的32,24等。

      针对以上列出的潜在风险,这里有两点建议:

      1. 使用相同的数据类型。尽量减少显式和隐式类型转换,使用相同的数据类型。
      2. 避免在指针与整形之间互相转换。尽量避免将指针赋给转型变量,或者显式地强制转换。如果真的需要,请使用uintptr_t类型。
    • 在32-bit和64-bit的软件之间交换数据。

      32-bit和64-bit的软件很可能通过网络读写同一份文件,甚至用户也会用32-bit软件的数据覆盖64-bit软件下的同一份数据,这些都会导致无法预测地错误。比如NSInteger在32-bit是4Byte,而在64-bit上则是8Byte。如果这时在32-bit软件里访问由64-bit软件生成的内容为NSInteger类型的文件,结果无法预测。

       struct second {
           int milliSecond;
           long microSecond;
       };
      
      在32-bit软件中second的大小为8Byte,microSecond的偏移量为4。而在64-bit软件中second的大小为16Byte,而microSecond得偏移量为8。如果这是在32-bit和64-bit系统中持久化改模型或者将其传到网络上供其他软件访问,将不会得到正确的结果。 针对这个问题,这里提供一些建议:
      1. 使用一致的数据类型。

        也即尽量使用与系统无关的数据类型,如果该软件同时存在32-bit和64-bit的版本,建议在32-bit和64-bit中使用相同的数据类型。比如,不管在32-bit软件还是64-bit软件上,尽量都使用int32_t或者int64_t。

      2. 创建内存模型一致的数据模型。

        即数据模型大小和其中的元素偏移量都相同。针对上面提到的第二个问题,有以下两种解决方案:

         struct second {
         int32_t milliSecond;
         int32_t microSecond;
         };  
        
         #pagram pack(4)
         struct second {
         int32_t milliSecond;
         int64_t microSecond;
         };
         #pragma options align=reset
        
      3. 使用plist,XML和JSON进行序列化和持久化。

        当使用NSCoder在64-bit软件上encode一个NSInteger数据,而后在32-bit软件上decode该NSInteger数据,同时该整型数值正好超出了32-bit int类型可以表示的范围时,将会抛出一个异常。

    • 消耗更多的内存。

      由以上的分析可知,很多的基本类型和指针地址都从4Byte增长为8Byte,这也预示着64-bit软件将消耗更多的内存。不但一些基本类型消耗了更多的内存,甚至常用的Foundation Object都要消耗更多的内存,由于其强大的功能,比如NSArray,NSDictionary。针对这个问题有以下几点建议:

      • 选用合适的Foundation Object。

        如果在NSArray里只存储一个简单的对象,然后产生成千上百这也的对象,那么消耗的内存将是巨大的。因此,尽量在合适的场合使用合适的类。

      • 选择紧凑的数据模型。

        尽量选择更合适的数据模型来表示你的数据。假定你要表示一个date类型,使用的数据模型如下:

          struct date {
              NSInteger second;
              NSInteger minute;
              NSInteger hour;
              NSInteger day;
              NSInteger month;
              NSInteger year;
          };
        

        在32-bit的软件上date的大小是24Byte,在64-bit的软件上date是48Byte,惊人吧!简单地改变一下设计,在达到目标的同时还可以节省很多的内存使用,结构如下:

          struct date {
          long seconds;
          };
        

        seconds表示流逝的总秒数,通过简单的计算即可得到year,month,day......

      • 消除多余的padding。

        为了性能的原因,编译器通常会在基本数据类型之间添加padding,以使他们对齐,避免多次访问内存。比如:

           struct morePadding {    //32-bit
               char second;         //offset 0
               int  minute;         //offset 4
               char hour;           //offset 8
               NSInteger day;       //offset 12
           };                     //total size 16
        

        morePadding的实际大小为10Byte,而占据的内存大小为16Byte。经过重新设计将其改为以下结构:

           struct morePadding {    //32-bit
               int  minute;         //offset 0
               NSInteger day;       //offset 4
               char second;         //offset 8
               char hour;           //offset 9
           };                     //total size 10
        
      • 使用尽量少的指针变量。

        避免在数据模型中过度使用指针,考虑以下模型:

           struct node{
                 node        *previous;
                 node        *next;
                 uint32_t    value;
               };
        

        在64-bit软件中node总大小为20Byte,而有效数据只有4Byte,80%的空间都被指针占据,可以考虑使用其他的数据结构代替。

      • 在可以表达的范围内使用更小的数据类型。

        如果只是表达几千几百的数字,则int就可以满足需求了,不需要使用NSInteger。宗旨就是使用够用的数据类型表示数字,没有必要64-bit软件就一定要使用int64_t类型。

      • 只cache必须的数据。

        为了性能优化的目的,我们的代码经常使用cache机制,即拿空间换时间,cache确实可以在很多地方提高软件的响应速度,甚至节省网络流量等。比如缓存网络图片避免下次联网,缓存经过滤镜处理以后的图片,避免CPU多次执行同意操作。64-bit软件消耗了更多的内存,如果缓存了过多的无关紧要的东西,可能反而会降低软件的整体性能。因此,建议以下的情况不要使用缓存:

        • 可以容易地重新计算产生的。
        • 很容易从其他地方获取的。
        • 廉价地重新生成的。
        • 只读数据可以通过mmap()访问的。

        所以,应该经常地测试cache确实提升了性能。

      • 合理使用@autoreleasepool。

        尽快释放不再需要的autorelease对象,避免内存耗尽迫使系统发出UIApplicationDidReceiveMemoryWarningNotification通知。尤其是for循环和递归调用的场合,需要给出特别的关注。

      • 处理UIApplicationDidReceiveMemoryWarningNotification。

        所有相关的对象都必须处理UIApplicationDidReceiveMemoryWarningNotification通知,尤其是各个Controller,cache Manager需要第一时间响应该通知,避免导致低内存的crash。

    Function calling

    如果没有使用汇编语言的话,转换到64-bit的影响并不是很大,只有一点,可变参数的函数的调用规则在64-bit软件上是不一样的。因此,对于函数调用建议如下:

    • 实参和形参使用一致的数据类型。
    • 避免在函数签名不一样的函数之间强制转换。

      在不同的函数签名的指针变量之间互相传递,很容易导致调用函数的时候传递不合适的参数,从而无法得到预期的结果,尤其在固定参数的函数和可变参数的函数之间强制转换,如下所示:

        int MyFunction(int a, int b, ...);
        int (*action)(int, int, int) = (int (*)(int, int, int)) MyFunction;
       action(1,2,3); // Error!
      
    • 给可变参数的函数传递正确的参数。

      由于可变参数列表通常未提供类型信息,如果这时传递了错误的参数值,将不会得到正确的结果。所以,可以考虑给可变参数添加格式字符串,提供一定的类型信息,比如printf()

    Objective-C Runtime

    不要直接访问OC对象的isa,在64-bit软件里边isa不再是一个指向class object的指针,它包含一些指针数据和一些运行时信息。如果需要得到class object,使用object_getClass函数。

    ARM64 Instruction

    arm64的指令极大地不同于32-bit的指令,因此,汇编代码需要重写。arm64的函数调用约定跟标准的arm不太一样,可以参考iOS ABI Function Call Guide

  • 相关阅读:
    解Bug之路-记一次存储故障的排查过程
    自己动手写SQL执行引擎
    从linux源码看socket(tcp)的timeout
    解Bug之路-记一次JVM堆外内存泄露Bug的查找
    从linux源码看epoll
    可拖拽圆形进度条组件(支持移动端)
    Threejs模仿实现滴滴官网首页地球动画
    css实现朋友圈照片排列布局
    H5页面设置title,解决设置默认title为空时闪烁问题
    vue-cli3.0本地代理cookie跨域请求Nginx配置
  • 原文地址:https://www.cnblogs.com/CoderPlace/p/4170418.html
Copyright © 2020-2023  润新知