第1节:ReactNative开发环境搭建与运行

[TOC]

Android_devices

一、Android中找不到devices的问题处理

查询设备,请使用adb devices

错误提示:error No Android devices connected.

no_Android_device

此时需要:

①、adb kill-server

②、adb start-server

adb devices

原因:模拟器在运行一段时间后,adb服务有可能会出现异常。这时需要重新对adb服务关闭和重启。

附:reload 命令adb shell input keyevent 82

安卓设备值来源:

设备_adb devices

安卓模拟器设置VPN

devtool_log_home_page

设置代理为 10.0.2.2 (固定) + 代理的端口

如你的终端代理命令是

1
export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890

则应该输入的内容为

1
2
10.0.2.2
7890

第1节:React开发环境搭建与运行

[TOC]

React

一、检测您是否具备了ReactNative的开发环境

1、环境已完善时

2、环境未完善时

2.1、React Native 环境搭建

  • 1、按照创建新的 React 应用进行环境搭建。

  • 安装方法:npm install create-react-app -g

  • 创建一个应用:create-react-app my-app

二、创建第一个ReactNative项目

1、创建项目

方法一:执行react-native init cj_rn_logindemo

此步骤会生成React Native依赖包node_modules文件夹。每个项目中都要有这部分内容。且该部分的内容可以直接被复制使用。所以,创建项目还有另一个方法。

生成的cj_rn_logindemo工程详见cj_rn_logindemo

方法二:原有iOS项目集成RN

参考来源:React Native 集成到原生项目(iOS)

react-native 指定版本创建项目

因为react-native更新太快,每次react-native init初始化项目的时候,都会安装最新的rn版本,导致之前的一些api不兼容等问题。
所以我们需要安装指定版本的rn来初始化项目:

1
react-native init demo --version 0.59.8
1
2
3
4
5
6
7
react-native 安装指定的版本

react-native init name --version 0.xx.xx

react-native 降低版本

react-native --save [react-native@0.xx.xx](mailto:react-native@0.xx.xx)

三、运行ReactNative项目

1、运行方法

1.1、常见运行方法

命令行运行项目

进入项目根目录cd TestRNProject

运行iOS项目react-native run-ios

1.2、像iOS原生那样在Xcode中运行

双击ios/TestRNProject.xcodeproj文件然后在Xcode中点击Run按钮。(最常用,对iOS开发者)

1.3、在WebStorm中直接运行

详见本文中的附一、进阶:为WebStorm搭建React Native开发环境

1.4、Android中找不到devices的问题处理

错误提示:error No Android devices connected.

rn_Android_device

此时需要:

①、adb kill-server

②、adb start-server

原因:模拟器在运行一段时间后,adb服务有可能会出现异常。这时需要重新对adb服务关闭和重启。

附:reload 命令adb shell input keyevent 82

2、运行项目的常见问题

2.1、RN项目报错“RCTBundleURLProvider.h” file not found

从网上下载别人的ReactNative项目,打开iOS项目的时候,xcode会报错,提示:“RCTBundleURLProvider.h” file not found

原因:node_modules文件夹不存在 或者 node_modules文件夹下的包和当前版本不匹配

解决方法:
1、打开Mac里面的终端,进入项目所在的文件夹目录;
2、把项目里面的 node_modules 文件夹删除掉,然后执行 npm install 命令
3、npm install安装完成后, 执行react-native upgrade命令
4、最后重新打开Xcode,clean一下,应该就没有问题了。

附一、进阶:为WebStorm搭建React Native开发环境

PS:最近又回来搞RN了,以下虽然都是一些老知识了,但你也许有用

问题背景:我不想在WebStorm/VSCode、终端、XCode中来回切换。我想在WebStorm直接运行ReactNative项目
问题背景:我不想在WebStorm/VSCode、终端、XCode中来回切换。我想在WebStorm直接运行ReactNative项目
问题背景:我不想在WebStorm/VSCode、终端、XCode中来回切换。我想在WebStorm直接运行ReactNative项目

1、为Run添加Add New Configuration

image-20190519205612140

点击,Edit configurations…会进入如下界面

image-20190519221955691

点击 + ,选择npm

image-20190519222125672

2、完善所添加的Configuration内容

外层内容填写如下:

image-20190519222852727

External tool内容如下:

image-20190519223454054

上述步骤2中的各值分别为:

  • Program:

    which react-native中获得的 /usr/local/bin/react-native

    image-20190519223703388

  • Arguments:

    run-ios --simulator="iPhone Xʀ"

    run-ios --device="iPhone7_Qian"

    run-android --deviceId="emulator-5554"

