读书笔记:由NSString *引发的思考
本文大约2000字,将会讲述由NSString *引发的关于OC内存模型以及一些语法的简单思考。
!!!原创文章,转载请注明来源: pany.fun
#缘起
大概两年半前,当我在校招面试的时候,曾经有个面试官问了我这么一个问题
“能不能这么声明一个字符串变量(NSString str = @”xxx”;)??会有什么问题??”
拿到这个问题的时候我是懵逼的,从我们写第一行OC开始,我们都是这么去声明一个字符串变量的
NSString *str = @"xxx";
这行最最简简单单我们每天都要写无无无无数次的代码,好像还真的没有去质疑过为什么要这么写。我们一直被教育的都是OC里声明一个对象是要带星号的,因为它是一个指针,也因为这是学习OC所需要接受的最基本规则——语法。这就像我们学了几十年数学,1+1=2就是数学里最基本的规则一样,我们也极少去思考1+1=3会怎么样,只因为它是定好的规则。
所以我的回答是:不能这么声明,因为这是OC当中的语法规则,这么声明会无法编译通过,会报语法错误。(当然我还回答了诸如*str是一个指针,str是对象 巴拉巴拉)
当时面试官紧追不放,依旧追问为什么语法不允许呢?仿佛在追问我为什么要规定1+1=2。所以很自然的我的面试挂掉啦😇
回家后我在XCode里敲上了这行代码,当时编译器报出了类似 “哎呀,你看你怎么这么粗心,是不是忘了写星号呀,快让我帮你Fix一下吧” 的错误(当时确实报的这种搞笑错误,但是现在已经不是这么报了),然后点Fix,就会自动加上星号。当时即使搜索也依旧找不出什么,大多数人都是传授正确用法而不会告诉为什么(毕竟是这么简单的基础语法,猪都能学会而且不会用错。PS:我到现在也不知道为什么一定要1+1=2😅)。
后来我在一些开发群里分享了我的这次面试经历,很多人都一致认为这个面试官太奇葩,八成不是专门做iOS开发的然后又来面试iOS。然后我也就释然了,只怪运气不好。
#重识
直到今年过年回家的时候,因为高铁上时间太无聊,所以去回顾了一本书——《Effective Objective-C 2.0》
书的第一章是讲OC的起源的,也顺带稍微提了一下OC的一些语法。可能很多人都会直接跳过这种看着就不正经的章节(毕竟现在太多书都用前面几页来说这本书非常好,堪比圣经,老少咸宜……),原来初看的时候我还是看了的,但是好像并没记住啥。而这次回顾我却在这些片段里找到了当初面试题的解答。
书中有这么一段(摘自Effective Objective-C 2.0第1章第1条,页码2…真的是很靠前、很像不正经)
Objective-C语言中的指针是用来指示对象的。想要声明一个变量,令其指代某个对象,可用如下语法:
NSString *someString = @"The string";
这种语法基本上是照搬C语言的,它声明了一个名为someString的变量,其类型是NSString *。也就是说,此变量为指向NSString的指针。所有Objective-C语言的对象都必须这样声明,因为对象所占内存总是分配在“堆空间”(head space)中,而绝不会分配在“栈”(stack)上。不能在栈中分配Objective-C对象:
NSString stackString; // error:interface type cannot be statically allocated
这也就是前面提到的面试题的答案啦
#解惑
NSString *str = @"xxx";
什么是NSString *str ?
str是一个栈上的变量,它存放的内容是一个内存地址,这个内存地址是一个堆上的地址,这个堆地址上存放了一个字符串@“xxx”(实际上编译器还会做一些处理,把编译期就已经确定的字符串放到常量区,此处主要表达它不会存在栈上)。虽然我们不能在栈上开辟内存用来存放对象数据,但是我们可以在堆上开辟内存用来存放对象数据存,然后在栈上开辟内存用来存放对象的堆内存地址,这个栈上开辟的内存就是我们的指针变量。
NSString str;
什么是NSString str ?
这种声明应该只能称为一种尝试,它并不能真的运行起来。它在尝试声明一个对象,并期望在栈里开辟空间来直接存放。这是不被OC的内存模型规则所允许的(没错,又一个规则👊)。
还有些再稍微深入一点点的👇👇
#思考
我们更深一步,再来想想另一个问题
NSString *str1;
NSString * str2;
NSString* str3;
是的,你没有眼花,我也没敲错,这3行代码只是星号的位置以及变量名不同而已。首先这3行代码都能编过,毕竟XCode天天报bug也是蛮累的,一些最简单的语法错误干脆就自己默默处理不报啦。
虽然3行代码都能编(骗)过,但是在思想上体现出来的东西是完全不一样的,请记住正确的写法永远只有第一种。
首先OC是允许我们声明变量并申请开辟内存空间的,但是也加了限制,只允许我们申请在栈上开辟内存空间。因为栈内存是不需要我们手动来管理的(就是不需要清理),它们会在栈帧弹出的时候自动清理,而相反堆内存是需要我们手动管理的。所以加上限制还是很有道理的。
聪明的小伙伴们以及一些入坑不太久的小伙伴估计要提问啦,那堆上内存我们要怎么管理呢?好像开发了这么多年都没管理过啊…这个就又要怪XCode啦,它写了嘛!ARC自动的插入了内存管理相关的代码,这些代码就是管理堆内存的。
那么为什么使用NSString *str这种写法而不使用其它两种呢?我想大概是为了 统一代码要表达的思想。别忘了我们还有一些非对象类型的变量,例如CGFloat f = 1.5; 这行代码表示我们想在栈上开辟一个空间,最终这个空间会给外部一个CGFloat。
我们的代码中的对象类型的声明,无论是NSString *str,还是NSString str,它们要表达的就是在栈上开辟一个空间,这个空间最终能够给到外部的、我们需要真正关心的,就是一个字符串——NSString。只不过语法允许的是要再经过一次内存地址的转换的,所以带上星号来标明;而不被语法允许的是直接指向的,没有星号。但是我们最主要的诉求,还是NSString,OC并不希望我们去关注更多其它的复杂信息,OC的变量就是一个黑盒,你只需要知道它能给你想要的。这也就跟非对象类型的变量的表达形成了统一。
创作不易,转载请注明来源!pany.fun
本文链接:http://pany.fun/post/由NSString*引发的思考/