HABIT IS POWER

习惯就是力量

0%

需求

系统自带的python版本是2.7,之前用homebrew直接安装了一个python 3.6.5,不方便切换版本,想要清理下环境,使用pyenv来统一管理系统内的python版本

清理老版本

清理老的pip安装

之前用sudu pip安装了不少package,太挫了,先把这部分清理一下

1
2
3
4
5
6
7
8
9
10
11
# 确认下当前使用的文件
which pip
which python

# 删除pip
sudu pip uninstall pip

# 卸载之前brew安装的python
brew uninstall python –ignore-dependencies

brew uninstall pyenv

检查是否清理干净

下面这几个命令应该都找不到了

1
2
3
pyenv
python
pip

安装新版本

最先需要安装的是pyenv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
brew install pyenv

# 打开.bash_profile
vim ~/.bash_profile

# 添加下面三行

# pyenv
export PYENV_ROOT=/usr/local/var/pyenv
eval "$(pyenv init -)"

# 重新加载下配置,查看当前的python版本,应该只有一个system版本
source ~/.bash_profile
pyenv versions

安装指定的python版本

本来应该是一条命令搞定的事情,比如:

1
pyenv install 3.6.8

结果遇到了问题:Install fails, “zlib not available” #530

简单来说,我确认了以下几个操作后最终可以了,我也不太确定是那个生效了:

1
2
3
4
5
6
7
8
9
10
# 检查xcode编译工具
xcode-select --install

# 检查几个安装,其实我之前都安装过了,提示是否需要reinstall,我没有重新安装
brew install readline

# 修正zlib的inclue路径
export CFLAGS="-I$(xcrun --show-sdk-path)/usr/include"
export CPPFLAGS="-I/usr/local/opt/zlib/include"
pyenv install 3.6.8

安装成功后,把默认版本修改下

1
2
pyenv versions
pyenv global 3.6.8

检查下版本是否正常,正常的话就算完成了

1
2
python --version
pip --version

需求

在Windows下像Mac和Linux那样使用NVM来管理node版本,使用npm

  • 原来的解决方案用的挺好的:
    https://github.com/coreybutler/nvm-windows

  • 今天更新node版本遇到了问题:
    之前安装的8.x的版本能正常使用,nvm install 10.10.0后发现node有但是npm文件没有下载下来

nvm root看了下安装路径是在AppData下,虚拟链接是在%Program Files%目录下,nvm use的时候会弹出UAC提示,照理应该也不会出错,不过nvm的对应版本目录下并没有下载到npm文件。

解决方案

github上看了下,最近(2018-08-02)更新了一个版本1.1.7:
https://github.com/coreybutler/nvm-windows/releases

我把老的版本卸载干净,然后重新安装了下新版本,然后重新安装,重新安装的时候我把安装目录和软连接目录都放到的我的非系统盘D盘的根目录下:

1
2
D:\nodejs\
D:\nvm\

重新使用nvm安装下最新版本的node:

1
2
3
4
5
6
nvm list available
nvm install 10.10.0
nvm use 10.10.0

node --version
npm --version

验证了下版本,没问题:

1
2
3
4
5
6
D:\>npm --version
6.4.1

D:\>node --version
v10.10.0

引言

2017年1月份,我和老婆带着未满3岁的儿子,从珠海出发,经由香港机场飞往日本大阪关西机场,并在大阪、京都、神户自由行了一个礼拜。

机票、酒店和游玩的行程安排都是老婆负责,而我负责的部分是游玩过程中的交通,结果只能算勉强及格,至少没走丢;这里我要分享的就是些关西旅行交通方面的经验教训。

地图应用

说交通怎么能不先说地图呢?我使用Google Map,需要提前买个日本3G上网卡;无论是地铁还是公交车,Google Map都能比较好的导航到搭车的地方,还有列车时刻表;你可以选择不同的地铁或公交线路;有个特别好的是,你在搭地铁的过程中,如果听不懂日语报站名,或者有些列车没有电子指示当前到哪一站了,这个时候你拿出手机看下Google Map,之前搜索的地铁路线包含了各个途径地铁站的名字和你当前的位置,非常实用。

