欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~

作者:QQ音乐技术团队

一、引言

在和服务器传输文本的时候,可能会因为某一个字符的编码格式不同、少了一个字节、多了一个字节等原因导致整段文本都无法解码。而实际上如果可以找到这个字符,然后替换成其他字符的话,那整段文本其他字符都是可以解码的,用户在UI上也许能猜测出正确的字符是什么,这种体验是好于用户看到一片空白。

代码的思路是对于无法用initWithData:encoding:方法解析的数据,则逐个字节的进行解析。源码的一个分支如下:

while(检索未超过文件长度)
{if(1字节长的编码){/*正确编码,继续循环*/}else if (2字节长的编码){CFStringRef cfstr = CFStringCreateWithBytes(kCFAllocatorDefault, {byte1, byte2}, 2, kCFStringEncodingUTF8, false);if (cfstr){/*正确编码,继续循环*/}else{/*替换字符*/}}else if(3,4,5,6个字节长的解码)...
}
复制代码

发现无法解析的字符后进行替换。这个方法的弊端在于CFStringCreateWithBytes方法分配的字符串是堆空间,如果数据过长,则很容易产生内存碎片。

解决这个问题有两种思路:一是在栈空间分配内存,二是分配一个可以重复利用的堆空间。

二、CFAllocatorRef的研究

从CFStringCreateWithBytes提供的参数看,调用者可以指定内存分配器。查阅官方文档对第一个参数CFAllocatorRef alloc给出的释义:The allocator to use to allocate memory for the new string. Pass NULL or kCFAllocatorDefault to use the current default allocator。接下来研究下这个内存分配器的数据结构以及系统提供的六个分配器的区别。

先看下CFAllocatorRef的数据结构:

typedef const struct CF_BRIDGED_TYPE(id) __CFAllocator * CFAllocatorRef;
struct __CFAllocator {CFRuntimeBase _base;CFAllocatorRef _allocator;CFAllocatorContext _context;
};
复制代码

只考虑iOS平台的话,__CFAllocator只有三个成员。其中CFAllocatorContext _context是分配器的核心,其作用是可以自定义分配和释放的回调函数:

typedef void *        (*CFAllocatorAllocateCallBack)(CFIndex allocSize, CFOptionFlags hint, void *info);
typedef void        (*CFAllocatorDeallocateCallBack)(void *ptr, void *info);
typedef struct {...CFAllocatorAllocateCallBack        allocate;CFAllocatorDeallocateCallBack    deallocate;...
} CFAllocatorContext;
复制代码

当系统使用这个分配器进行分配,释放,重分配等操作的时候会调用相应的回调函数来执行(上面代码省略了部分回调函数,有兴趣深入了解的同学可查看CFBase.m的源码)。

接下来看系统为提供的一系列分配器的源码(只考虑iOS平台)。

  • kCFAllocatorMalloc:系统的分配和释放本质就是malloc(),realloc(),free()。
static void * __CFAllocatorCPPMalloc(CFIndex allocSize, CFOptionFlags hint, void *info)
{return malloc(allocSize);    }
static void __CFAllocatorCPPFree(void *ptr, void *info)
{free(ptr);}
复制代码
  • kCFAllocatorMallocZone:看源码这个分配器在iOS上和kCFAllocatorMalloc是一样的,但在Mac的操作系统上是有区别的(malloc和malloc_zone_malloc)。

  • kCFAllocatorNull:其实什么都不会做,直接返回NULL。看文档说明主要是用于在释放的时候内存实际上不应该被释放。

    static void *__CFAllocatorNullAllocate(CFIndex size, CFOptionFlags hint, void *info)
    { return NULL;}
    复制代码
  • kCFAllocatorUseContext:是一个固定的地址,它只用于CFAllocatorCreate()创建分配器的时候。表示创建分配器时使用自身的context->allocate方法来分配内存。因为分配器也是一个CF对象。

    const CFAllocatorRef kCFAllocatorUseContext = (CFAllocatorRef)0x03ab;
    复制代码
  • kCFAllocatorDefault:这个是取系统当前的默认分配器,这个需要结合另外两个API来理。解:CFAllocatorGetDefault和CFAllocatorSetDefault方法。(源码中set方法有一段有意思的注释:系统retain了两次allocator,目的是为了在设置默认分配器的时候,之前的默认分配器不会释放。那这里不是会造成内存泄漏了吗?觉得要慎用)。

  • kCFAllocatorSystemDefault:这个才是系统级别的默认分配器,如果不调用CFAllocatorSetDefault(),则用CFAllocatorGetDefault()取出的分配器就是这个。从源码来看,目前和kCFAllocatorMalloc没区别(也许很久之前因为__CFAllocatorSystemAllocate不是用malloc实现的。后来兼容了,这里的故事有知道的欢迎告知)