附:启动和关闭ADB服务(adb start-serveradb kill-server

iOS设备值来源:

设备_iOS

安卓设备值来源:

设备_adb devices

执行的命令有:

注:前提要cd到工程中后,执行react-native run-android --help才有效。

设备_react-native run-android  --help

  • Working directory:

    建议通过如下图添加宏的方式添加Working directory值。

    image-20190519224816841

添加完成后的图为:

image-20190519225336448

至此,配置结束。接下来我们试下效果。

恭喜,配置结束,休息一下吧

3、试下配置效果是否有效

Run菜单中选择刚才添加的执行项,其就会在执行我们所设置的选项,即执行/usr/local/bin/react-native run-ios "--simulator=iPhone Xʀ"

执行结果如下:

image-20190519225942964

同样的其也会在终端中有如下显示:

image-20190519231236234

当然,如果你使用快捷键”Cmd+R”也能达到这个效果。

image-20190519231502977

经验证,配置无误,使用有效。

经验证,配置无误,使用有效,打工告成

四、Xcode11问题

2019.10.27: Could not find iPhone 11 simulator. Run CLI with –verbose flag for more details.

1、初步分析:

Xcode 11无法在模拟器上运行React-Native应用程序。看结论为没有你想运行的那个模拟器iPhone X。
2、判断分析正确否:

单独运行模拟器,在顶部菜单中,在“硬件,设备,iOS 13.0”下查看。

rn_Could not find simulator

①当您执行run-ios时,react-native旨在匹配请求的设备。内部硬编码默认值为iPhoneX。

②尝试匹配请求的设备的函数在:

/node_modules/@react-native-community/cli-platform-ios/build/commands/runIOS/findMatchingSimulator.js

设计此功能是为了使您可以为其提供设备和可选的版本号。

③如果找不到给定的设备和版本,则默认情况下它将使用列表中的第一个设备返回匹配项。

3、解决:

如上所述,首先自己运行模拟器,并记下所需的iPhone或iPad。

然后将此名称作为可选参数传递给run-ios命令行命令,如下所示:

第1节:ReactNative开发环境搭建与运行

[TOC]

ReactNative

一、检测您是否具备了ReactNative的开发环境

1、环境已完善时

2、环境未完善时

2.1、React Native 环境搭建

二、创建第一个ReactNative项目

1、创建项目

方法一:执行react-native init cj_rn_logindemo

此步骤会生成React Native依赖包node_modules文件夹。每个项目中都要有这部分内容。且该部分的内容可以直接被复制使用。所以,创建项目还有另一个方法。

生成的cj_rn_logindemo工程详见cj_rn_logindemo

方法二:原有iOS项目集成RN

参考来源:React Native 集成到原生项目(iOS)

react-native 指定版本创建项目

因为react-native更新太快,每次react-native init初始化项目的时候,都会安装最新的rn版本,导致之前的一些api不兼容等问题。
所以我们需要安装指定版本的rn来初始化项目:

1
react-native init demo --version 0.59.8
1
2
3
4
5
6
7
react-native 安装指定的版本

react-native init name --version 0.xx.xx

react-native 降低版本

react-native --save [react-native@0.xx.xx](mailto:react-native@0.xx.xx)

三、运行ReactNative项目

1、运行方法

1.1、常见运行方法

命令行运行项目

进入项目根目录cd TestRNProject

运行iOS项目react-native run-ios

1.2、像iOS原生那样在Xcode中运行

双击ios/TestRNProject.xcodeproj文件然后在Xcode中点击Run按钮。(最常用,对iOS开发者)

1.3、在WebStorm中直接运行

详见本文中的附一、进阶:为WebStorm搭建React Native开发环境

1.4、Android中找不到devices的问题处理

错误提示:error No Android devices connected.

rn_Android_device

此时需要:

①、adb kill-server

②、adb start-server

原因:模拟器在运行一段时间后,adb服务有可能会出现异常。这时需要重新对adb服务关闭和重启。

附:reload 命令adb shell input keyevent 82

2、运行项目的常见问题

2.1、RN项目报错“RCTBundleURLProvider.h” file not found

从网上下载别人的ReactNative项目,打开iOS项目的时候,xcode会报错,提示:“RCTBundleURLProvider.h” file not found

原因:node_modules文件夹不存在 或者 node_modules文件夹下的包和当前版本不匹配

解决方法:
1、打开Mac里面的终端,进入项目所在的文件夹目录;
2、把项目里面的 node_modules 文件夹删除掉,然后执行 npm install 命令
3、npm install安装完成后, 执行react-native upgrade命令
4、最后重新打开Xcode,clean一下,应该就没有问题了。

附一、进阶:为WebStorm搭建React Native开发环境

PS:最近又回来搞RN了,以下虽然都是一些老知识了,但你也许有用

问题背景:我不想在WebStorm/VSCode、终端、XCode中来回切换。我想在WebStorm直接运行ReactNative项目
问题背景:我不想在WebStorm/VSCode、终端、XCode中来回切换。我想在WebStorm直接运行ReactNative项目
问题背景:我不想在WebStorm/VSCode、终端、XCode中来回切换。我想在WebStorm直接运行ReactNative项目

1、为Run添加Add New Configuration

image-20190519205612140

点击,Edit configurations…会进入如下界面

image-20190519221955691

点击 + ,选择npm

image-20190519222125672

2、完善所添加的Configuration内容

外层内容填写如下:

image-20190519222852727

External tool内容如下:

image-20190519223454054

上述步骤2中的各值分别为:

  • Program:

    which react-native中获得的 /usr/local/bin/react-native

    image-20190519223703388

  • Arguments:

    run-ios --simulator="iPhone Xʀ"

    run-ios --device="iPhone7_Qian"

    run-android --deviceId="emulator-5554"

附:启动和关闭ADB服务(adb start-serveradb kill-server

iOS设备值来源:

设备_iOS

安卓设备值来源:

设备_adb devices

执行的命令有:

注:前提要cd到工程中后,执行react-native run-android --help才有效。

设备_react-native run-android  --help

  • Working directory:

    建议通过如下图添加宏的方式添加Working directory值。

    image-20190519224816841

添加完成后的图为:

image-20190519225336448

至此,配置结束。接下来我们试下效果。

恭喜,配置结束,休息一下吧

3、试下配置效果是否有效

Run菜单中选择刚才添加的执行项,其就会在执行我们所设置的选项,即执行/usr/local/bin/react-native run-ios "--simulator=iPhone Xʀ"

执行结果如下:

image-20190519225942964

同样的其也会在终端中有如下显示:

image-20190519231236234

当然,如果你使用快捷键”Cmd+R”也能达到这个效果。

image-20190519231502977

经验证,配置无误,使用有效。

经验证,配置无误,使用有效,打工告成

四、Xcode11问题

2019.10.27: Could not find iPhone 11 simulator. Run CLI with –verbose flag for more details.

1、初步分析:

Xcode 11无法在模拟器上运行React-Native应用程序。看结论为没有你想运行的那个模拟器iPhone X。
2、判断分析正确否:

单独运行模拟器,在顶部菜单中,在“硬件,设备,iOS 13.0”下查看。

rn_Could not find simulator

①当您执行run-ios时,react-native旨在匹配请求的设备。内部硬编码默认值为iPhoneX。

②尝试匹配请求的设备的函数在:

/node_modules/@react-native-community/cli-platform-ios/build/commands/runIOS/findMatchingSimulator.js

设计此功能是为了使您可以为其提供设备和可选的版本号。

③如果找不到给定的设备和版本,则默认情况下它将使用列表中的第一个设备返回匹配项。

3、解决:

如上所述,首先自己运行模拟器,并记下所需的iPhone或iPad。

然后将此名称作为可选参数传递给run-ios命令行命令,如下所示:

Weex

Weex

一、集成 Weex 到已有应用

集成 Weex 到已有应用(官网)

二、创建Weex项目

创建Weex项目方法:

  • ①、在终端执行weex create 目录即可。

    ②、cd 到项目/platforms/platforms.json所在的platforms目录下执行sudo npm run pack:ios(如果不是在该目录会提示您platforms.json的错误)

  • 在使用前,请使用weex -v进行weex环境检测。详见【附1】Weex开发环境检测

三、Weex编码

  • 编写.we/vue代码

  • 将.we/vue代码文件转为js文件放入项目中

1、将.we/vue文件转换成 js文件的终端命令

.we/vue 转换成 js文件的终端命令 作用
weex compile dir js 会将dir文件夹下的所有we文件转换到js文件夹下
weex compile dir/xxx.we js 会将dir文件夹下的xxx.we文件转换为js文件存到js文件夹下
weex compile dir/xxx.vue js 会将dir文件夹下的xxx.vue文件转换为js文件存到js文件夹下

2、vue文件编写

【附1】Weex开发环境检测

【附2】Weex工具WebStorm的安装

【附1】Weex开发环境检测

通过执行显示当前weex版本的weex -v命令,验证Weex开发环境是否都具备了,如果都成功的时候,其会如下图所示:

image-20181217175137799

安装命令

1
2
3
4
5
6
// 国外可使用方法:
npm install -g weex-toolkit

// 国内请使用方法,否则weekpack安装不上,导致weex create命令执行出错:
sudo npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install -g weex-toolkit

weex开发环境安装的命令对比

使用npm 使用cnpm
weex-bulider 安装成功 安装成功
weex-previewer 安装成功 安装成功
weexpack
(若无会导致执行weex create失败)
安装失败 安装成功

更详细的weex安装请查看

通常,安装了 Node.js 环境,npm 包管理工具也随之安装了。npm 是一个 JavaScript 包管理工具,它可以让开发者轻松共享和重用代码。

遇到的问题1、weex -v命令(显示当前weex版本)一直报错

解决办法:通过终端重新执行npm install -g weex-toolkit命令来重新安装weex

遇到的问题2、weex create 项目名命令(创建weex项目)出错

此问题,是你没正确安装weex-toolkit导致,详细看下个问题npm install -g weex-toolkit命令安装过程有错

遇到的问题3、npm install -g weex-toolkit命令安装过程有错

错误如下:

image-20181217173509332

分析可得错误原因是:npm的安装权限不足。

加上sudo后,执行sudo npm install -g weex-toolkit依旧如此。

最终通过进入/usr/local/lib/node_modules删除之前已经安装的weex-toolkit后,然后执行sudo npm install -g weex-toolkit安装通过。此时你会发现安装过程还有一些错误提示

image-20181217175640331

此问题引起的连锁反应是①终端执行weex -v的结果只有weex-bulider和weex-previewer,没有weexpack,从而导致②终端执行weex create 项目名失败

image-20181217174116400

【附2】Weex工具WebStorm的安装

1、WebStorm软件安装&破解

2、WebStorm配置vue环境

3、WebStorm其他配置

旧电脑(Mac)数据清理

[TOC]

清理项

系统偏好设置-用户与群组,左下角解锁点击加号添加一个新的管理员账号 点击左上角苹果图标,注销登陆新账号 同一个位置选择之前的账号点击减号删除 删除的时候选择删除文件就可以了

  • AppleID(涉及appstore)

    • 系统设置里退出
    • appStore –> 商店 –> 退出登录
  • iCloud账号(涉及备忘录、提醒事项、Safari等)

  • 设置 –> iCloud
  • 邮件、备忘录、提醒事项 清理

    • [ ] 清理系统邮箱内容

      1
      2
      进入 ~/Library/Containers/com.apple.mail/Data/Library/Mail Downloads
      请不要在终端用open命令,因为Mail Downloads这个有空格。
  • 备忘录、提醒事项
  • 钥匙串
  • 清除系统钥匙串中的密码等相关
  • 浏览器

    • Safari、Chrome账号&密码

    附:Mac如何查看Safari保存的密码

    • Safari、Chrome浏览记录
    • Safari、Chrome个人收藏
  • 微信记录
    在输入框中输入 ~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/,然后找到com.tencent.xinWeChat删除整个文件夹即可。

  • QQ记录
    在输入框输入~/Library/Containers/com.tencent.qq,删除整个文件即可。

  • Keynote、Page等清除打开的文件记录

    • 打开软件 –> 文件 –> 打开最近使用 –> 清除菜单
  • 应用软件账号删除(百度网盘、Evernote、SourceTree等)

  • 其他

    • iPhone模拟器重置

      1
      顶部 -> Erase All Content And Setting
  • 应用软件删除

  • 应用软件账号删除(百度网盘、Evernote、SourceTree等)

  • 文件夹删除(文稿、图片、下载内容等)

新电脑(Mac)初始安装

[TOC]

一、系统设置

  • [x] mac三指拖动设置

    1
    系统设置 --> 辅助功能 --> 鼠标与触控板 --> 触控板选项 --> 三指拖移
  • [x] mac显示隐藏文件

    1
    defaults write com.apple.finder AppleShowAllFiles -boolean true;killall Finder

二、搜狗输入法、QQ、微信、Wps

三、琐事记录&计划

1、开始记录各种琐事:个人博客Git

2、找到对应的文件/应用:Alfred、QSpace、Manico、Magnet

3、编辑:Typora、SublimeText、截图xnip、翻译Bob、粘贴Paste

4、列计划:omniplan、Xmind

5、其他:Bartender

四、代码相关

1、Sourcetree、gitlab、github项目、ssh

git clone的时候遭遇fatal: early EOF fatal: index-pack failed解决办法

一般这种情况都是因为项目分支过多,导致你要下载的东西太多,从而引起这个问题。

1
2
3
4
5
6
7
8
9
10
11
❌无效解法:
网上有一个流传比较多的修改方案:
git config --global core.compression -1
使用上述命令,来修改压缩模式,我尝试过了没有用。

✅有效解法:
分析因为引起这个问题的根源是文件过多,所以我们可以分批次下载文件,先下载一部分,再下载剩下的。以下就是我的解决方案:
git config --global core.compression 0 # 1、关闭 core.compression
git clone --depth 1 url xxx本地库名 # 2、然后使用depth这个指令来下载最近一次提交
cd xxx本地库名 && git fetch --unshallow # 3、然后获取完整库
git pull --all # 4、最后pull一下查看状态,问题解决
  • [x] Sourcetree

  • [x] 生成ssh

    1
    2
    3
    4
    5
    6
    7
    # 查看是已有
    cd ~/.ssh

    # 没有则执行生成
    ssh-keygen

    # 生成后,拷贝id_rsa.pub中的秘钥到git的ssh配置中
  • [x] gitlab

  • [x] github

    mac中用sourcetree下载clone代码,结果出错:warning: templates not found in /usr/local/git/share/git-core/templates

    image-20201103134013411

    解决办法:

    1
    2
    sudo mkdir -p /usr/local/git/share/git-core/templates
    sudo chmod -R 755 /usr/local/git/share/git-core/templates

    image-20201103135013020

2、Cocoapods、Spec

  • [x] Cocoapods官网

    1、安装Cocoapods

    1
    sudo gem install cocoapods

    2、拷贝cocoapods的Spec

    1
    2
    3
    4
    pod repo # 找到pod库的路径 得到  open ~/.cocoapods/repos
    cd 到上述路径
    拷贝cocoapods的Spec,并重命名该仓库名字
    pod repo update master # 将指定的仓库更新

3、go2sheell

五、iOS编码工具

1、Xcode

  • Xcode

2、CodeSnippets

1、进入 BlackMagic Git工程中的 CodeSnippets 并下载

2、CodeSnippets所在位置

①自己定义的代码块是在以下目录下:
~/Library/Developer/Xcode/UserData/CodeSnippets/

3、iSwift破解版

4、其他 cocoapods APP

5、沙盒 XSimulatorMng

沙盒数据查看.md

六、Android

1、Android Studio

七、WebStrom

1、WebStrom APP

2、WebStorm细节配置

2.2、WebStorm配置直接运行RN项目

具体操作请查看:[ReactNative开发环境搭建与运行](../../ReactNative/1入门/ReactNative开发环境搭建与运行.md)

七、AI 智能编程助手

AI编程助手 来源 官网 备注 推荐指数
codegeex 清华 https://codegeex.cn/ ⭐️⭐️⭐️⭐️⭐️
Codeium 初创公司 https://codeium.com/ 开源:https://github.com/THUDM/CodeGeeX
CodeWhisperer 亚马逊

image-20240317232514136

八、抓包 Charles

  • Charles 下载

    进入百度网盘,搜索Charles,目前实用4.5.5版本

    Charles

  • Charles.md

九、brew、yarn、npm

  • [ ] brew

    1
    /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  • [ ] yarn

    1
    brew install yarn
  • [ ] npm

  • [ ] wrap

十、个人娱乐类

十一、网站

1、APP网站及APP安装问题解决方法

详见:常识类/技术常识/好用的网站

十二、其他小工具

详见:实用工具/其他小工具

十三、科学上网

详见:常识类/技术常识/科学上网常识

其他、软件安装问题

1、已损坏

1
sudo xattr -r -d com.apple.quarantine xxx.app

内存

内存

[toc]

必看文章:

什么是内存管理?是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。

内存管理管理的是堆上的内存,栈上的内存并不需要我们管理。

说说内存管理

我们知道,当不再使用一个对象时应该将其释放,但是在某些情况下,我们很难理清一个对象什么时候不再使用(比如self.name不止在一个方法里会被调用到的时候),这可怎么办?ObjC提供autorelease方法来解决这个问题。

说到内存管理,我们就不得不提引用计数。当对象的引用计数为0,对象的内存就会被释放。

引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。使用引用计数技术可以实现自动资源管理的目的。

当我们在创建一个对象的实例并在堆上申请内存时,或者在其他对象中需要持有这个对象时,该对象的引用计数就会加1;
当代码结束使用该对象/释放对象时,则将对象的引用计数减1;而想要释放一个对象的内存,我们将该对象的引用计数减到0,即进行release到0。
在ARC的情况下,为了方便管理内存,它会有一个autorelease的东西,它省去了我们还要自己去执行release方法的操作。同时与autorelease相关的还有一个叫AutoreleasePool自动释放池。

通过autorelease,当给一个对象发送autorelease消息(类方法创建的对象系统会自动添加autorelease)时,对象在接收到autorelease消息后,它会被添加到了当前的自动释放池autoreleasepool中。在未来某个时间,当自动释放池被销毁时,会给池里所有的对象发送release消息将其释放(释放≠销毁)。如果自动释放池向对象发送release消息后对象的引用计数变为了0,则改对象就会被销毁,内存就会被回收。在释放前这个时间段内,对象还是可以使用的(注意1:autorelease不会改变对象的引用计数,release才改变引用计数)(注意2:自动释放池实质上只是在释放的时候给池中所有对象对象发送release消息,不保证对象一定会销毁,如果自动释放池向对象发送release消息后对象的引用计数仍大于1,对象就无法销毁。

1
2
3
id array = [NSMutableArray arrayWithCapacity:1];
此源代码等同于以下源码。
id array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];

所以AutoreleasePool的释放有如下两种情况。

  • 一是Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。(一定要回答到runloop)。那runloop什么时候结束呢?

    一个RunLoop包含若干个Mode,每个Mode包含若干个Source/Timer/Observer/Port。当启动一个RunLoop时会先指定一个Mode,检查指定Mode是否存在以及Mode中是否含有SourceTimer,如果Mode不存在或者Mode中无SourceTimer,认为该Mode是一个空的ModeRunLoop就直接退出。

  • 二是手动调用AutoreleasePool的释放方法(drain方法)来销毁AutoreleasePool

AutoreleasePool(自动释放池)

1、AutoreleasePool(自动释放池)介绍

AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。即当我们创建了一个对象,并把他加入到了自动释放池中时,他不会立即被释放,会等到一次runloop结束或者作用域超出autoreleasepool{}之后再被释放。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。

我们把main.m文件通过Xcode自带的xcrun命令,来编译成main.cpp文件

命令行如下

1
xcrun -sdk iphonesimulator clang -rewrite-objc ./main.m

可以在编译出来的cpp文件中,看到如下自动释放池的结构体如下。

1
2
3
4
5
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} // 构造函数
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} // 析构函数
void * atautoreleasepoolobj;
};

