《iOS 开发进阶(唐巧)》读书笔记

iOS开发进阶完整版 唐巧 这个文档适合那些已经从事了IOS开发的想有更上一步的朋友,里边的内容很全面。 立即下载

1. CocoaPods 的安装和使用

CocoaPods 是开发 iOS 应用程序的一个第三方库的依赖管理工具,起始于2011年8月,用 Ruby 写的。

1.0 CocoaPods 的原理

CocoaPods 的原理是将所有的依赖库都放到另一个名为 Pods 的项目中,然后让主项目依赖 Pods 项目。下面是一些技术细节:

  • Pods 项目最终会编译成一个名为 libPods.a 的文件,主项目只需要依赖这个 .a 文件即可
  • 对于资源文件,CocoaPods 提供了一个名为 Pods-resources.sh 的 bash 脚本,该脚本每次项目编译时都会执行,将第三方库的各种资源文件复制到目标目录中
  • CocoaPods 通过一个名为 Pods.xcconfig 的文件在编译时设置所有的依赖和参数

1.1 CocoaPods 的安装

Mac 自带 ruby,首先升级 ruby 的 gem 命令

$ sudo gem update --system

ruby 的软件源 rubygems.org 因为使用亚马逊的云服务,所以被墙了,需要更新一下 ruby 的源,下面代码将官方的 ruby 源替换成国内淘宝的源:

$ gem sources --remove https://rubygems.org/ 
$ gem sources -a https://ruby.taobao.org/

验证 ruby 的源是否为淘宝镜像

$ gem sources -l

使用 ruby 的 gem 命令即可下载安装:

$ sudo gem install cocoapods
$ pod setup

执行 pod setup 时,会输出 Setting up CocoaPods master repo 并等待好久,此时是 CocoaPods 将它的信息下载到 ~/.cocoapods 目录下。可以 cd 到那个目录,用 du -sh 来查看下载进度。

如果没有进度,那还需更换 ruby 镜像

$ gem sources --remove https://ruby.taobao.org/ 
$ gem sources -a https://gems.ruby-china.org/

更换成功后再次下载安装

$ sudo gem install cocoapods
$ pod setup

1.2 CocoaPods 的使用

在项目文件夹中创建名为 Podfile 的文件

$ touch Podfile

在文件中添加依赖库名称

platform :ios, ‘8.0’

target ‘jiaheyingyuan’ do
pod ‘AFNetworking’
pod ‘SDWebImage’
end

如果不确定三方库版本,查找第三方库

$ pod search AFNetworking

然后在项目文件夹中执行

$ pod install

注意事项

  • 每次更改了 Podfile 文件,都需要重新执行一次 pod update 命令
  • 执行 pod install 之后,会生成一个名为 Podfile.lock 的文件,不能把这个文件加入到 gitignore 中,因为 Podfile.lock 会锁定当前各依赖库的版本,之后执行 pod install 也不会更改版本,只有执行 pod update 才会改变 Podfile.lock。这样可以防止第三方库升级时造成大家各自的第三方库版本不一致。

1.3 其他

为自己的项目创建 podspec 文件参考这两篇文章

不更新 podspec

在执行 pod install 和 pod update 时,默认先更新一次 podspec 索引,如下代码可以禁止其做索引更新操作

pod install --no-repo-update
pod update  --no-repo-update

2. 网络封包分析工具 Charles

2.1 功能简介

  • 支持 SSL 代理
  • 支持流量控制,可以模拟慢速网络、等待时间较长的请求
  • 支持 AJAX 调试,自动将 JSON 或 XML 数据格式化,方便查看
  • 支持 AMF 调试,将 Flash Remoting 或 Flex Remoting 信息格式化,方便查看
  • 支持重发网络请求,方便后端调试
  • 支持修改网络请求参数
  • 支持网络请求的截获和动态修改
  • 检查 H5 内容是否符合 W3C 标准

2.2 Charles 的安装和使用

安装 Charles

