(每天更一题)群里收集的问答式小知识:

第一题,我们常说Objective C的消息(messaging)机制,那么Objective C的消息发送和c++的函数调用区别在哪里呢?

答: 主要区别就是 c++ 在编译期就会把函数调用转换为具体调用的哪块代码,而 OC 是在运行时才去找的。所以我们才能有那么多 runtime 黑科技

第二题,SomeClass* someObject = [SomeClass new]; 这行代码的内存分配在什么地方呢?

答: someObject 这个指针在栈区,指向的 object 在堆区啦。如果 someObject 有个 int 类型的属性,这个属性也是放在堆区的

第三题,我们知道最好在.m而不是.h里import,为了避免暴露过多细节、防止循环引用,加快编译等。但有哪些情况必须在.h中引用?这些情况下要是循环引用了咋办呢?

答:必须在 .h 中引用的情形包括继承父类,必须引用父类的 .h;实现 protocol,必须引用 protocol 的 .h。如果造成循环引用了怎么办呢?首先,两个类肯定不能互相继承,父类没有这问题。如果是 protocol,可以把 protocol 单拿出来放到一个 .h 里,跟任何类都不在一起……

第四题,常量的命名规范是什么样的?

答:第四题 就是一般情况放在 .m 里,用 k 开头。但是如果要暴露在 .h 里,就要用类名做前缀了,免得重复~

第五题,咱们平常定义变量都会static const CGFloat……这样,那不加static可以吗?

答:加 static const,会在编译优化的时候直接替换,跟 #define 差不多(但比 #define 有很多好处)。加 static 的好处是,可以让这个常量的范围限制在这个 .m 之内。如果不加 static,会在全局创建一个符号,如果有两个重名的就该报错啦。

第六题,假设我们为一个 enum 写了 switch 语句,switch(someEnum) { case xx: … break; ….} 这种,如果对 enum 的每一个取值都加上了 case 来处理,那么还要写 default 分支吗?为啥?

答:不写default较好。因为这样,如果后面增加了enum选项,会立即报一个warning,让你不会忘了处理它。一般即使不需要处理的选项,也要把它写出来,也是同样的原因。如果是每个分支直接return的话,最好在函数结尾加一句return。

第七题,使用enum的时候,我们经常会把几个选项或起来。那么接收方拿到这个或起来的结果,该怎么使用呢?如何知道里面或了哪几个选项呢?

答:与上对应的选项 if (options & SomeOption) 即可。唯一要注意的就是不要习惯性地写 == YES。因为与出来的结果可能为二进制的 10、100 等。

第八题, 你用过@dynamic吗?它有什么用?一般是什么情况下用的?

答: 其实这个 @dynamic 的意思就是跟系统说,不要创建 property 对应的成员变量(就是一般的 _someProperty),也不要自动生成 get/set 方法,同时不要报错,到在运行时我自己会来添加 get/set 方法。比如像 CoreData 的对象,有些属性并不是用 _someProperty 存起来的,而是从数据库里读出来的。那么就不需要系统默认的 getter、setter,而是在运行时生成~

第九题, @property (nonatomic, copy) NSMutableArray* array; 这样写有啥问题~ 就酱=w=

答:就是 NSMutableArray 一 copy 就会变成一个不可变的 NSArray。所以这个属性的 setter 就会导致,实际存起来的对象是不可变的。一旦调到 NSMutableArray 特有的方法比如 addObject 等,会马上 crash。非常危险。

第十题,如何重写一个atomic属性的getter,setter呢~大家自己写写看吧_
答:就把self锁上就可以了~注意如果你只重写getter或者setter就会报错,因为如果getter或setter不能锁住同一个东西的话就没法实现atomic鸟。