objc4是我们通常说的Runtime源码,我们遇到的libobjc.A.dylib就是用它编译出来的。

源码查看,进入http://opensource.apple.com/source/搜索objc4,

image-20211103173107899

NSThread、NSRunLoop 和 NSAutoreleasePool三者之间的关系

  • NSThread 和 NSRunLoop是一一对应的关系
  • 在NSRunLoop对象的每个运行循环(event loop)开始前,系统会自动创建一个autoreleasepool,并在运行循环(event loop)结束时drain掉这个pool,同时释放所有autorelease对象
  • autoreleasepool只会对应一个线程,每个线程可能会对应多个autoreleasepool,比如autoreleasepool嵌套的情况
  • autorelease本质上就是延迟调用release方法
  • MRC环境,通过调用[obj autorelease]延迟内存的释放
  • ARC环境,甚至可以完全不知道autorelease也能管理好内存

看到这里有人可能会问,那到底延迟到什么时候执行呢?看完本文后,各位心中自然会有答案。

(3)、autorelease、autoreleasepool(自动释放池)
(4)、autoreleasepool(自动释放池)  

  这里说到的自动释放池,顾名思义,就是一个池,这个池可以容纳对象,而且可以自动释放,这就大大增加了我们处理对象的灵活性。   

(5)、autoreleasepool里面对象的内存什么时候释放?

  在runloop sleep的时候当前autoreleasePool drain(objc_autoreleasePoolPop) 掉,向里面的对象都发送release消息,建立一个新的autoreleasePool(objc_autoreleasePoolPush)。或者简单的说就是当@autoreleasepool结束时,里面的内存就会回收;