在 Charles 官方网站(https://www.charlesproxy.com)下载安装。

安装 SSL 证书

如果需要截取分析 SSL 协议相关内容,那么需要安装 Charles 的 CA 证书(http://www.charlesproxy.com/ssl.zip)。解压后双击 .crt 文件,在钥匙串 -> 系统 -> 证书中可查看

截取 Mac 上的网络封包

Charles 是通过将自己设置成代理服务器来完成封包截取的,将 Charles 设置成系统代理,菜单中 Proxy -> Mac OS X Proxy 将 Charles 设置成系统代理。之后浏览网页就可以看到网络请求出现在 Charles 界面中。

过滤网络请求
  • 在主界面的中部的 Filter 栏中填入需要过滤出来的关键字,比如 baidu。这种方法是临时性的封包过滤。

  • Proxy -> Recording Settings -> Include,这种方法是经常性的封包过滤。

截取 iPhone 上的网络封包

Proxy -> Proxy Settings 填入代理端口 8888,并勾选 Enable transparent HTTP proxying 然后在手机端设置代理即可

模拟慢速网络

Proxy -> Throttle Setting 勾选 Enable Throttling 并且可以设置 Throttle Preset 的类型。如果只想模拟指定网站的慢速网络,可以勾选 Only for selected hosts 然后在对话框的下半部分设置中增加指定的 Hosts 项即可。

截取 SSL 信息

在该请求上单机右键,选择 SSL Proxying 然后对于该 Host 的所有 SSL 请求都可以被截取到了

修改网络请求内容

调试接口时,我们需要反复尝试不同参数的网络请求,在请求上点击右键,选择 Edit 即可创建一个可编辑的网络请求,可修改 URL 地址、端口、参数等,修改完成后单击 Execute 即可发送

修改服务器返回内容

Charles 提供了 Map、Rewrite、Breakpoints 功能,都可以达到修改服务器返回内容的目的。

  • Map 适合长期将某一些请求重定向到另一个网络地址或本地文件
  • Rewrite 适合对网络请求进行一些正则替换
  • Breakpoints 适合做一些临时性的修改
Map 功能

Map 功能分为两种,进入方式 Tools -> Map Remote 或 Map Local

  • Map Remote:将指定的网络请求重定向到另一个网址
  • Map Local:将指定的网络请求重定向到本地文件

对于 Map Remote 功能,我们需要分别填写网络重定向的源地址和目的地址,对于不需要限制的条件,可以留空。举例:Map From 测试服务器的请求重定向到 Map To 线上服务器。

对于 Map Local 功能,我们需要填写重定向的源地址和本地的目标文件。对于一些复杂的网络请求结果,我们可以先使用右键单击 Save Response 功能,将请求结果保存到本地,然后稍加修改,使其成为我们的目标映射文件。

Rewrite 功能

Rewrite 功能适合对某一类网络请求进行一些正则替换,以达到修改结果的目的。例如,一个 API 请求时获得用户昵称,将 Rewrite Rule 界面的 Match 的 value 设置之前的昵称,Replace 的 value 设置将要修改的昵称

Breakpoints 功能

Rewrite 功能适合做批量和长期的替换,临时性的修改最好使用 Breakpoints 功能。Breakpoints 功能类似 Xcode 中设置的断点,当网络请求发生时,Charles 会截取该请求,这个时候,我们可以在 Charles 中临时修改网络请求的返回内容。修改完成后单击 Execute 即可让网络请求继续进行。需要注意的是,使用 Breakpoints 功能将网络请求截取并修改的过程中,整个网络请求的计时并不会暂停,所以长时间的暂停可能导致客户端的请求超时。

3. 其他一些实用的工具

3.1 界面调试工具 Reveal

Reveal 可以在 iOS 开发时动态的查看和修改应用程序的界面。iOS 逆向工程中使用强大,可以分析他人的 APP,缺点是真特么贵。

3.2 移动统计工具 Flurry

Flurry 是一家专门为移动应用提供数据统计和分析的公司。Flurry 优点:保持独立和专注,数据安全性更高。友盟已经被阿里收购,当用户的应用涉及的业务和阿里有类似或重合的时候,该统计数据有潜在的安全性问题

3.3 崩溃日志记录工具 Crashlytics

Crashlytics 是专门为移动应用开发者提供的保存和分析应用崩溃信息的专业工具。

3.4 App Store 统计工具 App Annie

App Annie 是一个 App Store 数据的统计分析工具。该工具可以统计 App 在 App Store 的下载量、排名变化、销售收入情况、用户评价等信息。

苹果官方的 iTunes Connect 提供的销售数据统计功能比较差,例如只能保存最近30天的详细销售数据、界面丑陋、无法查看应用的排名历史变化情况等

3.5 Xcode 插件

  • Alcatraz:管理 Xcode 插件、模板和颜色配置
  • KSImageNamed:自动弹出图片预览
  • BBUDebuggerTuckAway:智能弹出隐藏调试窗口
  • VVDocumenter:自动生成代码注释
  • ClangFormat:自动调整代码风格
  • ColorSense:编写 UIColor 时,实时预览相应的颜色

3.6 Dash

Dash 是一款 API 文档查询及代码片段管理工具,超级好用

3.7 蒲公英

蒲公英是一个应用的内测分发工具,类似苹果的 TestFlight。把 App 安装包上传到蒲公英,生成一个二维码,用户扫码就可以安装应用。类似的还有 FIR 提供同样的服务。

4. 理解内存管理

4.1 我们为什么需要引用计数

假如对象 A 将其中的对象 M 作为参数传递给对象 B,没有引用计数的情况下,内存管理原则是“谁申请谁释放”。那么要在 B 不再需要 M 的时候,A 将 M 销毁。但 B 可能只是临时用一下 M,也可能觉得 M 很重要,将它设置成自己的一个成员变量。这种情况下,什么时候销毁 M 就成了一个难题。

有一个暴力的做法,就是 A 调用完 B 之后,马上就销毁参数 M,然后 B 将参数另外复制一份 M2,自己管理 M2 的生命期。这种做法有一个很大的问题,就是它带来了更多的内存申请、复制、释放的工作,实在太影响性能。

还有另外一种做法,A 在构造完 M 之后,始终不销毁 M,由 B 来完成 M 的销毁工作。如果 B 需要长时间使用 M,就不销毁它,如果只是临时用一下,则可以用完马上销毁。这样好像很好的解决了对象复制的问题,但是它强烈依赖于 A、B 两个对象的配合。而且 M 申请在 A 中,释放在 B 中,使得它的内存管理代码分散在不同对象中,管理起来非常费劲。再复杂点,B 需要再向 C 传递 M,那么 M 在 C 中又不能让 C 管理,所以这种方式带来的复杂性更大。

所以引用计数很好的解决了这个问题,哪些对象需要长时间使用,就把它的引用计数加1,使用完了再把引用计数减1,对象的生命期管理可以完全交给引用计数了。

4.2 不要向已经释放的对象发送消息

NSObject *obj = [[NSObject alloc] init];
NSLog(@"Reference Count = %u", [obj retainCount]);
[obj release];
NSLog(@"Reference Count = %u", [obj retainCount]);

输出结果可能是这样的

Reference Count = 1
Reference Count = 1

最后一次输出,引用计数为什么没有变成 0 呢?因为该对象的内存已经被回收,而我们向一个已经被回收的对象发了一个 retainCount 消息,所以它的输出结果应该是不确定的,如果该对象所占的内存被复用了,那么就有可能造成程序异常崩溃。

那为什么在这个对象被回收之后,这个不确定的值是 1 而不是 0 呢?因为当最后一次执行 release 时,系统知道马上就要回收内存了,就没有必要将 retainCount 减 1 了,因为不管减不减 1,该对象都肯定被回收,而对象被回收后,它所有的内存区域,包括 retainCount 值也变得没有意义。这样减少一次内存的操作,加速对象的回收。

4.3 弱引用

弱引用持有对象,但是不增加引用计数,这样就避免了循环引用的产生

4.4 使用 Leaks 检测循环引用

- (void)viewDidLoad {
    [super viewDidLoad];


    NSMutableArray *firstArray = [NSMutableArray array];
    NSMutableArray *secondArray = [NSMutableArray array];

    [firstArray addObject:secondArray];
    [secondArray addObject:firstArray];
}

我们可以切换打印模块上的 Leaks 切换为 Cycles & Roots 可能看到以图形方式显示出来的循环引用

4.5 Core Foundation 对象的内存管理

CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, "hello world", kCFStringEncodingUTF8);

CFRetain(str);  // 引用计数 +1
CFRelease(str);  // 引用计数 -1

CFRetain 和 CFRelease 方法与 Objective-C 对象的 retain 和 release 方法等价。

将 Core Foundation 对象转换成一个 Objective-C 对象,引入了 bridge 相关关键字

  • __bridge:只做类型转换,不修改相关对象的引用计数,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法
  • __bridge_retained:类型转换后,将相关对象的引用计数加 1,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法
  • __bridge_transfer:类型转换后,将该对象的引用计数交给 ARC 管理,Core Foundation 对象在不用时,不再需要调用 CFRelease 方法

5. 动态下载系统提供的多种中文字体

字体文件通常比较大,10 ~ 20 MB 是常见的字体库的大小,并且中文字体通常都是有版权的,所以使用特殊中文字体库的 iOS 应用较少,通常只有阅读类的应用才会使用特殊中文字体库。从 iOS 6 开始,苹果支持动态下载中文字体到系统中,使用系统提供的中文字体,既可以避免版权问题,又可以减少应用体积。

首先需要使用 Mac 内自带的应用“字体册”(Font Book)来获得相应字体的 PostScript 名称。

假如我们现在要下载“娃娃体”,它的 PostScript 名称为“DFWaWaSC-W5”,首先判断该字体是否已经被下载下来

- (BOOL)isfontDownloaded:(NSString *)fontName
{
    UIFont *aFont = [UIFont fontWithName:fontName size:12.0];
    if (aFont && ([aFont.fontName compare:fontName] == NSOrderedSame || [aFont.familyName compare:fontName] == NSOrderedSame)) {
        return YES;
    } else {
        return NO;
    }
}

如果该字体没有下载过,我们需要准备下载字体 API 需要的一些参数

NSMutableDictionary *attrs = [NSMutableDictionary dictionaryWithObjectsAndKeys:fontName, kCTFontNameAttribute, nil];

// 创建一个字体描述对象
CTFontDescriptorRef desc = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)attrs);

