前言
最近和基友 Maize 聊天,他给我普及了一个有意思的知识点,回看唐巧的 深入理解Tagged Pointer 的文章,再结合之前在公司看到的代码,突然有了一些灵感,我们先上一段代码。
@interface NSObject (AssociatedObject) @property (nonatomic, assign) CGFloat someProperty; @end @implementation NSObject (AssociatedObject) @dynamic someProperty; - (void)setSomeProperty:(CGFloat)someProperty{ return objc_setAssociatedObject(self, @selector(someProperty), @(someProperty), OBJC_ASSOCIATION_ASSIGN); } - (CGFloat)someProperty{ return [objc_getAssociatedObject(self, @selector(someProperty)) floatValue]; } @end
如果此时我们给 someProperty 属性赋值并打印该属性的值,你认为它会 crash 么? 我们先说一下结论,如果这个属性的值为100,那么它不会 crash,如果是 100.1 那么就一定会 crash。 如果你马上就明白了其中的原理,那么恭喜你,你完全不用再花时间看此篇文章了。 如果你没有意识到其中的问题,不妨花上 10 分钟时间来仔细读读这篇文章的内容。
Tagged Pointer 是什么?
本节内容是节选自唐巧的文章 - 深入理解Tagged Pointer
在 2013 年的 WWDC 上,Apple 推出了首个 64 位架构的双核处理器,为了节省内存和提高执行效率,Tagged Pointer 概念诞生了。Apple 宣称引入该技术后,相关逻辑能减少一半的内存占用,以及 3 倍的访问速度提升,100 倍的创建、销毁速度提升。
为了能让大家更好的理解上面代码的问题,我们需要了解 Tagged Pointer 的一些实现细节。
我们知道 NSNumber、NSDate 一类的变量本身的值需要占用的内存大小常常不需要 8 个字节,拿整数来说,4 个字节所能表示的有符号整数就可以达到 20 多亿(注:2^31=2147483648,另外 1 位作为符号位),对于绝大多数情况都是可以处理的。
所以 Apple 将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。所以,引入了 Tagged Pointer 对象之后,64 位 CPU 下 NSNumber 的内存图变成了以下这样:
int main(int argc, char * argv[]) {@autoreleasepool {NSNumber *number1 = @1;NSNumber *number2 = @2;NSNumber *number3 = @3;NSNumber *numberFFFF = @(0xFFFF);NSLog(@"number1 pointer is %p", number1);NSLog(@"number2 pointer is %p", number2);NSLog(@"number3 pointer is %p", number3);NSLog(@"numberffff pointer is %p", numberFFFF);return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));} }
运行之后,我们得到的结果如下,可以看到,除去最后的数字最末尾的 2 以及最开头的 0xb,其它数字刚好表示了相应 NSNumber 的值。
number1 pointer is 0xb000000000000012 number2 pointer is 0xb000000000000022 number3 pointer is 0xb000000000000032 numberFFFF pointer is 0xb0000000000ffff2
可见,苹果确实是将值直接存储到了指针本身里面。我们还可以猜测,数字最末尾的 2 以及最开头的 0xb 是否就是苹果对于 Tagged Pointer 的特殊标记呢?我们尝试放一个 8 字节的长的整数到 NSNumber 实例中,对于这样的实例,由于 Tagged Pointer 无法将其按上面的压缩方式来保存,那么应该就会以普通对象的方式来保存,我们的实验代码如下:
NSNumber *bigNumber = @(0xEFFFFFFFFFFFFFFF); NSLog(@"bigNumber pointer is %p", bigNumber);
运行之后,结果如下,验证了我们的猜测,bigNumber 的地址更像是一个普通的指针地址,和它本身的值看不出任何关系:
bigNumber pointer is 0x10921ecc0
Tagged Pointer 带来的问题?
我们结合一下刚才的代码,试想如果我们有以下两种使用场景,哪一种会有问题呢?
-
代码片段 1
// CodeSnippets 1 self.view.someProperty = 100; NSLog(@"%@", @(self.view.someProperty));
-
代码片段 2
//CodeSnippets 2 self.view.someProperty = 100.1; NSLog(@"%@", @(self.view.someProperty));
不管你是通过代码实验的,还是已经想明白了,第二个代码片段是会造成 crash 的,而且 Xcode 会提示这是一个野指针方面的问题。
那么结合刚才所说的 Tagged Pointer,我们怎么解释这个问题呢?
当赋值为 100 时,由于 Tagged Pointer 的优化,@(100)
生成的 NSNumber 对象并不是一个严格意义上的对象,所以系统不会在堆上开辟内存存储对象,值是直接保存在地址指针里面,在取出的时候也就不会出现任何释放的问题。
而当赋值为 100.1 时,由于 Tagged Pointer 对这种值没法进行优化,@(100.1) 生成的 NSNumber 对象是一个真正意义上的对象,所以此时存储下来的是一个地址指针,但由于在关联的时候我们选用了 OBJC_ASSOCIATION_ASSIGN
,那么此时系统并不会帮我们去进行那些计数引用等操作,所以当我们想再取出的时候,就会出问题了,也就是野指针的问题。
至此,应该很好的解释了前言中那段代码的问题。
结论
那么我们应该如何优化这段代码呢?
-
假设你真的想存一个 CGFloat,由于在存取过程中操作的是一个对象,不妨将
objc_AssociationPolicy
选项中的OBJC_ASSOCIATION_ASSIGN
换成OBJC_ASSOCIATION_RETAIN_NONATOMIC
。 -
如果可以的话,建议直接保存一个 NSNumber 类型的对象来替换 CGFloat 类型的数值,毕竟这样会更安全,记得同样把关联策略设定为
OBJC_ASSOCIATION_RETAIN_NONATOMIC
。
关于 CGFloat 还需要注意的一点是:在不同的 CPU 下,它的真实类型是不一样的,有时是 float,有时是 double,那么在从 NSNumber 做转换的时候,到底要返回何种类型,仍然需要开发者做好判断。
-
在 Apple 的 API 文档中,
OBJC_ASSOCIATION_ASSIGN
说是用于 weak 类型的引用,但并不是 ARC 范围内的 weak,严格来说应该更类似于unsafe_unretained
的概念,如果真的要用OBJC_ASSOCIATION_ASSIGN
策略,请时候考虑好对象的生命周期,避免不必要的 crash 。
参考 NSHipster 的文章Associated Objects
-
如果你是声明一个 ARC 范围内的
weak
属性,你可能需要一个类似弱引用表概念的东西,不妨阅读下瓜神的这篇文章 weak 弱引用的实现方式
网络安全入门学习路线
其实入门网络安全要学的东西不算多,也就是网络基础+操作系统+中间件+数据库,四个流程下来就差不多了。
1.网络安全法和了解电脑基础
其中包括操作系统Windows基础和Linux基础,标记语言HTML基础和代码JS基础,以及网络基础、数据库基础和虚拟机使用等...
别被这些看上去很多的东西给吓到了,其实都是很简单的基础知识,同学们看完基本上都能掌握。计算机专业的同学都应该接触了解过,这部分可以直接略过。没学过的同学也不要慌,可以去B站搜索相关视频,你搜关键词网络安全工程师会出现很多相关的视频教程,我粗略的看了一下,排名第一的视频就讲的很详细。 当然你也可以看下面这个视频教程仅展示部分截图:
学到http和https抓包后能读懂它在说什么就行。
2.网络基础和编程语言
3.入手Web安全
web是对外开放的,自然成了的重点关照对象,有事没事就来入侵一波,你说不管能行吗! 想学好Web安全,咱首先得先弄清web是怎么搭建的,知道它的构造才能精准打击。所以web前端和web后端的知识多少要了解点,然后再学点python,起码得看懂部分代码吧。
最后网站开发知识多少也要了解点,不过别紧张,只是学习基础知识。
等你用几周的时间学完这些,基本上算是具备了入门合格渗透工程师的资格,记得上述的重点要重点关注哦! 再就是,要正式进入web安全领域,得学会web渗透,OWASP TOP 10等常见Web漏洞原理与利用方式需要掌握,像SQL注入/XSS跨站脚本攻击/Webshell木马编写/命令执行等。
这个过程并不枯燥,一边打怪刷级一边成长岂不美哉,每个攻击手段都能让你玩得不亦乐乎,而且总有更猥琐的方法等着你去实践。
学完web渗透还不算完,还得掌握相关系统层面漏洞,像ms17-010永恒之蓝等各种微软ms漏洞,所以要学习后渗透。可能到这里大家已经不知所云了,不过不要紧,等你学会了web渗透再来看会发现很简单。
其实学会了这几步,你就正式从新手小白晋升为入门学员了,真的不算难,你上你也行。
4.安全体系
不过我们这个水平也就算个渗透测试工程师,也就只能做个基础的安全服务,而这个领域还有很多业务,像攻防演练、等保测评、风险评估等,我们的能力根本不够看。
所以想要成为一名合格的网络工程师,想要拿到安全公司的offer,还得再掌握更多的网络安全知识,能力再更上一层楼才行。即便以后进入企业,也需要学习很多新知识,不充实自己的技能就会被淘汰。
从时代发展的角度看,网络安全的知识是学不完的,而且以后要学的会更多,同学们要摆正心态,既然选择入门网络安全,就不能仅仅只是入门程度而已,能力越强机会才越多。
尾言
因为入门学习阶段知识点比较杂,所以我讲得比较笼统,最后联合CSDN整理了一套【282G】网络安全从入门到精通资料包,需要的小伙伴可以点击链接领取哦! 网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!