iOS 知识框架图
性能相关
1- 项目中造成Tableview等列表滑动卡顿的原因有哪些?你是如何优化的?
答:
造成卡顿的原因主要由两方面引起:CPU和GPU
CPU:
1- 对象的创建:对象的创建会分配内存、调整属性、甚至还有读取文件等操作,比较消耗 CPU 资源。尽量用轻量的对象代替重量的对象,可以对性能有所优化,比如用CALayer代替UIView。适当的场景用懒加载
2- 对象调整:对象的调整也经常是消耗 CPU 资源的地方。当视图层次调整时,UIView、CALayer 之间会出现很多方法调用与通知,所以在优化性能时,应该尽量避免调整视图层次、添加和移除视图。
3- 对象销毁:对象的销毁虽然消耗资源不多,但累积起来也是不容忽视的。当容器内有大量对象要销毁时,可以放在后台线程销毁。
4- 布局计算:尽量提前计算好布局,在需要时一次性调整好对应属性,而不要多次、频繁的计算和调整这些属性。
5- Autolayout:Autolayout 对于复杂视图来说常常会产生严重的性能问题。对于复杂试图可用frame计算布局。可以使用ComponentKit、AsyncDisplayKit(现已更换成Texture) 等框架。
6- 文本计算:常见的文本控件 (UILabel、UITextView 等),其排版和绘制都是在主线程进行的,当显示大量文本时,CPU 的压力会非常大。解决的方法只能通过自定义文本控件,用 TextKit 或最底层的 CoreText 对文本异步绘制。
7- 图片的解码:后台线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片。
8- 图像的绘制:把图像绘制任务放在后台线程中。例:
- (void)display {
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...);
// draw in context...
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
}
GPU:
1- 纹理的渲染:尽量减少在短时间内大量图片的显示,尽可能将多张图片合成为一张进行显示。
2- 视图的混合:尽量减少视图数量和层次,并在不透明的视图里标明 opaque 属性以避免无用的 Alpha 通道合成。
3- 图形的生成:避免离屏渲染,对于圆角,可以用一张已经绘制好的圆角图片覆盖到原本视图上面来模拟相同的视觉效果。最彻底的解决办法,就是把需要显示的图形在后台线程绘制为图片,避免使用圆角、阴影、遮罩等属性。
2- 如何实现列表多个定时器
答:定义一个数组存放列表的倒计时结束时间,创建一个GCD定时器,取出列表中每个时间进行倒计时,得到的定时器时间再对cell赋值即可。
参考demo:https://github.com/zhYes/YSTimeCountDown
3- 如何优化视频播放,如何实现自动根据网速选择合适清晰度
答:
1.监控网速:
导入头文件:
#include <ifaddrs.h>
#include <arpa/inet.h>
#include <net/if.h>
/*获取网络流量信息*/
- (long long) getInterfaceBytes
{
struct ifaddrs *ifa_list = 0, *ifa;
if (getifaddrs(&ifa_list) == -1)
{
return 0;
}
uint32_t iBytes = 0;
uint32_t oBytes = 0;
for (ifa = ifa_list; ifa; ifa = ifa->ifa_next)
{
if (AF_LINK != ifa->ifa_addr->sa_family)
continue;
if (!(ifa->ifa_flags & IFF_UP) && !(ifa->ifa_flags & IFF_RUNNING))
continue;
if (ifa->ifa_data == 0)
continue;
/* Not a loopback device. */
if (strncmp(ifa->ifa_name, "lo", 2))
{
struct if_data *if_data = (struct if_data *)ifa->ifa_data;
iBytes += if_data->ifi_ibytes;
oBytes += if_data->ifi_obytes;
}
}
freeifaddrs(ifa_list);
NSLog(@"\n[getInterfaceBytes-Total]%d,%d",iBytes,oBytes);
return iBytes + oBytes;
}
5、谈下iOS开发中知道的哪些锁?
一般开发中你最常用哪个?
哪个性能最差?了解SD和AFN使用哪些吗?
参考:
我们在使用多线程的时候多个线程可能会访问同一块资源,这样就很容易引发数据错乱和数据安全等问题,这时候就需要我们保证每次只有一个线程访问这一块资源,锁 应运而生
@synchronized
性能最差,SD和AFN等框架内部有使用这个.
NSRecursiveLock
和 NSLock
:建议使用前者,避免循环调用出现死锁
OSSpinLock
自旋锁,存在的问题是:优先级反转问题,破坏了spinlock
dispatch_semaphore
信号量 : 保持线程同步为线程加锁
dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 1.0f * NSEC_PER_SEC);
//Thread1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"Thread1 waiting");
dispatch_semaphore_wait(signal, overTime); //signal 值 -1
NSLog(@"Thread1");
dispatch_semaphore_signal(signal); //signal 值 +1
NSLog(@"Thread1 send signal");
});
//Thread2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"Thread2 waiting");
dispatch_semaphore_wait(signal, overTime);
NSLog(@"Thread2");
dispatch_semaphore_signal(signal);
NSLog(@"Thread2 send signal");
});
dispatch_semaphore_create(1): 若传入为0(over time 失效) 则阻塞线程并等待timeout,时间到后会执行其后的语句,
dispatch_semaphore_wait(signal, overTime):可以理解为 lock,会使得 signal 值 -1
dispatch_semaphore_signal(signal):可以理解为 unlock,会使得 signal 值 +1
6、@synchronized 、NSLock会带来什么问题,如何改进?
答:
- 容易造成死锁、效率低
- 派发队列可用来表述同步语义(synchronization semantic),这种做法要比使用 @synchronized 块或 NSLock 对象更简单。
- 将同步与异步派发结合起来,可以实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程。
- 使用同步队列及栅栏块,可以令同步行为更加高效。
方案一:(使用同步串行队列)
1 _syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL);
2 -(NSString *)someString {
3
4 __block NSString *localSomeString;
5 dispatch_sync(_syncQueue, ^{
6
7 localSomeString = _someString;
8
9 });
10 return localSomeString;
11 }
12
13
14
15 - (void)setSomeString:(NSString *)someString {
16
17 dispatch_sync(_suncQueue, ^{
18
19 _someString = someString;
20
21 });
22 }
方案二:(使用栅栏)
1 _syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAUL, 0);
2
3 - (NSString *)someString {
4
5 __block NSString *localSomeString ;
6 dispatch_sync(_syncQueue, ^{
7
8 localSomeString = _someString;
9
10 });
11 return localSomeString;
12 }
13
14 - (void)setSomeString:(NSString *)someString {
15
16 dispatch_barrier_async(_syncQueue, ^{
17
18 _someString = someString;
19
20 });
21 }
7、crash的收集和定位bug的方式
iTunes Connect(Manage Your Applications - View Details - Crash Reports),但是前提用户设置->隐私->诊断与用量->诊断与用量数据开启.一般不推荐
自己实现应用内崩溃收集,并上传服务器.(收集异常,存储到本地,下次用户打开程序时上传给我们)
在程序启动时加上一个异常捕获监听,用来处理程序崩溃时的回调动作UncaughtExceptionHandler是一个函数指针,该函数需要我们实现,可以取自己想要的名字。当程序发生异常崩溃时,该函数会得到调用,这跟C,C++中的回调函数的概念是一样的
NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler)。 程序启动代理方法
//:collection crash info by DragonLi
void UncaughtExceptionHandler(NSException *exception) {
NSArray *callStack = [exception callStackSymbols];
NSString *reason = [exception reason];
NSString *name = [exception name];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss"];
NSString * dateStr = [formatter stringFromDate:[NSDate date]];
NSString * iOS_Version = [[UIDevice currentDevice] systemVersion];
NSString * PhoneSize = NSStringFromCGSize([[UIScreen mainScreen] bounds].size);
NSString * App_Version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
NSString * iPhoneType = @"当前设备名字";
NSString *uploadString = @"所有拼接信息";
// 存储到本地沙盒.下次启动找寻
}
第三方收集crash (比如说集成Bugly/友盟,使用dSYM符号化并定位代码)
上报的方式,时机,策略(优缺点)等
8、runloop和线程有什么关系?
-
runloop和线程是一一对应关系
- key:thread ,value :loop
- 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。
保持程序的持续运行(ios程序为什么能一直活着不会死)
处理app中的各种事件(比如触摸事件、定时器事件【NSTimer】、selector事件【选择器·performSelector···】)
节省CPU资源,提高程序性能,有事情就做事情,没事情就休息
-
重要性
- 如果没有Runloop,那么程序一启动就会退出,什么事情都做不了。
- 如果有了Runloop,那么相当于在内部有一个事件循环,能够保证程序的持续运行
- main函数中的Runloop a 在UIApplication函数内部就启动了一个Runloop 该函数返回一个int类型的值 b 这个默认启动的Runloop是跟主线程相关联的
内存优化相关
1- 如何加快app启动速度
答:
app启动分冷启动和热启动两种,冷启动就是app从0开始启动,热启动是app退回桌面,没有被杀死的情况下启动。我们一般只需要对冷启动进行优化。
App启动过程:
①解析Info.plist
加载相关信息,例如闪屏
沙箱建立、权限检查
②Mach-O加载
如果是胖二进制文件,寻找合适当前CPU架构的部分
加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)
定位内部、外部指针引用,例如字符串、函数等
加载类扩展(Category)中的方法
C++静态对象加载、调用ObjC的 +load 函数
执行声明为__attribute__((constructor))的C函数
③程序执行
调用main()
调用UIApplicationMain()
调用applicationWillFinishLaunching
main()函数之前耗时的影响因素
- 动态库加载越多,启动越慢。
- ObjC类越多,启动越慢
- C的constructor函数越多,启动越慢
- C++静态对象越多,启动越慢
- ObjC的+load越多,启动越慢
对应的方法:
- 减少动态库,换成静态库。
- 多个动态库合成一个。
- 减少无用类和无用方法。
- 尽量不要写
__attribute__((constructor))
的C函数。 - 尽量不要用到C++的静态对象。
- 用
initlaze
方法代替load
方法。 - 合并功能类似的分类和扩展。
- 压缩资源图片
main()函数之后耗时的影响因素
- 执行main()函数的耗时
- 执行applicationWillFinishLaunching的耗时
- rootViewController及其childViewController的加载、view及其subviews的加载
对应的方法:
- 将不需要马上在applicationWillFinishLaunching执行的代码延后执行
- rootViewController的加载,适当将某一级的childViewController或subviews延后加载
- 如果你的App可能会被后台拉起并冷启动,可考虑不加载rootViewController
2、Autorelease的原理 ?
- ARC下面,我们使用@autoreleasepool{}来使用一个Autoreleasepool,实际上UIKit 通过RunLoopObserver 在RunLoop二次Sleep间Autoreleasepool进行Pop和Push,将这次Loop产生的autorelease对象释放 对编译器会编译大致如下:
void *DragonLiContext = objc_ AutoreleasepoolPush();
// {} 的 code
objc_ AutoreleasepoolPop(DragonLiContext);
- 释放时机: 当前RunLoop迭代结束时候释放.
ARC工作原理?
Automatic Reference Counting,自动引用计数,即ARC,ARC会自动帮你插入retain和release语句,ARC编译器有两部分,分别是前端编译器和优化器
前端编译器:前端编译器会为“拥有的”每一个对象插入相应的release语句。如果对象的所有权修饰符是__strong,那么它就是被拥有的。如果在某个方法内创建了一个对象,前端编译器会在方法末尾自动插入release语句以销毁它。而类拥有的对象(实例变量/属性)会在dealloc方法内被释放。事实上,你并不需要写dealloc方法或调用父类的dealloc方法,ARC会自动帮你完成一切。此外,由编译器生成的代码甚至会比你自己写的release语句的性能还要好,因为编辑器可以作出一些假设。在ARC中,没有类可以覆盖release方法,也没有调用它的必要。ARC会通过直接使用objc_release来优化调用过程。而对于retain也是同样的方法。ARC会调用objc_retain来取代保留消息
ARC优化器: 虽然前端编译器听起来很厉害的样子,但代码中有时仍会出现几个对retain和release的重复调用。ARC优化器负责移除多余的retain和release语句,确保生成的代码运行速度高于手动引用计数的代码
weak弱引用的代码逻辑实现?
objc_storeWeak() 实现
// HaveOld: true - 变量有值
// false - 需要被及时清理,当前值可能为 nil
// HaveNew: true - 需要被分配的新值,当前值可能为 nil
// false - 不需要分配新值
// CrashIfDeallocating: true - 说明 newObj 已经释放或者 newObj 不支持弱引用,该过程需要暂停
// false - 用 nil 替代存储
template bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
// 该过程用来更新弱引用指针的指向
// 初始化 previouslyInitializedClass 指针
Class previouslyInitializedClass = nil;
id oldObj;
// 声明两个 SideTable
// ① 新旧散列创建
SideTable *oldTable;
SideTable *newTable;
// 获得新值和旧值的锁存位置(用地址作为唯一标示)
// 通过地址来建立索引标志,防止桶重复
// 下面指向的操作会改变旧值
retry:
if (HaveOld) {
// 更改指针,获得以 oldObj 为索引所存储的值地址
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (HaveNew) {
// 更改新值指针,获得以 newObj 为索引所存储的值地址
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 加锁操作,防止多线程中竞争冲突
SideTable::lockTwoHaveOld, HaveNew>(oldTable, newTable);
// 避免线程冲突重处理
// location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改
if (HaveOld && *location != oldObj) {
SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
goto retry;
}
// 防止弱引用间死锁
// 并且通过 +initialize 初始化构造器保证所有弱引用的 isa 非空指向
if (HaveNew && newObj) {
// 获得新对象的 isa 指针
Class cls = newObj->getIsa();
// 判断 isa 非空且已经初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) {
// 解锁
SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
// 对其 isa 指针进行初始化
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// 如果该类已经完成执行 +initialize 方法是最理想情况
// 如果该类 +initialize 在线程中
// 例如 +initialize 正在调用 storeWeak 方法
// 需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记
previouslyInitializedClass = cls;
// 重新尝试
goto retry;
}
}
// ② 清除旧值
if (HaveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// ③ 分配新值
if (HaveNew) {
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,
(id)newObj, location,
CrashIfDeallocating);
// 如果弱引用被释放 weak_register_no_lock 方法返回 nil
// 在引用计数表中设置若引用标记位
if (newObj && !newObj->isTaggedPointer()) {
// 弱引用位初始化操作
// 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用
newObj->setWeaklyReferenced_nolock();
}
// 之前不要设置 location 对象,这里需要更改指针指向
*location = (id)newObj;
}
else {
// 没有新值,则无需更改
}
SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);
return (id)newObj;
}
安全相关
1- 项目中如何对敏感信息加密
信息加密常见的有两类:
第一类无需解密:例如系统登录密码加密,通过加密算法对用户输入密码进行加密后存放在数据库中,用户再次登录时依然拿相同的加密算法对用户输入密码进行加密,拿加密后的结果和数据库中存放的结果做对比,整个过程中都不需要知道用户输入的原始密码是什么,MD5是处理此类加密最常用的加密算法
第二类需要解密:例如我们写在项目代码中连接数据库的账号密码,项目代码中以密文方式存储,当需要连接数据库的时候,要对密文进行解密,拿到原始未加密的账号密码去连接数据库,与MD5单向加密不同,这类加密需要能对加密后的密文进行解密,此类加密方法目前最常用的加密算法为RSA
HTTPS加密流程:
服务器端用非对称加密(RSA)生成公钥和私钥
然后把公钥发给客户端, 服务器则保存私钥
客户端拿到公钥后, 会生成一个密钥, 这个密钥就是将来客户端和服务器用来通信的钥匙
然后客户端用公钥对密钥进行加密, 再发给服务器
服务器拿到客户端发来的加密后的密钥后, 再使用私钥解密密钥, 到此双方都获得通信的钥匙
2- 如何防抓包、防反调试?
防抓包:
可以判断用户是否设置了代理:
- (BOOL)getProxyStatus {
NSDictionary *proxySettings = (__bridge NSDictionary *)(CFNetworkCopySystemProxySettings());
NSArray *proxies = (__bridge NSArray *)(CFNetworkCopyProxiesForURL((__bridge CFURLRef _Nonnull)([NSURL URLWithString:@"http://www.baidu.com"]), (__bridge CFDictionaryRef _Nonnull)(proxySettings)));
NSDictionary *settings = [proxies objectAtIndex:0];
NSLog(@"host=%@", [settings objectForKey:(NSString *)kCFProxyHostNameKey]);
NSLog(@"port=%@", [settings objectForKey:(NSString *)kCFProxyPortNumberKey]);
NSLog(@"type=%@", [settings objectForKey:(NSString *)kCFProxyTypeKey]);
if ([[settings objectForKey:(NSString *)kCFProxyTypeKey] isEqualToString:@"kCFProxyTypeNone"]){
//没有设置代理
return NO;
}else{
//设置代理了
return YES;
}
}
防反调试:
- 防止Ptrace调试器依附。
- 越狱检测。
- 敏感字符串安全。
- 代码混淆。
- 关键代码使用C甚至汇编实现。
网络相关
HTTP和HTTPS的区别
- HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
- 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。
- HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
- http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
- HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,要比较 HTTPS 比 HTTP 要更耗费服务器资源。
1- 接入HTTPS流程
- 申请SSL证书
- 设置AFN请求管理者的时候 添加 https ssl 验证。
// 1.获得请求管理者
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// 2.加上这个函数,https ssl 验证。
[manager setSecurityPolicy:[self customSecurityPolicy]];
// https ssl 验证函数
- (AFSecurityPolicy *)customSecurityPolicy {
// 先导入证书
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"xxx" ofType:@"cer"];//证书的路径
NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
// AFSSLPinningModeCertificate 使用证书验证模式
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
// allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
//validatesDomainName 是否需要验证域名,默认为YES;
}
2- TCP和UDP的区别
3- TCP/IP 3次握手的流程
- 在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接
- 第一次握手:客户端尝试连接服务器,向服务器发送 syn 包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入 SYN_SEND 状态等待服务器确认
- 第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个 SYN包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态
- 第三次握手:第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手
4 网络通讯中加密方式有哪些,各自的原理?
- md5(哈希算法):把任意长度的字符串加密成一个128bit的大整数,并且是不可逆的 (已不再安全)
- RSA(非对称算法加密):产生一对非对称的公钥和私钥,公钥加密,私钥解密。私钥加密,公钥解密
- AES(对称加密):加密和解密的密钥是同一个
- base64(现代密码学的基础):原本8 bit一组的数据改为6bit一组,不足的地方补0,每两个0用一个 = 表示
设计、架构相关
1- MVVM的思想是什么?你在项目中是如何设计的
Cocoapods 相关
熟悉CocoaPods么?能大概讲一下工作原理么?
Podfile.lock:在pod install以后会生成一个Podfile.lock的文件,这个文件在多人协作开发的时候就不建议加入在.gitignore中,因为这个文件会锁定当前各依赖库的版本,就算之后再pod install也不会更改版本,不提交上去的话就可以防止第三方库升级后造成大家各自的第三方库版本不同
CocoaPods原理
CocoaPods的原理是将所有的依赖库都放到另一个名为Pods的项目中,然后让主项目依赖Pods项目,这样,源码管理工作都从主项目移到了Pods项目中。Pods项目最终会编译成一个名为libPods.a的文件,主项目只需要依赖这个.a文件即可。
- 运行pre-install hook
- 生成Pod Project
- 将该pod文件添加到工程中
- 添加对应的framework、.a库、bundle等
- 链接头文件,生成Target
- 运行post-install hook
- 生成podfile.lock ,之后生成文件副本mainfest.lock并将其放在Pod文件夹内。(如果出现 The sandbox is not sync with the podfile.lock这种错误,则表示manifest.lock和podfile.lock文件不一致),此时一般需要重新运行pod install命令。
- 配置原有的project文件(add build phase)
- 添加了 Embed Pods Frameworks
- 添加了 Copy Pod Resources 其中,pre-install hook和post-install hook可以理解成回调函数,是在podfile里对于install之前或者之后(生成工程但是还没写入磁盘)可以执行的逻辑,逻辑为:
- pre_install do |installer| # 做一些安装之前的hook end
- post_install do |installer| # 做一些安装之后的hook end
1. 简单概述 CocoaPods 的核心模块?
2. pod 命令是如何找到并启动 CocoaPods 程序的?
1- 每当我们输入 pod xxx 命令时,系统会首先调用 pod 命令。所有的命令都是在 /bin 目录下存放的脚本,当然 Ruby 环境的也不例外。
2- 程序 CocoaPods 是作为 Gem 被安装的,此脚本用于唤起 CocoaPods。逻辑比较简单,就是一个单纯的命令转发。Gem.activate_bin_path
和 Gem.bin_path
用于找到 CocoaPods 的安装目录 cocoapods/bin
,最终加载该目录下的/pod
文件。
3- ruby_executable_hooks
通过 bin 目录下的 pod 入口唤醒,再通过 eval[8] 的手段调起我们需要的 CocoaPods 工程。这是 RVM 的自身行为,它利用了 executable-hook
来注入 Gems 插件来定制扩展。
4- 在入口的最后部分,通过调用 Pod::Command.run(ARGV)
,实例化了一个 CLAide::Command
对象,开始我们的 CLAide
命令解析阶段。
3. 简述 pod install 流程?
4. resolve_dependencies 阶段中的pre_download 是为了解决什么问题?
该过程主要是解决当我们在通过 Git 地址引入的 Pod 仓库的情况下,系统无法从默认的 Source 拿到对应的 Spec,需要直接访问我们的 Git 地址下载仓库的 zip 包,并取出对应的 podspec 文件,从而进行对比分析。