CocoaPods 是开发 iOS 应用程序的一个第三方库的依赖管理工具,起始于2011年8月,用 Ruby 写的。
CocoaPods 的原理是将所有的依赖库都放到另一个名为 Pods 的项目中,然后让主项目依赖 Pods 项目。下面是一些技术细节:
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
在项目文件夹中创建名为 Podfile 的文件
$ touch Podfile
在文件中添加依赖库名称
platform :ios, ‘8.0’
target ‘jiaheyingyuan’ do
pod ‘AFNetworking’
pod ‘SDWebImage’
end
如果不确定三方库版本,查找第三方库
$ pod search AFNetworking
然后在项目文件夹中执行
$ pod install
注意事项
为自己的项目创建 podspec 文件参考这两篇文章
不更新 podspec
在执行 pod install 和 pod update 时,默认先更新一次 podspec 索引,如下代码可以禁止其做索引更新操作
pod install --no-repo-update
pod update --no-repo-update
在 Charles 官方网站(https://www.charlesproxy.com)下载安装。
如果需要截取分析 SSL 协议相关内容,那么需要安装 Charles 的 CA 证书(http://www.charlesproxy.com/ssl.zip)。解压后双击 .crt 文件,在钥匙串 -> 系统 -> 证书中可查看
Charles 是通过将自己设置成代理服务器来完成封包截取的,将 Charles 设置成系统代理,菜单中 Proxy -> Mac OS X Proxy 将 Charles 设置成系统代理。之后浏览网页就可以看到网络请求出现在 Charles 界面中。
在主界面的中部的 Filter 栏中填入需要过滤出来的关键字,比如 baidu。这种方法是临时性的封包过滤。
Proxy -> Recording Settings -> Include,这种方法是经常性的封包过滤。
Proxy -> Proxy Settings 填入代理端口 8888,并勾选 Enable transparent HTTP proxying 然后在手机端设置代理即可
Proxy -> Throttle Setting 勾选 Enable Throttling 并且可以设置 Throttle Preset 的类型。如果只想模拟指定网站的慢速网络,可以勾选 Only for selected hosts 然后在对话框的下半部分设置中增加指定的 Hosts 项即可。
在该请求上单机右键,选择 SSL Proxying 然后对于该 Host 的所有 SSL 请求都可以被截取到了
调试接口时,我们需要反复尝试不同参数的网络请求,在请求上点击右键,选择 Edit 即可创建一个可编辑的网络请求,可修改 URL 地址、端口、参数等,修改完成后单击 Execute 即可发送
Charles 提供了 Map、Rewrite、Breakpoints 功能,都可以达到修改服务器返回内容的目的。
Map 功能分为两种,进入方式 Tools -> Map Remote 或 Map Local
对于 Map Remote 功能,我们需要分别填写网络重定向的源地址和目的地址,对于不需要限制的条件,可以留空。举例:Map From 测试服务器的请求重定向到 Map To 线上服务器。
对于 Map Local 功能,我们需要填写重定向的源地址和本地的目标文件。对于一些复杂的网络请求结果,我们可以先使用右键单击 Save Response 功能,将请求结果保存到本地,然后稍加修改,使其成为我们的目标映射文件。
Rewrite 功能适合对某一类网络请求进行一些正则替换,以达到修改结果的目的。例如,一个 API 请求时获得用户昵称,将 Rewrite Rule 界面的 Match 的 value 设置之前的昵称,Replace 的 value 设置将要修改的昵称
Rewrite 功能适合做批量和长期的替换,临时性的修改最好使用 Breakpoints 功能。Breakpoints 功能类似 Xcode 中设置的断点,当网络请求发生时,Charles 会截取该请求,这个时候,我们可以在 Charles 中临时修改网络请求的返回内容。修改完成后单击 Execute 即可让网络请求继续进行。需要注意的是,使用 Breakpoints 功能将网络请求截取并修改的过程中,整个网络请求的计时并不会暂停,所以长时间的暂停可能导致客户端的请求超时。
Reveal 可以在 iOS 开发时动态的查看和修改应用程序的界面。iOS 逆向工程中使用强大,可以分析他人的 APP,缺点是真特么贵。
Flurry 是一家专门为移动应用提供数据统计和分析的公司。Flurry 优点:保持独立和专注,数据安全性更高。友盟已经被阿里收购,当用户的应用涉及的业务和阿里有类似或重合的时候,该统计数据有潜在的安全性问题
Crashlytics 是专门为移动应用开发者提供的保存和分析应用崩溃信息的专业工具。
App Annie 是一个 App Store 数据的统计分析工具。该工具可以统计 App 在 App Store 的下载量、排名变化、销售收入情况、用户评价等信息。
苹果官方的 iTunes Connect 提供的销售数据统计功能比较差,例如只能保存最近30天的详细销售数据、界面丑陋、无法查看应用的排名历史变化情况等
Dash 是一款 API 文档查询及代码片段管理工具,超级好用
蒲公英是一个应用的内测分发工具,类似苹果的 TestFlight。把 App 安装包上传到蒲公英,生成一个二维码,用户扫码就可以安装应用。类似的还有 FIR 提供同样的服务。
假如对象 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,对象的生命期管理可以完全交给引用计数了。
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 值也变得没有意义。这样减少一次内存的操作,加速对象的回收。
弱引用持有对象,但是不增加引用计数,这样就避免了循环引用的产生
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *firstArray = [NSMutableArray array];
NSMutableArray *secondArray = [NSMutableArray array];
[firstArray addObject:secondArray];
[secondArray addObject:firstArray];
}
我们可以切换打印模块上的 Leaks 切换为 Cycles & Roots 可能看到以图形方式显示出来的循环引用
CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, "hello world", kCFStringEncodingUTF8);
CFRetain(str); // 引用计数 +1
CFRelease(str); // 引用计数 -1
CFRetain 和 CFRelease 方法与 Objective-C 对象的 retain 和 release 方法等价。
将 Core Foundation 对象转换成一个 Objective-C 对象,引入了 bridge 相关关键字
字体文件通常比较大,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。
网络安全:json 字段加密,使其不能直观的猜出内容
js 文件安全:将 js 源码进行混淆和加密,防止黑客轻易的阅读和篡改相关的逻辑,也可以防止自己的 Web 端和 Native 端通讯协议泄漏
本地数据安全:对于本地的重要数据,我们应该加密存储或者将其保存到 keychain 中,以保证其不被篡改
源代码安全:对于 IDA 这类工具,我们的应对措施就比较少了,除了用一些宏来简单混淆类名外,我们也可以将关键的逻辑用存 C 实现,不但保证安全性,还可以在 iOS 和 Android 使用同一套底层通讯代码,达到复用的目的。
使用 CoreText 技术,我们可以对富文本进行复杂的排版。经过一些简单的扩展,我们还可以实现对于图片、链接的点击效果。CoreText 技术相对于 UIWebView 有内存占用少,可以后台渲染的优点,非常适合排版工作。
点击这里查看更多
最简单的办法是将应用的上架时间改成未来的一个时间,这样就会在数小时之内下架
在选择持久化方案时,系统提供的 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 秒
在 iOS 系统中,有时候会需要调用系统的一些 UI 控件,例如:
以上 UI 控件中,显示的语言并不是和你当前手机的系统语言一致的,语言设置成中文,需要在 info.plist 文件中增加以下内容即可
<key>CFBundleLocalizationskey>
<array>
<string>zh_CNstring>
<string>enstring>
array>
iOS 7 以后的系统,可以通过系统提供的 API 来实现截屏功能
- (nullable UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates;
早期的 QQ 在侧滑到一半的时候,整个当前界面被移动到了右半部分,同时在左半部分以半透明的方式露出了上一个界面。由于 ViewController 并不支持自己的 view 设置透明,所以需要我们自己实现。
为了使用截屏功能达到这种效果,我们在 NavigationController 进入到一个新的 ViewController 前,先进行截屏操作,保存当前的界面效果,然后将截到的当前界面作为参数,传递给目标 ViewController 当作背景。
这样,平时这个背景我们用内容遮挡住,当用户用手指向右滑动,我们将整个界面右移,露出这个背景,于是就会像看到了上一个 ViewController 一样。
快捷键 | 说明 |
---|---|
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 | 清空编译好的文件 |
- (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]);
}
iPhone 5s 配备了首个采用 64 位架构的 A7 双核处理器,同时提出了 Tagged Pointer 的概念。对于 64 位系统,引入 Tagged Pointer 后,相关逻辑能减少一半的内存占用,3 倍的访问速度提升,100 倍的创建、销毁速度提升。
举个例子:
所以如果没有 Tagged Pointer 对象,从 32 位机器迁移到 64 位机器中,虽然逻辑没有变化,但是对象所占用的内存会翻倍:
为了改进上面提到的内存占用和效率问题,苹果提出了 Tagged Pointer 对象。将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址,64 位 CPU 下 NSNumber 的内存图变成下面这样:
在 32 位环境下,对象的引用计数都保存在一个外部的表中,每一个对象的 Retain 操作,实际包括如下 5 个步骤:
而在 64 位环境下,isa 指针也是 64 位,实际作为指针部分只用到其中 33 位,剩余的 31 位苹果使用了类似 Tagged Pointer 的概念,其中 19 位将保存对象的引用计数,这样对引用计数的操作只需要修改这个指针即可。只有当引用计数超出 19 位,才会将引用计数保存到外部表,但是这种情况是很少的。在 64 位环境下,新的 Retain 操作包括如下 5 个步骤:
虽然步骤都是 5 步,但是由于没有了全局的加锁操作,所以引用计数的更改更快了。
bit 位 | 变量名 | 意义 |
---|---|---|
1 bit | indexed | 0 表示普通的 isa,1 表示 Tagged Pointer |
1 bit | has_assoc | 表示该对象是否有过 associated 对象,如果没有,在析构释放内存时可以更快 |
1 bit | has_cxx_dtor | 表示该对象是否有 C++ 或 ARC 的析构函数,如果没有,在析构释放内存时可以更快 |
30 bits | shiftcls | 类的指针 |
9 bits | magic | 其值固定为 0xd2,用于在调试时分辨对象是否未完成初始化 |
1 bit | weakly_referenced | 表示该对象是否有过 weak 对象,如果没有,在析构释放内存时可以更快 |
1 bit | deallocating | 表示该对象是否正在析构 |
1 bit | has_sidetable_rc | 表示该对象的引用计数值是否大到无法直接在 isa 中保存 |
19 bits | extra_rc | 表示该对象超过 1 的引用计数值,例如,如果该对象的引用计数是 6,则 extra_rc 的值为 5 |
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 部分构成:
void(^myBlock)() = ^{
NSLog(@"hello world");
};
NSLog(@"%@", myBlock); // <__NSGlobalBlock__: 0x10d65b240>
对 NSGlobalBlock 的 retain、copy、release 操作都无效。
在 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>
前前后后大概半个月,利用零碎时间看完了这本书。这本书前年就有所耳闻,网上的评价也褒贬不一,唐巧写的相对来说,语言通俗易懂,感觉像是博客一样,没有什么限制,读起来很畅快。这本书适合初学者,虽然这本书大部分内容之前都已经熟悉掌握,但是看过一遍还是很有收获的,有些知识点的总结很到位,感觉没有浪费时间。遗留的问题是 CoreText 部分内容打算结合其他资料系统的研究一下,这里就先不仔细阅读了,更多内容请关注我的 GitHub:https://github.com/Mayan29/ReadingNotes