关西的铁路交通

日本的铁路交通太发达了,所以铁路交通也是关西旅行交通的重头戏。

如果对日本的铁路交通完全没有概念的话,我建议先看下这个网站:西日本旅客铁道株式会社,这是JR-West的官方网站,有中文版;我是回国之后整理攻略才知道的这个网站,后悔莫及,网上各种攻略很多,但都没这个全面实用,走了不少弯路。

JR只是日本众多铁路公司中的一个,不过乘车的规则什么的基本都是类似的,所以你看了JR的乘车指南,再搭乘其他公司的铁路交通,会比较轻车熟路。

列车种类

官方分类看这里:列车种类

高铁(新幹線)、特快(特急列車)、快速(急行列車)、管内快速(新快速)、普快(快速)、普客(普通列車)

这么多分类,我自己搭乘后的经验是,其实就分三类就够了:
新干线、急行、普通。

不同类型的列车,除了车票价格的区别,买票方式上也有区别:
新干线需要单独买票,不能刷卡;
急行和普通的可以先刷IC卡进站,想要快的话,看到急行车上车后再补票,只是不一定能补到指定席。

车票分类

关键字:乘车券、自由席、指定席、急行券、新干线

在日本搭乘铁路交通,你至少需要“乘车券”;
在购票时你可以选择是自由席还是指定席;
而如果你想要搭乘急行或快速的列车,你还需要购买“急行券”;

上述几个选项是可以组合的,最便宜的是自由席乘车券,最贵的是指定席急行券;
需要注意的是并不是每个路线都有急行列车,需要提前查好车次;
还有就是日本的快车和慢车都是在同一个乘车站台,做错慢车的话很有可能早上车却更晚到达目的地。

还是没看明白的,可以看下JR-West的相关说明:搭乘JR-West的方法 购买车票

交通IC卡

各种日本旅行攻略里经常提到的交通IC卡主要是Suica和ICOCA,这两种卡目前都已经是日本全国通用了。

两个单词的读音可以提前学习下,因为这个卡的使用频率非常高:

Suica(スイカ),读音类似:[su i ga]
ICOCA(イコカ),读音类似:[i ko ga]

我用的是ICOCA,关西机场自动售卖机上能买到的也就是这个了,除了新干线需要单独购买乘车券,其他铁路只要不是指定席或者急行券,都可以直接刷卡上车,不用单独买票;另外市内公交车和便利店也能刷ICOCA。

ICOCA & HARUKA

因为我们的到达机场是关西机场,第一站去的是京都,这里说下从关西机场到京都最快最划算的方式:ICOCA & HARUKA

官方介绍看这里:ICOCA & HARUKA

这个有多快捷呢?
从关西机场到京都站,这是最快的方式,没有之一。

这个有多划算呢?
从关西机场搭大巴车到京都应该是要1800日元,而这个ICOCA & HARUKA折扣券是1600日元。

又快又便宜,哪来这么好的事情;
原因在于他是和ICOCA卡捆绑销售的;
ICOCA本身第一次购买的话是需要2000日元,其中包含500日元押金;
虽然是捆绑销售,但是我上面提到了ICOCA和Suica已经全日本通用,而且便利店买东西也能用,其实2000日元很快就花完了的,万一万一花不完还可以退卡(虽然我不推荐退卡)。

特别要注意的是购买方法:只能在JR的绿色办公室里面购买!
关西机场的JR绿色办公室就在JR地铁入口的对面,在一排自动售卖机的右手边,进去找人买就对了,在自动售卖机上是买不到的(T_T)。

儿童乘车

12岁以上算大人、6岁至11岁算儿童、1岁至5岁算幼儿、未满1岁算婴儿;
每名大人最多可免费携带2名幼儿同行;
简单说就是5岁以下的小朋友,大人带着不要钱,不用买票。