ARC时代,系统自动管理自己的autoreleasepool,runloop就是iOS中的消息循环机制,当一个runloop结束时系统才会一次性清理掉被autorelease处理过的对象,其实本质上说是在本次runloop迭代结束时清理掉被本次迭代期间被放到autorelease pool中的对象的。至于何时runloop结束并没有固定的duration。

(6)、runloop、autorelease pool以及线程之间的关系

每个线程(包含主线程)都有一个Runloop。对于每一个Runloop,系统会隐式创建一个Autorelease pool,这样所有的release pool会构成一个像callstack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶的Autorelease pool会被销毁,这样这个pool里的每个Object会被release。

##### (7)、自动释放池怎样创建

ObjC提供两种方法创建自动释放池:

方法一:使用NSAutoreleasePool来创建

1
2
3
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
//这里写代码
[pool release];

方法二:使用@autoreleasepool创建

1
2
3
@autoreleasepool {
//这里写代码
}

自动释放池创建后,就会成为活动的池子,释放池子后,池子将释放其所包含的所有对象。

以上两种方法推荐第一种,因为将内存交给ObjC管理更高效。

(8)、自动释放池使用注意

1)自动释放池实质上只是在释放的时候给池中所有对象对象发送release消息,不保证对象一定会销毁,如果自动释放池向对象发送release消息后对象的引用计数仍大于1,对象就无法销毁。

