iOS开发进阶观后-中篇(OC底层原理篇)

2018-01-13 11:06:31来源:oschina作者:RayChow_周小睿人点击

分享
简介
本篇主要内容是OC底层原理篇总结,分别从OC对象模型、Tagged Pointer、block三个方面,在此过程中还会涉及一些相关的系统底层MCU指针PC指针概念类比。
前言

由于篇幅问题,会分三篇描述对应不同内容。辅助工具,底层原理,开发中要注意问题三个方面,谈谈对应的总结。本人觉得书只是一个索引,特别对于技术类书籍,基本都是通过书籍引入一些观点,然后在通过其它第三方途径进行扩展。所以本文描述内容不一定就是书本内容,会与自身实践经验,还有部分内容精简和拓展。有些基础概念第三方描述已足够详细,实例也足够详细,本文仅仅提出一些个人总结理解,不在重复描述具体功能原理。

文字不如图片直观,所以先上一张本系列描述的观点的思维导图,梳理脉络。红色部分为本文内容梳理。

《思维导图链接(点我打开)最新版本》

输入图片说明


OC对象模型
1 isa指针 (类比8位MCU pc指针)

在Objective-C中,任何类的定义都是对象。类和类的实例(对象)没有任何本质上的区别。

isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。

类比8位MCU的pc指针,我个人理解isa指针,在硬件层面上就是指向代码的运行地址可以试RAM地址或ROM地址,等同MCU汇编的PC指针,调用函数时,需要先把当前函数地址压入堆栈,燃后放置新地址到PC指针,然后退去取回原堆栈内的地址。但是OC isa 在这个基础上增加了继承的概念。

以下是实例引用流程:

输入图片说明

每一个对象本质上都是一个类的实例。其中类定义了成员变量和成员方法的列表。对象通过对象的isa指针指向类。

每一个类本质上都是一个对象,类其实是元类(meteClass)的实例。元类定义了类方法的列表。类通过类的isa指针指向元类。

所有的元类最终继承一个根元类,根元类isa指针指向本身,形成一个封闭的内循环。

2 runtime 运行时,动态方法交换

指一个程序在运行(或者在被执行)的状态。也就是说,当你打开一个程序使它在电脑上运行的时候,那个程序就是处于运行时刻。在一些编程语言中,把某些可以重用的程序或者实例打包或者重建成为“运行库"。这些实例可以在它们运行的时候被连接或者被任何程序调用。

objective-c中runtime:是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API。 在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码。

RunTime基本常用法 (《iOS开发进阶》页码:220 - 225 ps:这里说得不够细)