这里再说下新干线买票的事情,因为我们当时带着小孩,没有买指定席,结果上车后带着小孩和行李换了好几次位置,乘务员说没办法补指定席的票,很麻烦,所以带小孩坐新干线的话最好直接买指定席。

公交车

我们只在京都坐了公交车,京都的东西南北分布着各种寺庙,但并不是每个寺庙都有地铁直达,这时候就需要市内公交发挥作用了。

提前用Google Map查好公交转乘的路线,还是挺方便的。

Google Map的路线是没问题的,但公交车站台位置不是很准,如果你到了一个站台发现没有Google Map提示的那个公交线路站牌,比如说你要坐25路,结果站牌上怎么都找不到25路的站牌,那肯定是你走错公交站了,再找找看吧。

坐公交车还需要注意的是,如果你下一站下车,到站之前记得按下车上随处可见的红色按钮(不骗你,真的是随处可见)。

的士

都说日本打车贵,一开始都不太敢尝试,但是行李太多了,我们从酒店去京都站搭新干线的时候,还是打了个的士:

3.2KM,1100日元,折合人民币60多块钱

跟国内比肯定是贵很多,但是你要知道日本的地铁也不便宜;
我们还要拿着两件大行李,一个婴儿车,这么算的话价格还是可以接受的。

的士作为一种交通途径,在合适的时候还是可以选用的,并不是天价。

长途大巴

我们从神户有马温泉去大阪的时候搭乘的是大巴车,车上10个人不到;
想不到的是大巴车上竟然没什么味道,更没有国内大巴车上那种无法描述的奇怪的大巴车味道……

大巴车快到大阪的时候,最左边的车道堵车堵了大概有10公里,但是右边的两个车道都还在正常快速行驶;中间好几次我都在想为什么右边这么快,没有人变道?后面才知道这10多公里的车都在排队下高速,高速出口有点堵车,但是只堵了最左边的一条道而已,佩服。

交通IC卡充值

地铁站买票的自动售卖机可以充值;
地铁站里面有精算机可以充值;
便利店可以充值。

关于问路

日本人英语听力都还不错,只是不太愿意说,你要是英文好的话直接英文问路完全没问题。

我的做法是,用有道翻译,把问路的关键字翻译成日语;注意是只是关键字而已,不要整个句子,因为我的实践结果告诉我,翻译句子比较坑;把关键字的日文给日本人看下,他们基本能猜猜八九不离十;实在不行再配合两句蹩脚的英语,嗯,应该可以了……

还不行的话你就比划比划或者试下眼神交流吧

对这次旅行交通的整体感受

准时,方便,不需要像国内那样预留太多的时间。

费用方面,这次旅行在关西交通上的花费,只是旅行费用中比较小的一部分;
我们也没有使用更划算的周游券,主要是因为我们带着小孩,行程本来就不固定,而且我们是三个城市走马观花式的自由行,不太适合;

只要行程确认,交通攻略上一个Google Map也就够了,还都是中文界面;
日本的交通很发达但也很规范,熟悉了规则之后会发现,日本的交通出行其实比你在国内一个陌生城市还要简单。

== The End ==

问题

我的问题是proxychains4 telnet 是没问题的,但是proxychains4 pod update就会报下面这样的警告,然后失败:

[proxychains] preloading ./libproxychains4.dylib
dyld: warning: could not load inserted library ‘./libproxychains4.dylib’ into library validated process because no suitable image found. Did find:
./libproxychains4.dylib: code signing blocked mmap() of ‘./libproxychains4.dylib’

我的环境如下:

  • macOS Sierra 10.12.3
  • proxychains-ng 4.12_1
  • SIP之前配置过用的是:csrutil enable –without debug,查询的状态如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $ csrutil status
    System Integrity Protection status: enabled (Custom Configuration).

    Configuration:
    Apple Internal: disabled
    Kext Signing: enabled
    Filesystem Protections: enabled
    Debugging Restrictions: disabled
    DTrace Restrictions: enabled
    NVRAM Protections: enabled
    BaseSystem Verification: enabled

    This is an unsupported configuration, likely to break in the future and leave your machine in an unknown state.