第十一题,在现代的 Objective-C 中,我们经常使用 . 语法来访问和修改属性~ 比如 self.name = @"hamster"; 很少再使用下划线的语法了。但是,请问哪些情况下必须使用下划线语法,而不能使用点语法呢?~
答:
第一种,getter、setter 里必须用下划线语法,原因是显而易见的,不然会无限递归.
第二种,init 方法里必须用下划线语法。这个原因好像没有同学说,主要是因为,子类可能会重写 setter 方法。如果 init 里用点语法,父类的 init 方法里会不留神调到子类的 setter,造成意料之外的乌龙,还可能会有危险。
第三种,dealloc 里不要用任何点语法~ 一方面还是子类父类的问题,dealloc 是 init 的逆过程,问题同理。另一方面,重写的 getter、setter 里可能会有在 dealloc 阶段来做不安全的行为,要避免触发它们。

第十二题:我们都知道category 可以给一个类增加新的方法,尤其是系统的类,我们改变不了。那么,可以给一个系统的类增加新的属性吗?category 新加的方法可以使用到这些新的属性吗?
答:
1.雷纯峰的: http://blog.leichunfeng.com/blog/2015/06/26/objective-c-associated-objects-implementation-principle 主要讲解Associated Objects 的实现原理和内存管理策略
2.sunny的: http://blog.sunnyxx.com/2014/03/05/objc_category_secret/ 主要讲解catogry拓展方法的实现原理
3.美团网的: http://tech.meituan.com/DiveIntoCategory.html 更深入讲解category的实现原理

第十三题:大家如果看了它的API,会发现跟kvc的key object机制很像。那么,能说一说 associate object 和 kvc 机制的区别在哪里吗?~
答:这个问题想强调的就是,associated object语法看着像key value,但实际它的key机制跟我们平常习惯的有一点不同,我们习惯的比如NSDictonary,两个key只要isEqual方法返回YES就认为是相同的key,而associated object 的key是类似用==来判断的,不是要求内容相同,而是指针地址必须完全一致,所以我们一般把key存到一个静态变量里~

第十四题:当我们写下@property时我们在写什么?