//1 动态增加变量
@property (nonatomic, assign) BOOL isNotIgnore;
//runtime 动态绑定 属性
- (BOOL)isNotIgnore{
//_cmd == @select(isIgnore); 和set方法里一致
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (void)setIsNotIgnore:(BOOL)isNotIgnore{
// 注意BOOL类型 需要用OBJC_ASSOCIATION_RETAIN_NONATOMIC 不要用错,否则set方法会赋值出错
objc_setAssociatedObject(self, @selector(isNotIgnore), @(isNotIgnore), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}//2 对象方法的交换
/**
*对象方法的交换
*
*@param anClass哪个类
*@param method1Sel 方法1
*@param method2Sel 方法2
*/
+ (void)exchangeInstanceMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel {
Method method1 = class_getInstanceMethod(anClass, method1Sel);
Method method2 = class_getInstanceMethod(anClass, method2Sel);
method_exchangeImplementations(method1, method2);
}//3 动态类方法的交换
/**
*类方法的交换
*
*@param anClass哪个类
*@param method1Sel 方法1
*@param method2Sel 方法2
*/
+ (void)exchangeClassMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel {
Method method1 = class_getClassMethod(anClass, method1Sel);
Method method2 = class_getClassMethod(anClass, method2Sel);
method_exchangeImplementations(method1, method2);
}//4 动态类方法的交换
/**
*对象方法重置
*
*@param anClass哪个类
*@param method1Sel 方法1
*/
+ (void)setClassMethod:(Class)anClass oldMethodSel:(SEL) oldMethodSel newMethodSel:(SEL)newMethodSel{
Method oldMethod = class_getInstanceMethod(anClass, oldMethodSel);
Method newMethod = class_getInstanceMethod(anClass, newMethodSel);
method_setImplementation(oldMethod, method_getImplementation(newMethod));
}Tagged Pointer
1 32/64位设备区别

这个概念其实对应现在已经有点老了,32为设备已在苹果放弃支持的日程表上了。但是对于一些基础还是有必要了解一下。简单就是在64位设备上,同一地址裂开2部分使用,那其中32位最基础值类保存,另外位数存指针,索引表之类的数据,复用一个地址上的存储空间。这点和mcu内存一个byte,低4位和高4分开存在2个0-16的数值,是类似的。

假设我们要存储一个NSNumber对象,其值是一个整数。正常情况下,如果这个整数只是一个NSInteger的普通变量,那么它所占用的内存是与CPU的位数有关,在32位CPU下占4个字节,在64位CPU下是占8个字节的。而指针类型的大小通常也是与CPU位数相关,一个指针所占用的内存在32位CPU下为4个字节,在64位CPU下也是8个字节。

普通的iOS程序,如果没有Tagged Pointer对象,从32位机器迁移到64位机器中后,虽然逻辑没有任何变化,但这种NSNumber、NSDate一类的对象所占用的内存会翻倍

为了改进上面提到的内存占用和效率问题,苹果提出了Tagged Pointer对象。由于NSNumber、NSDate一类的变量本身的值需要占用的内存大小常常不需要8个字节,拿整数来说,4个字节所能表示的有符号整数就可以达到20多亿(注:2^31=2147483648,另外1位作为符号位),对于绝大多数情况都是可以处理的。

Block
1 系统底层实现
《Block技巧与底层解析》这个说明说得比书本更详细。我简单总结一下,底层就是结构体存着函数地址起始,及其长度。在需要时调用该变量。但是中间过程会有变量的复制问题,和引用问题处理。
2 block中变量复制问题

__block修饰变量,在block中,使用能直接改变变量的值。

没有__block修饰变量,在block使用,等于原变量copy了一个新变量,改变其值不影响原值。

3 循环引用 (ARC下)

这个问题出现在block替换代理时,引用变量导致控制器不释放比较常见。因为copy导致原变量引用加一。

所以block引用self,必须加上弱引声明。

__weak typeof(BaseViewController) *weakSelf = self;
4 《block基本用法》
内存管理 (参考《OC内存管理》)
1 引用计数

本质原因是因为对象和其他数据类型在系统中的存储空间不一样,其它局部变量主要存放于栈中,而对象存储于堆中,当代码块结束时这个代码块中涉及的所有局部变量会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,造成内存泄露。

每个OC对象都有自己的引用计数器,是一个整数表示对象被引用的次数,即现在有多少东西在使用这个对象。对象刚被创建时,默认计数器值为1,当计数器的值变为0时,则对象销毁。
在每个OC对象内部,都专门有4个字节的存储空间来存储引用计数器。判断对象要不要回收的唯一依据就是计数器是否为0,若不为0则存在。

block中,由于对象会copy,所有会做成原变量引用加入,所有会导致不释放。

2 ARC/MRC
MRC就是手工管理对象引用问题,ARC就是编译器处理引用问题,自动插入释放引用代码。实际衔接用MRC的可能就只有第三方库,真实开发暂时都是ARC。
3 新旧系统消息处理区别
有一个坑IOS8以下系统对已被释放的对象发消息会导致APP崩溃,并且dealloc时移除通知.不然有可能奇奇怪怪的问题出现。
GCD (《iOS中GCD的使用小结》《GCD死锁及取消》《GCD 与 NSOperationQueue 区别》)
1 异步处理同步化
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
//子线程异步执行下载任务,防止主线程卡顿
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
NSError *error;
NSString *htmlData = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if (htmlData != nil) {
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//异步返回主线程,根据获取的数据,更新UI
dispatch_async(mainQueue, ^{
NSLog(@"根据更新UI界面");
});
} else {
NSLog(@"error when download:%@",error);
}
});
2 相互依赖
- (void)groupSync
{
dispatch_queue_t disqueue =dispatch_queue_create("com.shidaiyinuo.NetWorkStudy", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t disgroup = dispatch_group_create();
dispatch_group_async(disgroup, disqueue, ^{

NSLog(@"任务一完成");
});dispatch_group_async(disgroup, disqueue, ^{

sleep(8);
NSLog(@"任务二完成");
});dispatch_group_notify(disgroup, disqueue, ^{

NSLog(@"dispatch_group_notify 执行");
});dispatch_async(dispatch_get_global_queue(0, 0), ^{

dispatch_group_wait(disgroup, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
NSLog(@"dispatch_group_wait 结束");
});
}
3 强制打断
业务场景 滚动scrolview,监听最后一次滚动效果。或请求超时强制放弃超时结果。或多次调用统一函数只取最后一次结果。
@property (nonatomic, strong) dispatch_block_t dispatch_stopLoading_block;
@weakify(self)
if (self.dispatch_stopLoading_block) {
dispatch_block_cancel(self.dispatch_stopLoading_block);
self.dispatch_stopLoading_block = NULL;
}
self.dispatch_stopLoading_block = dispatch_block_create(0, ^{
@strongify(self)
NSLog(@"任务一完成");
});
//任务延迟启动
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), self.dispatch_stopLoading_block);
4 信号量 (《浅谈GCD中的信号量》)
//由于设定的信号值为2,先执行两个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2。
-(void)dispatchSignal{
//crate的value表示,最多几个资源可访问
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//任务1
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(1);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(1);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});
}

原文:http://raychow.linkfun.top/2018/01/07/archives/1_ios/2017-section-2/index/

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台