寻找原因

一开始是找到了这篇issues:Proxychains4 with brew for MacOs error #109,以为是我的proxychains4版本问题或者是我的SIP问题,所以做了以下尝试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 进入系统的recovery mode,禁用SIP
csrutil disable
reboot

# 重启后卸载老的proxychains
brew tap beeftornado/rmtree
brew rmtree proxychains-ng

# 重新安装
proxychains-ng --universal

# 确认下proxychains-ng的配置是否正确,如果不正确就再配置下proxychains.conf
# 确认下telnet是否正常
proxychains4 telnet google.com 80

# 再尝试下pod update
proxychains4 pod update

# 问题依旧:code signing blocked mmap()

问题所在

在另外一个issue78:Not working on OS X 10.11 due to SIP #78中看到了被点赞了4次的这个回答,让我最后解决了问题:

It only happens if you execute a system binary using proxychains, e.g. proxychains4 ssh user@server. For now, a workaround is to copy the executable to another location (e.g. cp /usr/bin/ssh ~/XXX), and use it (e.g. proxychains4 ~/XXX/ssh user@server). You can modify the path variable so that ~/XXX/ssh is executed instead of /usr/bin/ssh, when you just type “ssh”.

一句话描述问题:

proxychain尝试注入系统bin目录下的二进制文件会出现这种情况,解决方案就是换个非系统目录的文件来执行和注入就可以了。

解决方案

根据上面的问题描述,再考虑pod update的实际操作,其实就是调用git去更新,而我的git用的是系统自带的:

1
2
3
$ which git
/usr/bin/git

这样一来问题就变成了让pod update使用我自己安装的git就可以了:

1
2
3
4
5
brew install git

# 修改.bash_profile,增加下面的export配置,优先搜索/usr/local/bin目录
# 这样修改后,terminal下使用git就会优先使用我们刚刚安装的git版本了
export PATH=/usr/local/bin:/usr/local/sbin:${PATH}

好了,配置好了,再试一下,成功了:

1
proxychains4 pod update

关于SIP

最后我验证了下,SIP其实不需要disable,csrutil enable –without debug下proxychains也是能正常工作的

总结

说下我这里能够正常运行的环境和因素:

  • macOS Sierra 10.12.3
  • proxychains-ng 4.12_1
  • 进入系统的recovery mode,打开terminal,csrutil enable –without debug
    可以使用这个命令查询的状态,我的状态如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $ csrutil status
    System Integrity Protection status: enabled (Custom Configuration).

    Configuration:
    Apple Internal: disabled
    Kext Signing: enabled
    Filesystem Protections: enabled
    Debugging Restrictions: disabled
    DTrace Restrictions: enabled
    NVRAM Protections: enabled
    BaseSystem Verification: enabled

    This is an unsupported configuration, likely to break in the future and leave your machine in an unknown state.
  • 我的git使用的是brew install git的版本:
    1
    2
    $ which git
    /usr/local/bin/git

    参考材料

    Not working on OS X 10.11 due to SIP #78
    Proxychains4 with brew for MacOs error #109
    code signing blocked mmap() #159

问题

由于使用了Multipeer Connectivity和NSOutputStream,在某些网络环境切换的情况下,总是会遇到这样的crash:

Signal 13 was raised. SIGPIPE

查找资料后,发现最简单的解决方案就是忽略这个错误:

signal(SIGPIPE, SIG_IGN);

但实际的结果是我们设置了还是会继续出现闪退

原因

排查了很久后才发现,问题的原因是BugTags也会控制这个开头,默认是不忽略,而且这个默认显然代码是主动调用了,而不是我们期望的不调用就不设置:

1
2
3
4
/**
* 是否忽略 PIPE Signal (SIGPIPE) 闪退,默认 NO
*/
@property(nonatomic, assign) BOOL ignorePIPESignalCrash;