NSMutableArray *descs = [NSMutableArray arrayWithObject:(id)descs];
CFRelease(desc);

准备好上面的 descs 变量后,就可以进行字体的下载了

__block BOOL errorDuringDownload = NO;
CTFontDescriptorMatchFontDescriptorsWithProgressHandler((__bridge CFArrayRef)descs, NULL, ^bool(CTFontDescriptorMatchingState state, CFDictionaryRef  _Nonnull progressParameter) {

    double progressValue = [[(__bridge NSDictionary *)progressParameter objectForKey:(id)kCTFontDescriptorMatchingPercentage] doubleValue];

    if (state == kCTFontDescriptorMatchingDidBegin) {
        NSLog(@"字体已经匹配");
    } else if (state == kCTFontDescriptorMatchingDidFinish) {
        if (!errorDuringDownload) {
            NSLog(@"字体 %@ 下载完成", fontName);
        }
    } else if (state == kCTFontDescriptorMatchingWillBeginDownloading) {
       NSLog(@"字体开始下载");
    } else if (state == kCTFontDescriptorMatchingDidFinishDownloading) {
        NSLog(@"字体下载完成");
    } else if (state == kCTFontDescriptorMatchingDownloading) {
        NSLog(@"下载进度 %.0f%%", progressValue);
    } else if (state == kCTFontDescriptorMatchingDidFailWithError) {
        NSError *error = [(__bridge NSDictionary *)progressParameter objectForKey:(id)kCTFontDescriptorMatchingError];
        errorDuringDownload = YES;
    }
    return YES;
});