2)自动释放池中的对象会集中同一时间释放,如果操作需要生成的对象较多占用内存空间大,可以使用多个释放池来进行优化。比如在一个循环中需要创建大量的临时变量,可以创建内部的池子来降低内存占用峰值。

3)autorelease不会改变对象的引用计数

(9)、自动释放池的应用/什么时候要用@autoreleasepool

有些情况下,我们还是需要手动创建自动释放池,那么,什么时候呢?

苹果文档中的翻译如下:

  1. 如果你正在编写不基于UI 框架的程序,比如命令行工具。
  2. 如果你编写的循环创建了很多临时对象。那么你可以在循环中使用自动释放池block,在下次迭代前处理这些对象。在循环中使用自动释放池block,有助于减少应用程序的内存占用。
  3. 你生成了一个辅助线程。 一旦线程开始执行你必须自己创建自动释放池。否则,应用将泄漏对象。

按我的理解,最重要的使用场景,应该是有大量中间临时变量产生时,避免内存使用峰值过高,及时释放内存的场景。

如在一个循环事件中,如果循环次数较大或者事件处理占用内存较大,就会导致内存占用不断增长,可能会导致不希望看到的后果。

举个例子,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
}
}

//或
for (int i = 0; i < 100000; i ++) {
@autoreleasepool {
NSString * log = [NSString stringWithFormat:@"%d", i];
NSLog(@"%@", log);
}
}

