2.对象、消息、运行期
第6条 理解属性这一概念
以上也是Category 为何不能添加成员变量的原因,更多参考:
http://quotation.github.io/objc/2015/05/21/objc-runtime-ivar-access.html
http://www.jianshu.com/p/2d63477c4d46
-
__unsafe_unretained 跟 assign 类似,只不过它是修饰对象的,而 assign 是修饰基本类型的。两者修饰的属性在释放时,都不会自动将属性值置为 nil。
-
修饰NSString 的时候最好用 copy,原因如下:
@property (retain) NSString *name;
- (void)xxx
{
NSMutableString *mName = [@"mName" mutableCopy];
self.name = mName;
//接下来,如果我修改了 mName(比如在 mName上拼字符串),那么self.name也会跟着修改,
//但开发者可能会认为 self.name 只是个 NSString 的不可变字符串,不应该被修改,但其实却是个 NSMutableString。
//若用了copy就不会发生这种情况。
}
- 如果属性用 copy 修饰了,在 init 方法里要对应的用 copy 赋值,如:
@property (copy) NSString *name;
- (instancetype)initWithName:(NSString *)newName
{
self = [super init];
if (self) {
_name = [newName copy]; //要用copy赋值
}
return self;
}
- 如果设置了
readonly
,可以只在init
方法里赋值(如下代码),其他地方再调用self.name = xxx
赋值的话,就会报错了。
@property (copy, readonly) NSString *name;
- (instancetype)initWithName:(NSString *)newName
{
self = [super init];
if (self) {
_name = [newName copy];
}
return self;
}
-
atomic 和 nonatomic 的区别
第7条 在对象内部尽量直接访问成员变量
- 在对象内部,读取的时候尽量用成员变量(惰性初始化除外,如下代码),赋值的时候尽量用属性。
原因:- 读取时,直接访问成员变量,速度会比较快,
- 但是如果赋值时,如果直接访问成员变量就绕过了内存管理机制,比如 copy 修饰了一个属性,若直接给成员变量赋值,就会 retain 而不会 copy 了。
- 直接访问成员变量,不会触发 KVO。
- 用属性,方便在 setter 方法里打断点调试。
- 在 init 和 dealloc 中,尽量直接使用成员变量读写数据。
// 惰性初始化
- (Student *)student
{
if (!_ student) {
_ student = [Student new];
}
return _ student;
}
第8条 理解“对象等同性”这个概念
NSObject 的判断俩对象是否相同的方法 isEqual:
和 - (NSUInteger)hash
的关系:
若用 isEqual:
判断俩对象相等,则 hash
一定相同,但 hash
相同,isEqual:
判断不一定相等。
- hash函数如何编写(未读懂)
-
NSArray
调用isEqual:
的实现原理: 先判断个数是否相同,若相同,每一项调用isEqual:
判断是否相同。 -
NSSet
(集合)是一种哈希表,运用散列算法,查找集合中的元素比数组速度更快,但是它没有顺序。集合中的数据是唯一的,比如我存入俩相同的字符串,集合里只会有一个。
第9条 以“类族(类簇)模式”隐藏实现细节
类族一般没有 init 方法,只有一些工厂方法,它只是一个抽象的基类,中间的实现过程都隐藏了。例如UIButton,初始化没有init,而是用 buttonWithType:
,其内部可能是每个类型声明了一个新的类(只是举个UIButton的例子,UIButton实际并不是类族)。
collection类(集合类)一般都是类族,包括 NSArray、NSSet和NSDictionary等,NSString、NSNumber等也是类族,以下结果返回的是NO:
NSArray *arr = [NSArray arrayWithObjects:@"abc", nil];
if ([arr isMemberOfClass:[NSArray class]])
{
NSLog(@"YES");
}
else {
NSLog(@"NO");
}
第10条 在既有类中使用关联对象存储自定义数据
#import <objc/runtime.h>
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /** 和 assgin 等效 */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /** strong、nonatomic */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /** copy、nonatomic */
OBJC_ASSOCIATION_RETAIN = 01401, /** strong、atomic */
OBJC_ASSOCIATION_COPY = 01403 /**< copy、atomic */
};
//设置关联
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//获取关联的对象值
id objc_getAssociatedObject(id object, const void *key)
//移除指定对象的全部关联对象
void objc_removeAssociatedObjects(id object)
UIAlertView 的 delegate 传值的时候,如果传一个 tag 还好说,如果是传多个参数,要么用一个子类继承于 UIAlertView,设置多个属性,要么就用关联对象。
第11条 理解 objc_msgSend的作用
C语言是静态绑定,若不考虑内联(inline),当你调用一个函数的时候,编译的时候编译器就知道存在这个函数了。
// OC 中的语句
id retureValue = [obj messageName:parameter];
// 转成C语言后的原型
id retureValue = objc_msgSend(obj, @selector(messageName:), parameter);
objc_msgSend 方法会在 接收者(obj)中寻找方法列表,如果找不到,就在从父类依次往上找,一直找到NSObject,如果还没找到,就开始消息转发。这个过程中,objc_msgSend并非每次都会查找,每个类会有一个缓存,这样大大提高了查找效率。
objc_msgSend_stret
objc_msgSend_fpret
objc_msgSendSuper
...
还有些内容太抽象,此处省略。
第12条 理解消息转发机制
上条介绍的消息传递,传递过程中找不到对应的方法,就会执行消息转发。消息转发分为两大阶段:
- 动态解析:询问接收者所属的类,能否动态添加方法。
- 完整的消息转发:系统请求接收者用其他方式处理消息。又分为俩阶段(就是俩回调方法),首先,先看下其他类能否处理该消息,若没有,系统会把与消息有关的全部细节封装给NSInvocation对象中,再给接收者,最后一次机会。
代码示例,建了一个EOCAutoDictionary的类,可以用该类任意存值。如:
ECOAutoDictionary *a = [ECOAutoDictionary new];
a.data = [NSDate date];
NSLog(@"a.data-------%@", a.date);
第13条 用“方法调配技术”调试“黑盒方法”
method swizzling,一般用于打印一个系统方法日志,最好不要滥用,否则会让代码不易被读懂。
第14条 理解“类对象”的用意
NSObject
类在 oc 中的定义是:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
举个例子:
NSString *str = @"abc";
看下面的图的定义,在这个例子中,str 的 isa 指针是指向 NSString,而 NSString 也有个 isa 指针,这个指针指向的类就是 元类,说明 NSString 是该元类的一个实例对象。NSString 的类方法就是在元类里定义的。
NSProxy
简单用法:http://ios.jobbole.com/87856/NSProxy
的作用:负责将消息转发到真正的target的代理类。举个例子,你想要卖一件二手物品,但是你并不想直接跟卖家接触(直接向target发消息),这时你去找了一个第三方,你告诉这个第三方你要买什么、出多少钱买、什么时候要等(向代理发消息),第三方再去跟卖家接触并把这些信息转告卖家(转发消息给真实的target),最后通过第三方去完成这个交易。