通常需要在下载完字体后开始使用字体,一般是将响应代码放到 kCTFontDescriptorMatchingDidFinish 条件中,用 GCD 修改 UI 或者发 Notification 来通知相应的 Controller。

6 安全性问题

  • 网络安全:json 字段加密,使其不能直观的猜出内容

  • js 文件安全:将 js 源码进行混淆和加密,防止黑客轻易的阅读和篡改相关的逻辑,也可以防止自己的 Web 端和 Native 端通讯协议泄漏

  • 本地数据安全:对于本地的重要数据,我们应该加密存储或者将其保存到 keychain 中,以保证其不被篡改

  • 源代码安全:对于 IDA 这类工具,我们的应对措施就比较少了,除了用一些宏来简单混淆类名外,我们也可以将关键的逻辑用存 C 实现,不但保证安全性,还可以在 iOS 和 Android 使用同一套底层通讯代码,达到复用的目的。

7 基于 CoreText 的排版引擎

使用 CoreText 技术,我们可以对富文本进行复杂的排版。经过一些简单的扩展,我们还可以实现对于图片、链接的点击效果。CoreText 技术相对于 UIWebView 有内存占用少,可以后台渲染的优点,非常适合排版工作。

CoreText 和 UIWebView 比较

CoreText和UIWebView

优点
  • CoreText 占用的内存少,渲染速度更快;
  • CoreText 在渲染界面前就可以精确的获得显示内容的高度(只要有了 CTFrame 即可),而 UIWebView 只有渲染出内容后,才能获得内容的高度(而且还需要用 JavaScript 代码来获取);
  • CoreText 的 CTFrame 可以在后台线程渲染,UIWebView 的内容只能在主线程渲染;
  • 基于 CoreText 可以做更好的原生交互效果,而 UIWebView 的交互效果都是用 JavaScript 来实现的,在交互效果上会有一些卡顿情况存在。

缺点

  • CoreText 渲染出来的内容不能像 UIWebView 那样方便的支持内容的复制;
  • 基于 CoreText 来排版需要自己处理很多复杂的逻辑,例如需要自己处理图片和文字混排相关的逻辑,也需要自己实现链接点击操作的支持。

点击这里查看更多

8. 实战技巧

8.1 申请加急审核

  1. 访问 iTunes Connect 网站:https://itunesconnect.apple.com
  2. 单击网站底部的 Contact Us 按钮
  3. 在问题 1 中选择 App Review
  4. 在问题 2 中选择 Request Expedited Review
  5. 单击 Request an Expedited App Review 按钮即可填写加急审核的申请表。最好使用英文填写,最容易通过的理由是严重的崩溃 bug(在理由一栏选择 Critical Bug Fix),在原因处详细描述该 bug 的重现步骤,则很容易使申请通过。

8.2 如何将应用下架

最简单的办法是将应用的上架时间改成未来的一个时间,这样就会在数小时之内下架

8.3 NSJSONSerialization 比 NSKeyedArchiver 更好

在选择持久化方案时,系统提供的 NSJSONSerialization 比 NSKeyedArchiver 在效率和体积上都更优。经过测试,NSJSONSerialization 比 NSKeyedArchiver 快了 7 倍,而且序列化之后的体积是 NSKeyedArchiver 的一半。

NSLog(@"NSJSONSerialization 开始存储");

NSMutableArray *persons1 = [NSMutableArray array];
for (int i = 0; i < 100000; i++) {

    [persons1 addObject:[NSJSONSerialization dataWithJSONObject:dic options:0 error:nil]];
}

NSLog(@"NSJSONSerialization 结束存储");
NSLog(@"NSKeyedArchiver 开始存储");


NSMutableArray *persons2 = [NSMutableArray array];
for (int i = 0; i < 100000; i++) {

    [persons2 addObject:[NSKeyedArchiver archivedDataWithRootObject:dic]];
}


NSLog(@"NSKeyedArchiver 结束存储");

测试结果为:NSJSONSerialization 用了 0.426 秒,NSKeyedArchiver 用了 4.344 秒

8.4 设置应用内的系统控件语言

在 iOS 系统中,有时候会需要调用系统的一些 UI 控件,例如:

  • 在 UIWebView 中长按会弹出系统的上下文菜单;
  • 在 UIImagePickerController 中会使用系统的照相机界面;
  • 在编辑状态下的 UITableViewCell 处于待删除状态时,会有一个系统的删除按钮;

以上 UI 控件中,显示的语言并不是和你当前手机的系统语言一致的,语言设置成中文,需要在 info.plist 文件中增加以下内容即可

<key>CFBundleLocalizations</key>
<array>
    <string>zh_CN</string>
    <string>en</string>
</array>

8.5 用截屏功能来实现侧滑返回效果

iOS 7 以后的系统,可以通过系统提供的 API 来实现截屏功能