如果这个for循环里不使用@autoreleasepool,虽然每个循环中生成的字符串对象都会放在自动释放池子中(假设是1号自动释放池),但是这个1号自动释放池是需要等到循环事件结束时释放的。这时候由于循环太大,势必会造成在循环期间内存不增长。所以,这里我们需要使用@autoreleasepool,额外创建一个2号自动释放池,来使得在每个@autoreleasepool结束时,里面的临时变量都会回收,内存使用更加合理。

例子2:假如有2000张图片,每张1M左右,现在需要获取所有图片的尺寸,你会怎么做?
  如果这样做

1
2
3
4
for (int i = 0; i < 2000; i ++) {
CGSize size = [UIImage imageNamed:[NSString stringWithFormat:@"%d.jpg",i]].size;
//add size to array
}

  用imageNamed方法加载图片占用Cache的内存,autoReleasePool也不能释放,对此问题需要另外的解决方法,当然保险的当然是双管齐下了

1
2
3
4
5
6
   for (int i = 0; i < 2000; i ++) {
@autoreleasepool {
CGSize size = [UIImage imageWithContentsOfFile:filePath].size;
//add siez to array
}
}

常见笔试/面试题

< 返回目录

自动释放池底层怎么实现?

答:自动释放池以栈的形式实现:当你创建一个新的自动释放池时,它将被添加到栈顶.当一个对象收到发送autorelease消息时,他被添加到当前线程的处于栈顶的自动释放池中,当自动释放池被回收时,他们从栈中被删除,并且会给池子里面所有的对象都会做一次release操作