解决方案

设置BugTags的对应开关即可:

1
2
3
4
// Bugtags
BugtagsOptions *options = [[BugtagsOptions alloc] init];
options.ignorePIPESignalCrash = YES;
[Bugtags startWithAppKey:BugTagsAPPKEY invocationEvent:BTGInvocationEventNone options:options];

思考

这个问题我们排查了很久,一直在找我们自己的原因,但其实这种bug因为跟网络环境有关系不容易重现;
其实反思后发现,排查这个问题我们应该先做一个事情,就是手动出发SIGNAL 13,看闪退是否被正确忽略;
而我们恰恰没做这个事情,思路上的疏漏导致了之前的大量工作变成了无用功。

参考材料

如何在 iOS 上避免 SIGPIPE 信号导致的 crash (Avoiding SIGPIPE signal crash in iOS)
NSStream close
Writing OutputStreams
[深入浅出Cocoa]iOS网络编程之NSStream
How to make a NSStream instance reusable?

问题

有个视频播放页面需要手动控制页面的横竖屏状态,使用setStatusBarOrientation可以达到这个效果;
但是很奇怪的事情发生了,我们发现代码在某台机器的某种情况下会出现setStatusBarOrientation调用无效。

原因

直接的原因是我们使用了BugTags,并且在白名单用户登录后打开了BugTags的悬浮小球,悬浮小球出现的时候setStatusBarOrientation调用就会无效;

分析后发现真正的原因是,页面已经不是the top-most full-screen view controller了:

The setStatusBarOrientation:animated: method is not deprecated outright. It now works only if the supportedInterfaceOrientations method of the top-most full-screen view controller returns 0

解决方案

知道原因就简单了,需要全屏的时候隐藏BugTags的悬浮球即可

BTGInvocationEventNone

参考

setStatusBarOrientation:animated: not working in iOS 6
setStatusBarOrientation 未生效的解决办法

前言

之前用的Jekyll,最大的痛点是对Markdown的代码格式支持的不是很好,尝试过一些方法,不过都不太理想;
最近偶然看到Hexo的介绍,决定尝试一下;
迁移过程中有些小Tips觉得有必要小结下,对于迁移的人来说还是很有用的。

迁移步骤

  • 我的系统是Mac OS 10.12.1

  • 我原来的Jekylly代码在这个仓库下:https://github.com/linyehui/linyehui.github.io

  • 代码备份

    • 从master建立分支用于代码备份(多留个心眼,后面发现很有用),备份用的分支:release-jekyll
    • 保险起见,我把本地的github 仓库也备份了下
  • 创建一个新的github仓库用于存储我的hexo blog代码:linyehui-hexo-blog,git clone到本地,得到hexo的本地根目录:linyehui-hexo-blog

  • 初始化hexo本地环境

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    git clone https://github.com/linyehui/linyehui-hexo-blog
    cd linyehui-hexo-blog
    npm install -g hexo-cli

    hexo init

    npm install hexo --save
    npm install hexo-generator-index --save
    npm install hexo-generator-archive --save
    npm install hexo-generator-category --save
    npm install hexo-generator-tag --save
    npm install hexo-server --save
    npm install hexo-deployer-git --save
    npm install hexo-deployer-heroku --save
    npm install hexo-deployer-rsync --save
    npm install hexo-deployer-openshift --save
    npm install hexo-renderer-marked@0.2 --save
    npm install hexo-renderer-stylus@0.2 --save
    npm install hexo-generator-feed@1 --save
    npm install hexo-generator-sitemap@1 --save

    这一步完成的时候,你就可以在根目录下执行hexo server,并且可以在浏览器上看到Hello World页面了

  • 配置下_config.yml,这里就是各种名字的配置,配置完了之后,浏览器预览没问题就行了

  • 配置theme,我用的是iissnan/hexo-theme-next

    1
    2
    3
    4
    5
    6
    7
    # git clone到另外一个目录,然后复制到./linyehui-hexo-blog/themes/next目录下
    git clone https://github.com/iissnan/hexo-theme-next

    # 复制hexo-theme-next目录到./linyehui-hexo-blog/themes/next

    # 修改_config.yml中的这行文件,主题名字和目录名一致
    theme: next

    根据自己的需要修改下主题的配置:./linyehui-hexo-blog/themes/next/_cofig.yml

  • 迁移之前博客的文章

    • 把Jekyll版本的_posts目录下的文件复制到./linyehui-hexo-blog/source/_posts/
    • 把之前根目录下的媒体文件目录:media(图片)复制到./linyehui-hexo-blog/source/media

    浏览器中预览下,首页应该能看到你的文章了,数据迁移非常简单,就这一步

  • 创建tags文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ cd linyehui-hexo-blog
    $ hexo new page tags

    # 文件内容如下:
    title: 标签
    date: 2016-12-08 12:36:06
    type: "tags"
    comments: false
    ---
  • 上面都没问题,就可以发布了

    1
    2
    ## 发布前记得generate,保险起见带上--generate参数
    hexo deploy --generate

    每次发布,你都会发现你的github仓库的master分支只有两次commit,也就是说hexo deploy把老的master分支给删除了……

  • 发布没问题,我就把新的代码仓库下的文件给入库了,记得配置下.gitignore

    1
    2
    3
    4
    5
    .DS_Store
    .deploy_git
    db.json
    node_modules
    public
  • 到这一步blog的主体功能已经迁移完成,剩下的就是些美化、图标、头像,这里就不细说了