- (nullable UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates;

早期的 QQ 在侧滑到一半的时候,整个当前界面被移动到了右半部分,同时在左半部分以半透明的方式露出了上一个界面。由于 ViewController 并不支持自己的 view 设置透明,所以需要我们自己实现。

为了使用截屏功能达到这种效果,我们在 NavigationController 进入到一个新的 ViewController 前,先进行截屏操作,保存当前的界面效果,然后将截到的当前界面作为参数,传递给目标 ViewController 当作背景。

这样,平时这个背景我们用内容遮挡住,当用户用手指向右滑动,我们将整个界面右移,露出这个背景,于是就会像看到了上一个 ViewController 一样。

8.6 内存警告

  1. CALayer 是一个 bitmap 图像的容器类,当 UIView 调用自身的 drawRect 时,CALayer 才会创建这个 bitmap 图像类。
  2. CALayer 只占 48 Bytes,UIView 只占 96 Bytes,而一个 iPad 的全屏 UIView 的 bitmap 类会占到 12 MB 的大小!
  3. 当系统发出 MemoryWarning 时,系统会自动回收 bitmap 类,但是不回收 UIView 和 CALayer 类。这样既能回收大部分内存,又能在需要 bitmap 类时,通过调用 UIView 的 drawRect 方法重建。
  4. 当一段内存被分配时,它会被标记成 In use 以防止被重复使用,当内存被释放时,这段内存会被标记成 Not in use,这样在有新的内存申请时,这块内存就可能被分配给其他变量。
  5. CALayer 包括的具体的 bitmap 内容的私有成员变量类型为 CABackingStore,当收到 MemoryWarning 时,CABackingStore 类型的内存区会被标记成可能再次被原变量使用。

8.7 Xcode 快捷键

常用快捷键
快捷键说明
Ctrl + 6列出当前文件中所有方法,快速定位方法位置
Cmd + Ctrl + Up在 .h 和 .m 文件之间切换
Cmd + Ctrl + Left到上 / 下一次编辑的位置
Cmd + Shift + F在工程中查找
Cmd + .结束本次调试
不太常用快捷键
快捷键说明
Cmd + Shift + Y切换控制台的显示或隐藏
Cmd + 0隐藏左边的导航区
Cmd + Opt + 0隐藏右边的工具区
Cmd + Shift + K清空编译好的文件

9. Objective-C 对象

9.1 isa 指针

  • 每一个对象都是一个类的实例,每一个对象都有一个名为 isa 的指针,指向该对象的类;
  • 每一个类描述了它的实例的特点,包括成员变量的列表、成员函数的列表等;
  • 每一个对象都可以接收消息,消息列表保存在它所对应的类中。每一个类也可以接收消息,例如 [NSObject alloc];
  • 每一个类也是一个对象,每一个类也有一个 isa 的指针,所以它必须是另一个类的实例,这个类就是元类。元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有,该元类会向它的父类查找该方法;
  • 元类也是一个对象,为了设计上的完整,所有的元类的 isa 指针都会指向一个根元类,根元类本身的 isa 指针指向自己;

9.2 动态创建类和对象

- (void)viewDidLoad {
    [super viewDidLoad];


    // 创建一个名为 MYView 的类,它是 UIView 的子类
    Class newClass = objc_allocateClassPair([UIView class], "MYView", 0);

    // 为该类增加一个名为 report 的方法
    class_addMethod(newClass, NSSelectorFromString(@"report"), (IMP)ReportFunction, "v@:");

    // 注册该类
    objc_registerClassPair(newClass);


    // 创建一个 MYView 类的实例
    id instanceOfNewClass = [[newClass alloc] init];
    // 调用 report 方法
    [instanceOfNewClass performSelector:NSSelectorFromString(@"report")];
}

void ReportFunction(id self, SEL _cmd)
{
    NSLog(@"Class is %@, and super is %@", [self class], [self superclass]);
}

9.3 动态方法替换

  • class_replaceMethod,当需要替换的方法有可能不存在时使用;
  • method_exchangeIpmlementations,当需要交换两个方法的实现时使用;
  • method_setImplementation,仅仅需要为一个方法设置其实现方式时使用。

10. Tagged Pointer 对象

iPhone 5s 配备了首个采用 64 位架构的 A7 双核处理器,同时提出了 Tagged Pointer 的概念。对于 64 位系统,引入 Tagged Pointer 后,相关逻辑能减少一半的内存占用,3 倍的访问速度提升,100 倍的创建、销毁速度提升。

10.1 原有系统的问题

举个例子:

  • NSNumber 对象,在 32 位 CPU 下占 4 个字节,在 64 位 CPU 下占 8 个字节;
  • 指针在 32 位 CPU 下占 4 个字节,在 64 位 CPU 下占 8 个字节;

所以如果没有 Tagged Pointer 对象,从 32 位机器迁移到 64 位机器中,虽然逻辑没有变化,但是对象所占用的内存会翻倍:

32 位

10.2 Tagged Pointer 介绍

为了改进上面提到的内存占用和效率问题,苹果提出了 Tagged Pointer 对象。将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址,64 位 CPU 下 NSNumber 的内存图变成下面这样:

64 位

10.3 Tagged Pointer 特点

  1. Tagged Pointer 专门用来存储小的对象,例如 NSNumber 和 NSDate;
  2. Tagged Pointer 指针的值不再是地址,而是真正的值。所以实际上它不再是一个对象了,只是一个披着对象“皮”的普通变量而已,所以,它的内存并不存储在堆中;
  3. 在内存读取上有着以前 3 倍的效率,创建时比以前快 106 倍。

10.4 引用计数相关变化

在 32 位环境下,对象的引用计数都保存在一个外部的表中,每一个对象的 Retain 操作,实际包括如下 5 个步骤:

  1. 获得全局的记录引用计数的 hash 表;
  2. 为了线程安全,给该 hash 表加锁;
  3. 查找到目标对象的引用计数值;
  4. 将该引用计数值加 1,写回 hash 表;
  5. 给该 hash 表解锁。

而在 64 位环境下,isa 指针也是 64 位,实际作为指针部分只用到其中 33 位,剩余的 31 位苹果使用了类似 Tagged Pointer 的概念,其中 19 位将保存对象的引用计数,这样对引用计数的操作只需要修改这个指针即可。只有当引用计数超出 19 位,才会将引用计数保存到外部表,但是这种情况是很少的。在 64 位环境下,新的 Retain 操作包括如下 5 个步骤:

  1. 检查 isa 指针上面的标记位,看引用计数是否保存在 isa 变量中,如果不是,则使用以前的步骤,否则执行第 2 步;
  2. 检查当前对象是否正在释放,如果是,不做任何事情;
  3. 增加该对象的引用计数,但是并不马上写回到 isa 变量中;
  4. 检查增加后的引用计数的值是否能够被 19 位表示,如果不是,则切换成以前的办法,否则执行第 5 步;
  5. 进行一个原子的写操作,将 isa 的值写回。

虽然步骤都是 5 步,但是由于没有了全局的加锁操作,所以引用计数的更改更快了。

10.5 isa 的 bit 位含义

bit 位变量名意义
1 bitindexed0 表示普通的 isa,1 表示 Tagged Pointer
1 bithas_assoc表示该对象是否有过 associated 对象,如果没有,在析构释放内存时可以更快
1 bithas_cxx_dtor表示该对象是否有 C++ 或 ARC 的析构函数,如果没有,在析构释放内存时可以更快
30 bitsshiftcls类的指针
9 bitsmagic其值固定为 0xd2,用于在调试时分辨对象是否未完成初始化
1 bitweakly_referenced表示该对象是否有过 weak 对象,如果没有,在析构释放内存时可以更快
1 bitdeallocating表示该对象是否正在析构
1 bithas_sidetable_rc表示该对象的引用计数值是否大到无法直接在 isa 中保存
19 bitsextra_rc表示该对象超过 1 的引用计数值,例如,如果该对象的引用计数是 6,则 extra_rc 的值为 5

11. block 对象

11.1 block 内部数据结构定义

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

struct Block_layout {
    void *isa;
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
};

一个 block 实例由 6 部分构成:

  1. isa 指针
  2. flags,用于按 bit 位表示一些 block 的附加信息
  3. reserved,保留变量
  4. invoke,函数指针,指向具体的 block 实现的函数调用地址
  5. descriptor,表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针
  6. variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。

11.2 block 三种类型

  • _NSConcreteGlobalBlock,全局的静态 block,不会访问任何外部变量;
  • _NSConcreteStackBlock,保存在栈中的 block,当函数返回时会被销毁;
  • _NSConcreteMallocBlock,保存在堆中的 block,当引用计数为 0 时会被销毁。
NSGlobalBlock
void(^myBlock)() = ^{
    NSLog(@"hello world");
};

NSLog(@"%@", myBlock);  // <__NSGlobalBlock__: 0x10d65b240>

对 NSGlobalBlock 的 retain、copy、release 操作都无效。

NSStackBlock 和 NSMallocBlock

在 MRC 模式下打印:

int num = 10;
void(^myBlock)() = ^{
    NSLog(@"malloc block and num = %d", num);
};

NSLog(@"%@", myBlock);  // <__NSStackBlock__: 0x7fff574d5a08>

block 在函数退出的时候,就会被回收,如果再调用该 block 会导致 crash

在 ARC 模式下打印:

int num = 10;
void(^myBlock)() = ^{
    NSLog(@"malloc block and num = %d", num);
};

NSLog(@"%@", myBlock);  // <__NSMallocBlock__: 0x600000048220>

这里为什么打印的是 NSMallocBlock 呢?在 ARC 模式下生成的 block 也是 NSStackBlock,只是当赋值给 strong 对象时,系统会主动对其进行 copy,将栈上复制到堆上。如果不赋值,直接打印,则为 NSStackBlock

int num = 10;

NSLog(@"%@", ^{
    NSLog(@"hello world and num = %d", num);
});  // <__NSStackBlock__: 0x7fff574d5a08>

11.3 循环引用

  1. 在 MRC 时代,__block 修饰避免循环引用;ARC 时代,__block 修饰同样会引起循环引用;
  2. 不用 __block 修饰,block 外的变量引用,是复制其本事;使用 __block 修饰,block 外的变量引用,是复制其引用地址;
  3. __block 不管是 ARC 还是 MRC 模式下都可以修饰对象和基本数据类型;__weak 只能在 ARC 模式下使用,也只能修饰对象,不能修饰基本数据类型;
  4. __block 对象可以在 block 中被重新赋值,__weak 不可以。

后记

前前后后大概半个月,利用零碎时间看完了这本书。这本书前年就有所耳闻,网上的评价也褒贬不一,唐巧写的相对来说,语言通俗易懂,感觉像是博客一样,没有什么限制,读起来很畅快。这本书适合初学者,虽然这本书大部分内容之前都已经熟悉掌握,但是看过一遍还是很有收获的,有些知识点的总结很到位,感觉没有浪费时间。遗留的问题是 CoreText 部分内容打算结合其他资料系统的研究一下,这里就先不仔细阅读了,更多内容请关注我的 GitHub:https://github.com/Mayan29/ReadingNotes

iOS开发进阶-完整版 唐巧的《iOS开发进阶》,希望对一些初级的程序员有用 立即下载

相关推荐

iOS开发进阶(唐巧)

本书分工具、实践、理论三大部分。第一部分介绍ios开发的常用工具,第二部分介绍ios开发中的一些常见的实践经验,第三部分介绍ios开发中涉及的原理

Podspec语法

前言 ​ 长时间不写Podspec文件,容易忘记里面的一些属性含义,所以本文把 官方Podspec语法v1.9.0 翻译出来并且加上了一部分自己的理解,方便后续在用到的时候可以直接拿过来看。 CocoaPods版本升级后会保持对本文进行更新。 一、Root specification相关 root规范存储了相关库特定版本的信息。 下面的属性只能写在root规范上,而不能写在“sub-spec”上。 1、必须的字段 . name spec.name = 'AFNetworking' pod search 搜

eeybee的专栏 1627

iOS开发进阶

作者唐巧,本书定位于帮助那些iOS开发人员提高自己的开发水平

pod引用第三方库的几种方式

pod引用库的原理,本质上是去找.podspec文件,podspec中包含库的地址及最新的版本号(tag标签),如果pod时没有指定版本,则pod install时会去下载podspec文件中指定的最新版本,如果pod时指定了版本规则,则pod install会按照此版本规则去下载指定的版本 例如想引用YBUtils这个库,有三种方法: 1. 直接从官方的pod r...

weixin_30699443的博客 1236

500道C++面试高频题解析(附答案、实战示例与进阶追问)

本文提供500道C++面试高频题的详细解析,涵盖基础概念到进阶应用,包括面向对象编程、内存管理、现代C++特性、并发编程等核心考点。每道题配有实战示例和进阶追问,帮助开发者突破八股文局限,掌握解决实际问题的能力,适合准备C++技术面试的工程师系统复习。

weixin_29002595的博客 310

PCIe信号眼图总是不开?手把手教你调CTLE和DFE参数(附实测波形对比)

本文详细解析了PCIe信号眼图调试中CTLE和DFE参数的优化方法,帮助硬件工程师解决眼图无法张开的问题。通过实际案例和步骤指导,介绍了如何调整CTLE直流增益和DFE抽头系数,提升接收端均衡器性能,确保信号完整性。

weixin_26805597的博客 214

唐巧的技术博客

ios http://devtang.com/blog/categories/ios/

qq_19697705的专栏 2529

Cadence OrCAD: 层次化设计 Hierarchical Design

详细介绍OrCAD的原理图层次化设计方法。

Fantasy237的博客 1万+

唐巧访谈: iOS大V的技术进阶之路

如果你有志于将iOS开发作为职业,或者已经是一位iOS开发者,那么你应该听说过唐巧的名字。唐巧,2012年从网易有道离开参与创业, 目前是猿题库iOS高级研发工程师。难得的是,他本身是一线的iOS开发者,同时又对iOS开发在国内的布道做了不少的工作,身影活跃于博客、微博、微信公众账号等地。从2011年以来,唐巧开始在博客分享自己写的iOS开发技术文章,CocoaChina 也曾多次转载过他的博客文

lx31433的专栏 1351

从零到一:基于JasperGold的FPV实战入门与避坑指南

本文详细介绍了基于JasperGold的形式化验证(FPV)实战入门指南,涵盖环境搭建、SVA断言编写、TCL脚本调试等关键步骤。通过实际案例解析,帮助IC设计工程师掌握FPV技术,有效验证RTL设计中的复杂逻辑和corner case,提升验证效率与质量。

weixin_30899023的博客 360

YOLOv11与3D点云检测融合技术深度解析:从2D检测到三维空间投影的完整实现

2D-3D融合目标检测技术研究 本文系统阐述了基于YOLOv11的2D-3D目标检测融合技术,重点分析了YOLOv11在融合系统中的定位优势及其架构设计。研究表明,2D图像与3D点云数据具有显著的互补特性:2D图像提供丰富语义信息,而3D点云则精确描述几何结构。 关键技术要点: YOLOv11通过GSConv平衡卷积和VoVGSCS特征融合模块,在保持实时性的同时提升了检测精度 系统采用模块化设计,包含坐标投影、点云分割、边界框拟合等核心组件 针对点云数据的非均匀分布特性,提出了多尺度特征增强和距离差异化检

本博客聚焦 YOLOv11 全流程落地,涵盖架构优化、数据集处理、训练技巧与多场景部署,兼及国产数据库、Java 开发与 AI 模型应用,内容兼顾理论与工程实践,为开发者提供系统干货,助力高效提升技术能力。 1126

CocoaPods 简介 转自唐巧的博客

每种语言发展到一个阶段,就会出现相应的依赖管理工具,例如 Java 语言的 Maven,nodejs 的 npm。随着 iOS 开发者的增多,业界也出现了为 iOS 程序提供依赖管理的工具,它的名字叫做:CocoaPods。 CocoaPods项目的源码 在 Github 上管理。该项目开始于 2011 年 8 月 12 日,经过多年发展,现在已经成为 iOS 开发事实上的依赖管理标准工具。开发 iOS 项目不可避免地要使用第三方开源库,CocoaPods 的出现使得我们可以节省设置和更新第三方开源库的时

Flyingasnail的博客 1031

AUTOSAR TcpIp模块 以太网配置开发2

someip协议之tcpIp协议

汽车电子电气 1085

【原】iOS开发进阶(唐巧)读书笔记(二)

第三部分:iOS开发底层原理 1、Objective-C对象模型 1.1 isa指针 NSObject.h部分代码: NS_ROOT_CLASS @interface NSObject <NSObject> { Class isa; } objc.h部分代码: typedef struct objc_class *Class; typedet struct objc_object { Class isa; } *id; 每个对象都有一个名为isa的指针,指向该对象...

ElegantHedgehog的博客 272

【异常】OpenClaw调用火山引擎大模型提示词 Agent failed before reply: All models failed (6): volcengine/kimi-k2-5-26

摘要: 本文针对OpenClaw调用火山引擎大模型服务时触发的全量熔断报错(All models failed)提供全链路排查方案。报错核心为服务熔断(cooldown)和模型不可用(model_not_found),主要源于账号权限、API凭证失效或模型标识错误。解决方案按优先级分步实施:1)校验账号状态与AK/SK有效性;2)核对官方模型标识;3)重启服务解除熔断;4)修复配置文件;5)通过原生API测试定位网络或服务端问题。日志分析(openclaw logs)可快速识别HTTP状态码(如401/40

奶爸的编程之路,也就一周冷个三天 837

PINN:当深度学习遇上朗伯-比尔定律

光谱数据处理中引入PINN的三种架构设计

qq_53529450的博客 154

iOS开发进阶-唐巧.pdf资源介绍:iOS开发技能提升宝典

iOS开发进阶-唐巧.pdf资源介绍:iOS开发技能提升宝典 去发现同类优质开源项目:https://gitcode.com/ 项目介绍 在iOS开发领域,提升技能是每位开发者永恒的追求。《iOS开发进阶-唐巧.pdf》应运而生,这部由资深开发者唐巧倾心编写的教程,从工具、实践到理论,全方位助力开发者掌握iOS开发的高级技巧,实现技能的飞跃。 项目技术分析 《iOS开发进阶-唐巧.pdf》涵盖了i...

gitblog_06711的博客 357

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

《iOS开发进阶-唐巧.pdf》资源介绍

《iOS开发进阶-唐巧.pdf》资源介绍 去发现同类优质开源项目:https://gitcode.com/ 《iOS开发进阶-唐巧.pdf》是一部深受开发者喜爱的iOS开发进阶教程。本书由三大部分组成:工具、实践和理论,旨在全面提升开发者的iOS开发技能。 内容概述 工具部分:详细介绍了iOS开发中常用的工具,帮助开发者掌握开发过程中的必备利器,提升开发效率。 实践部分:分享了一些iOS开...

gitblog_06748的博客 318

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

【笔记】《iOS开发进阶-唐巧》

CocoaPods 关于.gitignore 当你执行 pod install 之后,除了 Pod le 外,CocoaPods 还会生成一个名为 Podfile.lock 的文件,你不应该把这个文件加入到.gitignore 中。因为 Podfile.lock 会锁定当前各依 赖库的版本,之后如果多次执行 pod install 不会更改版本,要执行 pod update 才会改变 Podfi

吹泡泡的星星 712

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

唐巧的《iOS开发进阶》 - 读后感

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33682790的博客 2293

唐巧专访:用 HTML5 写移动应用终究不会成为主流

本文为 DevLink 专访系列,本期采访嘉宾是 iDev 苹果开发者大会特约出品人、小猿搜题产品技术负责人——唐巧。

DevLink 609

读<iOS开发进阶>有感

花了两天时间, 零零散散看完了这本书。总的来说, 比较失望吧。花点时间记录下。第一次看到这本书, 是在看唐巧大神博客的时候看到的  ---------->  《iOS开发进阶》即将出版那时候, 就被吸引住了。原因不外乎如下三点:作者: 唐巧大神写的书, 怎么能错过内容: 开发进阶, 学习了iOS有一段时间, 确实比较期待这类的书籍封面: 简约大气, 很喜欢。然后就一直等出版, 本来说12月底就能出

Colin丶 3226

唐巧的iOS技术博客好文列表

转载于:http://segmentfault.com/blog/hongliang/1190000002435233 过去几年里我和我的团队做的事情太杂了,甚至还扑在了cocos2d-x上,接下来终于可以专注在iOS和Android原生开发领域了,所以今天特意花了一天时间补课,把唐巧的博客截止到今天(2014.12.19)的好文都看了一遍,特此收藏一些有价值的文章: 设置系统控件的语

不积跬步无以至千里,千里之行始于足下! 2139

浅谈iOS进阶路线

目录 1.对本职业看法 2.学习方法 3.职业规划 4.产品公司&外包公司 5.做一个负责任的开发者 6.iOS开发学习路线 7.iOS基础知识点 8.iOS中级知识点 9.iOS高级知识点 10.官方Kit 对本职业看法 16年我尝试加入各种iOS开发交流群,群里的气氛大致就是:学什么iOS,iOS完了,OC完了.群里大致三种人:谁有企业开发证书,马甲包了解一下,iOS某网课推广.至今,大部分iOS开发群还都是仅供吹水用,偶尔能碰见几个好心人解决一下问题. 个人观点 个人观点:iOS开.

super_man_ww的博客 1335
上一篇: 《AV Foundation 开发秘籍》读书笔记(一)
下一篇: 《AV Foundation 开发秘籍》读书笔记(二)
mayan29
博客等级 码龄11年 13粉丝 24原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值