在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
第1条 了解Objective-C语言起源这一章先是澄清了OC的消息机制和函数调用机制的区别。C++的函数调用机制在涉及到多态的时候也是动态绑定的,而OC只是普通的函数调用也是动态绑定的,也就是运行时查找应该执行的函数指针。 接着介绍了OC中的对象都是保存在堆上的,非对象类型都是保存在栈上的。 第2条 在类的头文件中尽量少引入其他头问题介绍了通过@class关键字声明类,可以将类的定义文件引入推迟到.m文件中,这样头文件的互相依赖就会减少,加快编译速度。另外在使用protocol的时候,如果类和协议互相引用,使用@class是优雅的解决方法。 代码示例:
第3条 多用字面语法,少用与之等价的方法介绍了字符串,数组,字典,数组对象等字面变量的语法,推荐使用的原因主要是简洁。 代码示例:
第4条 多用类型常量,少用#define预处理指令因为预处理指令仅仅是简单的替换,不能让编译器检查语法。因此包括C和C++语言中都一样不推荐使用#define。文中建议使用static const方式声明常量,如果是全局的使用extern const。前者表示这个常量的定义仅仅在本编译单元有效,后者表示这个常量是全局的,其定义在全局符号表中,在链接阶段会找到的。
const NSString * 和NSString * const const NSString *表示字符串的内容是不能改的,实际上这么写没有意义,NSString本身就是不可变对象。NSString * const 表示变量只能指向这个字符串,不能重新给变量赋值。
因此使用全局变量声明的时候应该写extern NSString * const str = @“XXX”; 第5条 用枚举表示状态、选项、状态码前边介绍了C++11的enum新特性。但是在OC中应该使用NS_ENUM和NS_OPTION来定义枚举,前者的枚举值是累加的,后者的枚举值是位移的,可以用于位或。 代码示例:
另外作者建议在switch语句中使用枚举值时,不要使用default,这样如果枚举多了一项,会得到编译器的警告。感觉这个看团队编码规范,因为有时候仅仅需要处理少数状态,其他的状态大可使用default处理,而新增一种状态的时候,往往是要增加逻辑的,编程者一般也会考虑到。 第6条 理解“属性”这一概念当我们写下@property的时候,编译器会合成getter和setter方法,还有一个实例变量。对于类内部的时候,也可以不使用属性,直接定义实例变量。
但是这样定义的变量是"private"的,也就是只能类内部使用。OC这么做是为了ABI(应用程序二进制接口)兼容性。如果将示例变量暴露给外边,如果类实例变量改变了,就会破坏ABI的兼容性(即使外部使用的那个实例变量没发生变化,也会有问题,因为编译器靠偏移量找实例变量)。 而使用属性,就等于通过函数访问实例变量,改变了实例变量,这个类本身肯定是要重新编译的,这样通过函数访问实例变量的过程也会得到更新,这样对外的接口就是稳定的(使用这个类的部分不用重新编译)。 属性的标识符(特质) atomic,nonatomic 因为属性的访问会转化成函数的调用(严肃的说是消息的发送),因此atomic能保证属性访问或者设置的原子性,atomic是属性的默认设置,如果在编程的时候能保证属性访问不是多线程的,应该加上nonatomic,因为在iOS上atomic的开销还是比较大的。 readwrite,readonly 属性默认设置是readwrite的,如果希望属性是只读的,使用readonly,编译器将只合成getter方法。一个使用的技巧是属性在接口中只读,但是在内部通过扩展重新指定为readwrite。(直接访问示例变量也可以达到目的,见下节) assign,strong,weak,unsafe_unretained,copy assign标量类型的属性设置,strong,对象类型属性设置,weak,表示不增加引用计数,并且当对象销毁的时候能自动置为nil,unsafe_unretained和weak作用一样,但是不能自动置为nil,只有当weak不能用的时候才用这个(有些foundation的类不支持weak);copy用于有可变子类的对象,如NSArray,NSDictionary,NSString等。 还有一个知识点就是@dynamic,告诉编译器不要合成实例变量,只合成getter,setter方法。这在Core Data中常见,因为数据是动态产生的。 第7条 在对象内部尽量直接访问实例变量第一,在初始化的时候,应该总是直接访问实例变量,因为子类可能会覆盖setter方法。 但是如果初始化的是基类的不可见变量,只能通过属性的方式。 第二,懒惰初始化。如果有这样的设计,应该总是使用属性而不是直接访问实例变量,只有一种情况例外:就是想释放资源的时候。(更合理的方式是,使用了懒惰初始化同时提供一个释放资源的方法) 第三,如果想触发KVO应该使用属性方式。 可以看出这个话题并没有严格统一的答案,应该在编码时根据语义来决定。 第8条 理解“对象等同性”这一概念什么叫对象等同? 使用==判断两个对象是否等同,表示比较的是两个对象的指针是否指向同一个地址。一般情况下,地址相等就意味着两个对象等同。
更多情况下,对象是否等同是和语义相关的。比如
str1和str2应该是等同的,虽然他们的地址不同。 比较对象是否等同应该使用isEqual来决定,isEqual是NSObject协议中的一个方法。NSObject中默认的实现就是比较地址。 如果想要实现自己语义的等同,需要实现NSObject协议中以下两个方法:
等同的语义是:hash函数返回同一个值,且isEqual返回YES; 理由是这样的:如果把一个对象放入一个NSMutableSet,容器是先判断hash值的,如果已经存在该hash值的对象(这些对象一般会保存到一个数组中),会遍历这些hash值相同的对象判断是否相等,如果相等,就认为对象重复,不会添加到Set中。因此isEqual满足的前提应该是hash值相等,如果不这么做,使用HashTable相关的集合类型会发生错误。同样,上边的例子也说明了hash值相等的对象isEqual可以是NO。 一个面试问题,一个类的hash函数,这样写会有什么后果?
答:会极大的影响在Hash Table类型的容器中的效率,这包括NSMutableSet,NSDictionary的key等。因为所有的对象都返回一个hash值,那么所有的对象按照数组方式存放,Hash Table的优势丧失殆尽。 一个常用的设计模式:
实现自己类别的isEqualToXXX(就像NSString的isEqualToString:一样),然后在实现isEqual:的时候判断如果传入的对象是自己的类别,就使用自己的比较方式,如果不是就直接调用基类的比较方式。 最后,提示了如果将可变对象放入可变容器,尤其是Set类型的容器可能会产生的Bug。可能会造成Set容器中有两个相同的元素。 第9条 以"类族模式"隐藏细节就是一个简单工厂和抽象基类的结合,不过OC中没有抽象类的概念。举个UIKit中的例子:
实际上返回的是一个UIButton的子类,然而一般情况下使用者不关系子类的具体实现,只要用基类的接口就可以完成工作了。 使用这种模式需要针对每种子类定义一套类型常量,或者字符串常量,也就是上边参数的type。 如果不使用运行时,基类的实现中(.m文件)中将需要包含子类的头文件,形成不符合设计模式的依赖。这个要根据实际情况看看是否可以接受。一个直接后果就是不能不修改基类源代码扩展。 第10条 在既有的类中使用关联对象存放自定义数据关联对象的技术是通过runtime的API在不侵入继承体系的情况下给类添加成员数据。但是滥用也容易破坏代码结构,写不好还会引起内存管理的问题。
关联对象使用指针的地址作为key,因此要静态定义一个指针,即使不给指针赋值也可以,因为并不用到指针指向的内容。 这是关联对象的时候,使用内存管理语义的,和@property的语义一致。 关联对象常常和Category同时使用,用于给没有源码的类或者不想侵入的类添加属性。 作者建议,只有在没有其他更好的办法的时候才使用关联对象技术,因为容易引入难以查找的bug。 第11条 理解objc_msgSend的作用对于Objective-C的消息机制,另外成文,参考官方文档和我的博客 第12条 理解消息转发机制同11 第13条 用“方法调配技术”调试“黑盒方法”又叫method swizzling,就是在运行时将方法替换掉,实现一些“黑魔法”效果。
上边一段代码可以在将NSString转换成小写的时候打印日志。这里并没有任何关于NSString的lowercaseString函数的知识,就可以给它添加功能。 通常方法的交换在一个类的+load方法中,确保在使用前已经调换好,并且要使用dispatch_once保证线程安全。 method swizzling可以用于统一的打点统计,输出DEBUG信息等。有一种设计模式叫做面向切片编程,就是和主业务垂直的辅助业务,可以通过在程序架构中某一层注入的方式来解除耦合。method swizzling是面向切片编程的武器之一。 第14条 理解“类对象”的用意上图是一个书中的例子,一个SomeClass的类继承自NSObject,其类对象和元类对象结构如图。 一个类的信息(如类的实例方法都有啥,实例变量都有啥等)保存在“类对象”的实例中,每一个类都有一个类对象,并且是单例的。类对象是Class类型,也是一个OC类,它也有信息,保存Class类的信息的对象叫做元类对象,元类对象的数据已经很固定,就没有自己的类对象了。最终继承体系闭合在NSObject的元类对象上。 isKindOfClass判断一个实例是否在一个继承体系上,isMemberOfClass精确的判断了这个对象是否是某个类的实例。 以下两种写法有什么区别?
1)如果obj是XXXClass的子类实例,第一个表达式返回YES,第二个返回NO。 2)如果obj是一个Proxy,继承自NSProxy的,也就是通过消息转发方式代理了其他对象,有可能两个表达式返回的值不同。具体情况看isKindOfClass的转发情况。 |
请发表评论