三、自定义分配器

看完系统提供的分配器后发现都是在堆空间分配内存,没有合适的。后发现系统提供了另外一个API:CFAllocatorCreate。这时可以考虑自定义一个分配器,分配器在分配内存的时候,返回一块固定大小的内存重复使用。

void *customAlloc(CFIndex size, CFOptionFlags hint, void *info)
{return info;
}void *customRealloc(void *ptr, CFIndex newsize, CFOptionFlags hint, void *info)
{NSLog(@"警告:发生了内存重新分配");return NULL;//不写这个回调系统也是返回NULL的。这里简单的打句log。
}void customDealloc(void *ptr, void *info)
{//因为alloc的地址是外部传来的,所以应该由外部来管理,这里不要释放
}CFAllocatorRef customAllocator(void *address)
{CFAllocatorRef allocator = NULL;if (NULL == allocator){CFAllocatorContext context = {0, NULL, NULL, NULL, NULL, customAlloc, customRealloc, customDealloc, NULL};context.info = address;allocator = CFAllocatorCreate(kCFAllocatorSystemDefault, &context);}return allocator;
}int main()
{char allocAddress[160] = {0};CFAllocatorRef allocator = customAllocator(allocAddress);CFStringRef cfstr = CFStringCreateWithBytes(allocator, tuple, 2, kCFStringEncodingUTF8, false);if (cfstr){//CFRelease(cfstr);//这里不要释放,这里分配的内存是allocAddress的栈空间,由系统自己自己回收就好}CFAllocatorDeallocate(kCFAllocatorSystemDefault, (void *)allocator);
}
复制代码

这里用了一个技巧是重复使用的内存首地址利用context的info来传递。allocAddress的大小为什么是160个字节呢?这个大小只要取CFStringRef需要的最大长度就可以了。如果自己项目需要引用这个方法,需要考虑这个size需要设置多大。(取决于CFStringCreateWithBytes()的numBytes参数值,这里会有字节对齐的知识)。

创建的CFAllocatorRef也是在堆空间上,它也需要被释放。系统同样提供了释放API:CFAllocatorDeallocate。这里需要注意dealloc的allocator需要和create时是同一个allocator。否则无法释放,造成内存泄漏。

四、结语

自定义分配器让我们对内存的分配拥有了一定的可操作性,文中的应用场景是在创建对象时返回一块固定的内存区域重复使用,避免了重复创建和释放导致的内存碎片问题。这种可操作性相信以后在解决内存方面问题时会为你多提供一种解决方案。

CFBase的源码最近一次更新是2015.9.11日。这份源码最新也是基于iOS9的。在写这种底层代码的时候需要格外小心,作者在写的时候因为CFAllocatorCreate和CFAllocatorDeallocate的allocator参数传的不同,导致内存泄漏,需要多多测试。发布到外网的时候需要加上灰度策略以及开关控制。

最后分享一个额外小知识,iOS线程的默认栈空间大小是512KB(这个在苹果出了新系统和新机器后可能会变大,所以使用的时候尽量多测试)。这里踩过坑,程序源码中orignalBytes一开始是临时变量,分配在栈上,但是由于字符串太长,导致栈溢出crash,所以后面分配在堆上了。

参考链接

1.github.com/opensource-…

2.gist.github.com/oleganza/78…

3.developer.apple.com/library/pre…

4.developer.apple.com/library/pre…

相关阅读

一站式满足电商节云计算需求的秘诀
iOS 开发之动画中的时间
使用 Skeleton Screen 提升用户感知体验

此文已由作者授权腾讯云技术社区发布,转载请注明文章出处
原文链接:https://cloud.tencent.com/community/article/383806

一种避免 iOS 内存碎片的方法相关推荐

  1. android内存池,两种常见的内存管理方法:堆和内存池

    描述 本文导读 在程序运行过程中,可能产生一些数据,例如,串口接收的数据,ADC采集的数据.若需将数据存储在内存中,以便进一步运算.处理,则应为其分配合适的内存空间,数据处理完毕后,再释放相应的内存空 ...

  2. 【IOS】IOS开发问题解决方法索引(三)

    1       判断js对象是否拥有某属性 http://www.cnblogs.com/snandy/archive/2011/03/04/1970162.html 两种方式,但稍有区别 1,in运 ...

  3. 分分钟实现梦想 —— 两种快速打造App的方法

    分分钟实现梦想 -- 两种快速打造App的方法 几年前,做App还是土豪和移动开发者的专利.移动开发者使用Java或者C++这类开发工具,将一行行代码变成可以被手指轻松触控的应用.土豪们花钱雇佣这些移 ...

  4. 两种常见的内存管理方法:堆和内存池

    在程序运行过程中,可能产生一些数据,例如,串口接收的数据,ADC采集的数据.若需将数据存储在内存中,以便进一步运算.处理,则应为其分配合适的内存空间,数据处理完毕后,再释放相应的内存空间.为了便于内存 ...

  5. 浅析iOS界面设计方法

    眼看移动互联网之风吹遍祖国大地,各种移动应用接踵而来,作为互联网设计师,若能掌握一些基本设计原则,将会对新平台带来的的挑战轻松应对!今天就以iPhone为起点,让咱们来分析一下iOS界面的设计方法- ...

  6. JVM内存调优原则及几种JVM内存调优方法

    JVM内存调优原则及几种JVM内存调优方法 1.堆大小设置. 2.回收器选择. 1.在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因 ...

  7. 几种任务调度的 Java 实现方法与比较

    综观目前的 Web 应用,多数应用都具备任务调度的功能.本文由浅入深介绍了几种任务调度的 Java 实现方法,包括 Timer,Scheduler, Quartz 以及 JCron Tab,并对其优缺 ...

  8. 一种定位内存泄露的方法(Linux)

    2019独角兽企业重金招聘Python工程师标准>>> 目的: 本文是<一种定位内存泄露的方法(Solaris)>对应的Linux版本,调试器使用gdb.主要介绍实例部分 ...

  9. Cisco IOS的故障恢复方法

    IOS是路由器交换机设备的核心,IOS全称internet operate system,中文是网络操作系统的意思.他就好比计算机的操作系统windows一样,虽然是软件但出现问题就无法进行任何软件的 ...

最新文章

  1. GitBook本地的安装与查看
  2. 【OpenCV 4开发详解】中值滤波
  3. 小块头大性能才能得到用户的青睐
  4. 某些情况下安卓引入so冲突的解决
  5. 简单剖析C语言中的位扩展问题
  6. SpringMVC的简单知识
  7. Angular依赖注入的一个例子和注入原理单步调试
  8. “超人”助阵,IE静音很简单
  9. Hibernate→ORM、简介、第一个Hibernate实现、核心XML配置、Hibernate执行流程、操作数据库对象session、事务、映射XML配置、单例CRUD、get与load
  10. Java:接口interface
  11. tomcat之组成结构
  12. 在线教学试卷讲评利器——屏幕画笔
  13. exls表格搜索快捷键_excel表格快速查找快捷键
  14. 微信实现电脑远程关机
  15. 基于bootstrap的二维码支付系统webAPP设计
  16. 三进制计算机比二进制快,三进制会取代二进制计算机吗?
  17. python小写变为大写_在python中改为大写和小写
  18. Jquery入门和案例
  19. Android之图片压缩
  20. ulimit -u

热门文章

  1. 函数 -- 1.模块导入 2.ATM架构 # 14
  2. JMeter记录篇2——性能测试基础(2)
  3. struts 标签s:ierator的简单使用说明
  4. C标准I/O建立一个文件仓库
  5. Android风格与主题
  6. Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' 的解决办
  7. FTP下载文件中文名乱码FTP访问
  8. Python入门(01) -- 列表简介
  9. Spring Boot、Spring Cloud、Dubbo的区别
  10. Nodejs学习笔记(七)——接口API