内存-②Block

内存-②Block

[toc]

iOS-Block本质

iOS-Block本质

block本质上也是一个OC对象,它内部也有个isa指针,最终继承NSObject。

1
NSGlobalBlock <--__NSGlobalBlock <--NSBlock <-- NSObject

Q:block有哪几种类型 及 各类型的block在内存中如何分配的?

block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型。

:动态分配内存,需要程序员自己申请,程序员自己管理

:自动分配内存,自动销毁,先入后出,栈上的内容存在自动销毁的情况

  • NSGlobalBlock 在数据区

    1
    2
    3
    void (^block1)(void) = ^{
    NSLog(@"block1");
    };
  • NSMallocBlock 在堆区

    1
    2
    3
    4
    int age1 = 1;
    void (^block2)(void) = ^{
    NSLog(@"block2:%d", age1);
    };
  • NSStackBlock 在栈区

    1
    2
    3
    4
    int age2 = 2;
    NSLog(@"%@", [^{
    NSLog(@"block3:%d", age2);
    } class]);

附:打印block类型NSLog(@"%@", [block1 class]);

问:以下各情况,Person的对象什么时候才销毁?或Person对象在block上是如何操作的?

有Person类如下:

1
2
3
4
5
6
7
8
9
#import "Person.h"

@implementation Person