迁移Tips

  • hexo deploy 每次会将master分支删除后重新创建,迁移之前的代码切记要建立分支进行备份
  • hexo deploy 所做的事情:删除现有的master分支,并建立新的master分支,把hexo目录下的.deploy_git目录push到master分支上
  • source/_posts目录支持按年份划分目录,_config.yml中这么配置就行了:
    1
    new_post_name: :year/:year-:month-:day-:title.md # File name of new posts

参考文章

Jekyll迁移到Hexo搭建个人博客
将博客从Jekyll迁移至Hexo

问题描述

对一个NSMutableArray进行排序,却造成了内存泄漏……

代码说明:

在多线程的流程中,存在一个NSMutableArray *sortList;

对sortList进行排序,一开始用的方法是这样的,有内存泄漏:

1
2
3
NSArray *sortedSendQuery = [self.sortList sortedArrayUsingFunction:frameDataCompare context:NULL];
[self.sortList removeAllObjects];
[self.sortList addObjectsFromArray:sortedSendQuery];

修正后的代码只有一行,没有内存泄漏:

1
[self.sortList sortUsingFunction:frameDataCompare context:nil];

原因分析

NSArray的排序方法是:sortedArrayUsingFunction
NSMutableArray的排序方法是:sortUsingFunction
self.sortList是一个NSMutableArray,直接使用NSMutableArray的排序方法sortUsingFunction不需要进行内存复制,可以提高性能;

除了性能问题,由于在多线程中使用,而且如果NSMutableArray中的object内存分配来自于其他线程的,那么不必要的内存复制会导致内存泄漏,出现的内存泄漏问题类似于:
Memory leaks from Multidimensional array: NSMutableArray, NSArray, addObject and insertObjectAtIndex

结论

对于NSMutable的使用,要留意调用正确的方法,能不做类型转换就不做

问题描述

使用ARC的过程中,由于循环引用或者其他使用不当,会造成对象的内存没有及时释放,而这些内存在页面关闭的时候又会随着页面的生命周期一起释放,这就给我们排查“内存泄漏”带来了麻烦;

这种内存问题用Leaks是找不出问题的,因为这些内存其实只是释放不及时,而不是没有释放。

解决方案

使用Instruments Allocations的Mark Generation功能能帮助我们排查出这种问题。

使用之前先说几个Instruments的调试技巧