答:1. 生成一个叫_foo的成员变量 2.声明 foo,setFoo 两个getter setter方法 3.默认实现这两个方法
(1)实际上以前第1步是要手动声明,第3步是要写一句@synthesize的,不过现在一般情况不用写。只有你把getter setter都重写的情况下,编译会报错,就说没有_foo这个成员变量呀?这时候你还是要写一句@synthesize
(2)前天我问大家category怎么加property,答案是关联对象。一位同学说,不对呀,category里可以写@property呀?是可以,protocol里也可以。然而,这里写的@property,相当于只有第2步,没有第1 3步。所以,它实际上是只声明了getter setter,而没有真正开辟一块存储对象的空间,并把getter setter与这个对象绑定。相当于只挂了门牌号,没有房子。只有手机号,没有手机。而关联对象做的是什么?做的是第1步。第1步做好,第2 3步你可以按自己的需要去做。第一部分就讲这么多。
(3)下面讲第二部分,weak strong assign copy。前三个是一组。想必大家都知道weak strong有什么区别,对于OC中的对象,有一个“引用计数”的概念。比如,我声明了一个临时变量,Foo* foo =new Foo(); 那么系统就生成了一个Foo对象,并用foo这个指针指向它。这时候这个对象的引用计数是多少?是1,因为foo这个指针持有它。下面我把它赋给一个strong的属性,self.foo=foo; 现在引用计数是多少?是2。然后这个函数结束了。这时候引用计数变成多少?变成1。因为foo这个临时变量被销毁了,所以引用计数减一。那么,如果这个属性不是strong,而是weak呢?self.foo=foo,仍然把这个指针的值保存在了self.foo里。只不过区别在于,引用计数不加一。所以引用计数仍然是1。那么,函数结束时发生了什么呢?像上面一样,foo这个临时变量销毁时,引用计数要减一。这时候引用计数就变成了0。对象就被系统回收了。以上就是strong 和weak的区别。
(4)weak类型的属性,在对象被回收的时候,会做一个特殊处理:把这个属性置为nil。以后我们可以讨论下这个特殊机制怎么实现的,这应该算是个比较高阶的面试题。之后我们再访问self.foo,它的值就是nil了。那么,缺了这一步会怎样,有哪些危险呢?这种情况下,指针仍然指向那一块内存。然而,管内存管理的哥们,并不知道这块内存有人用。当我们使用这个属性的值,就像刻舟求剑一样,就像一个人坐过一次火车,下次他还去同一个站台坐火车,然而火车开往哪个终点,很可能完全不一样了。这种情况,非常危险,很可能引起crash。
主要原因在于,
1. 这块内存后来又被分给了其他对象,写上了别的数据。当我还取这个属性时,那块内存已面目全非,根本取不出一个完整的foo了。于是系统就果断crash。
2. 就是相反地,别的对象已经占用了这块空间,那么我再给这个 foo 赋值,又会把别的对象已有的数据写坏。那么一旦用到那个对象,必定又要出问题。
3. 对象销毁的时候,也可能会 crash。如果大家切到 MRC 模式,new 出一个对象,然后 release 两次,就要 crash。这能解释孙总昨天的问题,为何他写了一个 assign 的属性,然后 self.foo = [Foo new]; 就会 crash。而 Foo* foo = [Foo new]; self.foo = foo; 就不会 crash,因为此时局部变量 foo 还引用着这个对象,引用计数还是 1,表面还是正常的。当然这个函数结束之后,局部变量被释放,也就进入不正常的模式了。以上能告诉我们的就是,一定不要用 assign 修饰一个对象类型,这样很不安全。关于刚才说的引用计数,大家可能会有一个疑问,比如 Foo* foo = [Foo new]; return foo; 当我 return 的时候,局部变量 foo 不是应该销毁嘛,那接收方怎么接到的?这又是一个比较难一点的面试题…… 我们以后可以继续讨论 O O
4.昨天我们讲了 strong、weak、assign,下面我们讲一下 copy ……copy 跟前面三个含义不一样,它表示的是 setter 方法会对参数进行一次拷贝,然后再储存起来。这里提一个小问题:既然如此,为何把这个 copy 关键字跟 strong、weak、assign 它们放在一组呢?为何不设计成 copy 能分别跟 strong、weak 组合呢?系统有一些常见的类可以 copy,比如 NSString、NSArray、NSDictionary。而也有一些不能,比如 UIView、UIViewController。想知道一个类能不能写 copy,只需查看它是否实现了 NSCopying 协议.而咱们要想让自己的类支持 copy ,也需要实现这个协议,并且实现其中唯一的方法 - copyWithZone
5.关于 copy 方法的实现,还有两点需要注意的:1. 实现时一定要是深拷贝,不然就没有实现 copy 的语义,让使用者迷惑,造成意外的结果 2. 注意几个 mutable 的类不能用 copy 修饰,这个咱们之前讲过。无论是 NSArray 还是 NSMutableArray,只要 copy 出来的都是不可变的,只要 mutableCopy 出来都是可变的
6.atomic 和 nonatomic...如果不写这两个关键字,系统会默认选择哪一个呢?在 iOS 里默认是 nonatomic,OSX 里默认是 atomic。这是因为 atomic 更安全,但耗性能。nonatomic 相反。在手机上性能比较紧张, 电脑上就可以豪爽一点,所以是这么设定的。注意两个点:1. atomic 是否能保证线程安全?答案一定是不能。线程安全是没有这么简单的,需要在逻辑上按自己的需求进行限制,而 atomic 只是做了一点小小的防护而已。

第十五题:对于 NSString 这个类,判断两个实例相等时,==、isEqual 方法和 isEqualToString 方法有什么区别?我们平常用的时候应该用哪一个呢?

答:对于 NSString 来说,isEqual 跟 isEqualToString 的区别在于,前者还会判断是不是同一个类。如果你能确定是两个 string 相互比较,调用 isEqualToString 效率要高一点儿~ 就这样---参考书),但实际isEqualToString 也会判类型,所以NSString的isEqual == isEqualToString

你可能感兴趣的:((每天更一题)群里收集的问答式小知识:)