- (void)dealloc {
NSLog(@"Person dealloc");
}

@end

了解解题的基础知识:

1、当对象类型的auto变量被block内部访问时,是强引用还是弱引用?(延伸:引用过后,block内部无法修改auto变量值,除非使用__block)

  • 如果block在空间,不管外部变量是强引用还是弱引用,block都会弱引用访问对象
  • 如果block在空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用

2、GCD API的方法参数block,在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上。

附:在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上的几种情况?

  • 1.block作为函数返回值时

  • 2.将block赋值给__strong指针时

  • 3.block作为Cocoa API中方法名含有usingBlock的方法参数时

  • 4.block作为GCD API的方法参数时

    1
    2
    3
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

    });

1、问: 下列代码中的Person的对象什么时候才销毁?

1
2
3
4
5
6
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
Person *person = [[Person alloc] init];
person.age = 10;

NSLog(@"age:%d", person.age);
}

答:方法代码执行完就person就销毁了

2、问:以下gcd的block中引用 Person的对象什么时候才销毁?

1
2
3
4
5
6
7
8
9
10
11
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
Person *person = [[Person alloc] init];
person.age = 10;

// 不使用__weak修饰
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"age:%d", person.age); // 堆block会对Person强引用,故而也就只有block销毁时候Person才会被释放。这里是2秒后销毁。
});

NSLog(@"touchesBegan");
}

答:

1、gcd的block默认会做copy操作,即dispatch_after的block是堆block。

2、我们知道如果block在堆空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用

所以这里外部的Person没有声明为__weak,所以堆block会对Person强引用,故而也就只有block销毁时候Person才会被释放。这里是2秒后销毁。(如果没有gcd)

3、续问:如果上述Person被添加上__weak修饰,那Person什么时候释放?会造成什么问题?

续答:此时上述的代码会变为

1
2
3
4
5
6
7
8
9
10
11
12
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
Person *person = [[Person alloc] init];
person.age = 10;

// 不使用__weak修饰
__weak Person *weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"person:%p", weakPerson); // 当Person被添加上__weak修饰后,堆block会对Person弱引用。而在touchesBegan函数结束后,由于Person就会被释放,所以2秒后gcd无法捕捉到Person的后果。所以此处person 是 nil,打印的%p地址,是0x0。
});

NSLog(@"touchesBegan");
}

同样,还是我们知道如果block在堆空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用。所以当Person被添加上__weak修饰后,堆block会对Person弱引用。而在touchesBegan函数结束后,由于Person就会被释放,所以2秒后gcd无法捕捉到Person的后果。即上述的输出结果是

1
person 是 nil,打印的%p地址,是0x0。

4、上述代码使用__weak后,变量被释放了,那怎么防止block持有的对象提前释放

Q:block的属性修饰词为什么是copy?

block一旦没有进行copy操作,就不会在堆上
block在堆上,程序员就可以对block做内存管理等操作,可以控制block的生命周期

Q:当block被copy到堆时,对__block修饰的变量做了什么?

  • 会调用block内部的copy函数
  • copy函数内部会调用_Block_object_assign函数
  • _Block_object_assign函数会对__block变量形成强引用(retain)
  • 对于__block 修饰的变量 assign函数对其强引用;对于外部对象 assign函数根据外部如何引用而引用

Q:当block从堆中移除时,对__block修饰的变量做了什么?

  • 会调用block内部的dispose函数
  • dispose函数内部会调用_Block_object_dispose函数
  • _Block_object_dispose函数会自动释放引用的__block变量(release)

二、__block修饰符

Q:__block int age = 10,系统做了哪些?

Q:__block 修饰符作用?