1、调试内存可以用Debug模式来调试,但是记得在工程设置中把Debug模式的Debug Information Format设置成:DWARF with dSYM File,否则无法在Instruments中看到出问题的符号对应的代码

2、性能调试真机调试的时候有时候连接不上,试试重启手机,实在还是不行,可以换成Debug模式,相对没那么容易连接不上手机(修改方法:Edit Scheme->Profile->Build Configuration 修改成Debug)

一个典型案例,问题描述

假定我们有一个页面MainViewController,打开这个页面后会开始进行视频直播,在直播的过程中会出现内存没有及时释放,比如10分钟后应用的内存占用已经达到了200MB,但是页面一关闭内存有都正确释放了。

针对这样的一个问题我们应该怎么来排查呢?

排查步骤

1、Xcode中启动Profile工具
image

2、选择Allocations工具,点击红色按钮开始“录制”,这时Instruments会启动我们的应用
image

3、打开有内存释放问题的页面,稍微等一下,等页面的常规初始化完成,比如等个10秒钟

4、从Instruments右下角的区域切换到“Display Settings” Tab页,并点击“Mark Generation”生成第一个Generation:Generation A;
这个相当于是我们判断内存没有及时释放的起始点
image

5、为了让没有及时释放的内存泄漏问题,累积的更多一点,第二次Mark的时间可以稍微多等久一点(等多久这个取决于你的内存问题出现所需要的时间,比如2分钟),然后再次点击“Mark Generation”,这个时候你就会发现两次Generation之间的内存增长,而这里面的增长其实就是我们所寻找的没有及时释放的内存:

image

6、点击小箭头查看更详细的信息,根据内存占用的比例,找到占用比例最高的,并且不断展开,直到找到我们自己的代码,这就是出问题的地方了。
image

需求

1、使用AudioEngine进行音频采集,并加入部分EQ等效果器

2、将混音后的音频输出为PCM的Buffer

3、将PCM Buffer编码为AAC

遇到的坑

AudioEngine的这个方法有Bug: installTapOnBus

1
- (void)installTapOnBus:(AVAudioNodeBus)bus bufferSize:(AVAudioFrameCount)bufferSize format:(AVAudioFormat * __nullable)format block:(AVAudioNodeTapBlock)tapBlock;

iOS 10一下的版本,installTapOnBus的第二个参数bufferSize设置是无效的,
比如我设置bufferSize为1024,如果我的format中的bytePerFrame是4,那么最后出来的bufferSize应该是1024*4=4096才对,但是在iOS 10一下的版本,出来的bufferSize每次都是66148

如果我们要进行AAC编码,那么AAC编码的bufferSize必须是能够被1024整除的,而66148是无法被1024整除

如果AAC编码的时候没有对齐bufferSize和1024,就会出现音频数据丢失的情况

解决这个问题的两个角度

1、让installTapOnBus出来的数据能够被1024整除

2、自己用做对齐的事情,那么部分音频数据的采集时间戳就需要自己构造

我个人比较偏向第一种,这样就不存在构造时间戳的问题。

解决方案

第一步:installTapOnBus的Block中设置frameLength

1
2
3
4
5
6
7
const uint32_t frameLength = 1024;
[_inputNode installTapOnBus:0 bufferSize:frameLength format:[_inputNode outputFormatForBus:0] block:
^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
// iOS 10以下需要这么设置才可以缩短Block回调的Buffer大小和同时也能缩短Block回调的时间间隔
// linyehui 2016-11-11
buffer.frameLength = frameLength;
}];

参考:I want to call 20 times per second the installTapOnBus:bufferSize:format:block:

第二步:不要使用AVAudioPCMBuffer的mutableAudioBufferList

如果你使用了步骤一的代码,那么installTapOnBus回调出来的AVAudioPCMBuffer,不要使用mutableAudioBufferList,必须使用audioBufferList。

因为mutableAudioBufferList的mDataByteSize是frameCapacity,而audioBufferList的mDataByteSize是真实的frameLength