用来封装对象中的数据。
如果使用了属性的话,那么编译器就会主动编写访问这些属性所需的方法,此过程叫做“自动合成”。这个过程在编译期执行,点语法是编译时特性。
@synthesize 语法来指定实例变量的名字。
@synthesize testString = _testString;
@dynamic 关键字会告诉编译器不要创建实现属性所用的实例变量,也不要为其创建存取方法。而且,在编译访问属性的代码时,即使编译器发现没有定义存取方法也不会报错,它相信这些方法能在运行时找到。
@dynamic testString;
_testString //Use of undeclared identifier '_testString'
属性特质的设定也会影响编译器所生成的存取方法。属性特质包括:原子性、读写属性、内存管理语义(assign、strong、weak、unsafe_unretained、copy)、方法名(getter==…)。
如果想在其他方法里设置属性值,同样要遵守属性定义中所宣称的语义,因为“属性定义”就相当于“类”和“待设置的属性值”之间所达成的契约
比如在指定构造器中对成员变量的赋值
@interface TestObject ()
//虽然这个属性已经是只读性质,也要写上具体的语义,以此表明初始化方法在设置这些值时所用方法
@property(copy,readonly) NSString *testString;
@end
@implementation TestObject
initWithString:(NSString *)string {
self = [super init];
if (self) {
//用初始化方法设置好属性值之后,就不要再改变了,此时属性应设为“只读”
_testString = [string copy];
}
return self;
}
@end
“==”操作符比较的是两个指针本身,而不是所指的对象。应该使用 NSObject 协议中声明的“isEqual”方法来判断两个对象的等同性。
NSString *textA = @"textA";
NSString *textAnother = [NSString stringWithFormat:@"textA"];
NSLog(@"%d",textA == textAnother);// 0
NSLog(@"%d",[textA isEqual:textAnother]);// 1
NSLog(@"%d",[textA isEqualToString:textAnother]);// 1
在自定义的对象中正确复写“isEqual”"hash"方法,来判定两个方法是否相等。
如果 “isEqual”方法判定两个对象相等,那么其 hash 方法也必须返回同一个值。
比如下面这个类
@interface TestObject : NSObject
@property NSString *testString;
@end
@implementation TestObject
(BOOL)isEqual:(id)object {
if (self == object) return YES;
if ([self class] != [object class]) return NO;
TestObject *otherObject = (TestObject *)object;
if (![self.testString isEqualToString:otherObject.testString]) {
return NO;
}
return YES;
}
-(NSUInteger)hash {
//在没有性能问题下,hash 方法可以直接返回一个数
return 1227;
}
在继承体系中判断等同性,还需判断是否是其子类
相同的对象必须具有相同的哈希码,但是相同哈希码的对象却未必相同
自己创建等同性判断方法,无需检测参数类型,大大提升检测速度。就像“isEqualToString”一样。
(BOOL)isEqualToTestObject:(TestObject *)testobject {
if (self == testobject) {
return YES;
}
if (![self.testString isEqualToString:testobject.testString]) {
return NO;
}
return YES;
}
(BOOL)isEqual:(id)object {
if ([self class] == [object class]) {
return [self isEqualToTestObject:(TestObject *)object];
}else {
return [super isEqual:object];
}
}
有时候无需将所有数据逐个比较,只根据其中部分数据即可判明二者是否相等。
比方说一个模型类的实例是根据数据库的数据创建而来,那么其中可能会含有一个唯一标识符(unique identifier),在数据库中用作主键。这时候,我们就可以根据标识符来判定等同性,尤其是此属性声明为 readonly 时更应该如此。只要标识符相等,就可以说明这两个对象是由相同数据源创建,据此断定,其他数据也相等。
当然,只有类的编写者才知道那个关键属性是什么。
要点:不要盲目的逐个检测每条属性,而是应该按照具体需求制定检测方案
在对象上调用方法是 OC 中经常使用的方法。专业术语叫做“传递消息”,消息有名称(或叫选择子),可以接受参数,或许还有返回值。
在 OC 中,对象收到消息之后,究竟该调用哪个方法完全于运行期决定,甚至可以在运行时改变,这些特性使 OC 成为一门真正的动态语言。
给对象发送消息可以这样写:
id value = [obj messageName:parameter]
obj 叫做接收者,messageName 叫做 selector,selector 和参数合起来称为消息
编译器看到此消息后,将其转换为一条标准的 C 语言函数调用
void objc_msgSend(id _Nullable self, SEL _Nonnull op, …)
第一个参数代表接收者,第二个代表 selector(SEL是selector类型)
这是个“参数个数可变的函数”,”…“ 代表后续参数,就是消息中的参数
objc_msgSend 方法在接收者所属的类中搜寻其”方法列表“,如果能找到与 selector 名称相符的方法,就跳至其实现代码。若是找不到,就沿着继承体系继续向上查找,等找到合适的方法之后再跳转。如果最终还是找不到相符的方法,就执行”消息转发“(在之后解释)。
看起来,想调用一个方法似乎需要很多步骤。所幸 objc_msgSend 会将匹配结果缓存在”快速映射表“中,每个类都有这样一个缓存,若是后来还向该类发送相同的消息,那么执行起来就会很快了。
其他消息发送函数
//Sends a message with a simple return value to the superclass of an instance of a class.
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, …)
//Sends a message with a data-structure return value to an instance of a class.
objc_msgSend_stret(id _Nullable self, SEL _Nonnull op, …)
//Sends a message with a data-structure return value to the superclass of an instance of a class.
objc_msgSendSuper_stret(struct objc_super * _Nonnull super, SEL _Nonnull op, …)
刚才提到 objc_msgSend 找到合适的方法之后,就会”跳转过去“。之所以可以这样做是因为 OC 对象的每个方法都可以视为简单的 C 函数,原型如下:
每个类都有一张表格,selector 的名称就是查表时所用的 key
尾调用技术:编译器会生成跳转至另一函数所需的指令码,而且不会向调用堆栈中推入新的”栈帧“
对象在收到无法解读的消息之后会发生什么情况?如果在控制台中看到上面这种提示信息,那就说明你给对象发送了一条其无法解读的消息,启动了消息转发机制
消息转发分为两大阶段
第一阶段:征询接收者,能否动态添加方法,处理当前这个“未知的 selector”
//当未知的 selector 是实例方法时的调用
使用这种办法的前提是:相关方法的实现代码已经写好,只等着运行时加入类里面
第二阶段:运行时系统会请求接收者以其他手段处理与消息相关的方法调用,可细分为两小步。
//第一步:询问能不能把未知的消息转给其他接收者处理
若当前接收者能找到备援接收者,则将其返回,若找不到,则返回 nil
-如果返回一个对象,则运行期系统把消息转给那个对象,于是消息转发结束
如果返回 nil,执行第二步
手机扫一扫
移动阅读更方便
你可能感兴趣的文章