前言
OC是面向对象的变成语言,“对象”就是“基本构造单元”。而对象之间传递数据并执行任务的过程,就是“消息传递”。对于OC这种消息结构的语言理解“消息传递”的理解,非常有必要。
当应用程序运行起来,围棋提供相关支持的代码叫做"Object-c运行期环境"(Objective-C runtime), 它提供了一些是得对象之间能够传递信息的重要函数,并且包含创建类实例所用的全部逻辑。理解运行期环境中各个部分协同工作的原理,你的开发水平将会进一步的提升。
第一、关于属性(property)
1.property、 synthesize、 dynamic
参考一下框图已经非常详细了。
2.在对象内部使用访问实例变量
笔者建议在读取实例变量的时候采用直接访问的形式,而在设置实例变量的时候采用属性来做。
直接访问实际变量优点
1.由于不经过OC的“方法派发”步骤,所以直接访问实例变量的速度比较快。
不会调用“设置方法”,绕过了相关属性的“内存管理语义”,比如:ARC下直接访问一个声明为copy的属性,那么并不会copy该属性,只会保留新值,释放旧的值。
不会触发“KVO(key-Value Observing, KVO)”通知。这样做是否会产生问题还需要取决于具体的对象的行为
通过属性来分文有助排查错误,因为可以给“获取方法”和“设置方法”中新增断点,监控调用者和调用时机。
注意点
1.在初始化方法中应该如何设置属性值,因为子类可能会覆写该方法。为了防止初始化父类用set方法调用到不确定子类的set的方法,所以在初始化方法和delloc方法中,总是通过实例变量来读写数据
2.当存在着懒加载的情况,那么一定需要使用属性的方法来读取数据。
第二、关于“对象等同性”
== 比较出来的是指针的比较,学习C语言指针的话,对此将非常容易理解。
NSString *fir = @"number:123";
NSString *sec = @"number:123";
BOOL equalA = (fir == sec); // NO
BOOL equalB = [fir isEqual:sec]; // YES
BOOL equalC = [fir isEqualToString:sec]; //YES
从以上的例子可以看出,==是比较指针,isEqualToString是NSString提供自己独特的等同性的判断方法。并且调用该方法比isEqual方法快。后者还需奥执行额外的步骤。
1.关于isEqual方法
如果isEqual的方法判定两个对象相等,那么其hash方法也必须返回同一个值,如果两个对象的hash方法返回同一个值,但是"isEqual:"方法未必会认为二者相等。
如果经常需要判断等同性,那么可以自己创建等同性的方法,提高性能。正如 isEqualToString一样
对于NSArray对象中,如果对应位置上的对象均相等,那么这两个数组就等同,称之为“深度等同性判定”。当然比如从数据库中创建出来的对象代用Primary Key那么我们只需要比较readonly的Primagry Key是否相等来判定等同,不需要检测所有的字段。所以只有类的编写者才可以确定两个对象实例在何种情况下可以被判定为相等。
2.工厂模式和Class Cluster
类族模式可以把所有实现的细节隐藏到一套简单的公共接口的后面。
系统框架中经常使用类族
从类族的公共抽象基类中继承子类是要当心。
3.技巧篇
在既有类中使用关联对象存放自定义数据,如下代码不过需要注意该部分被包含在头文件 中
import <objc/runtime.h>
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
@implementation Person (Property)
//使用的是不透明的指针,一般选用静态全局变量
static NSString const *kcountryKey = @"PersonCountryKey";
static NSString const *kdetailAddressKey = @"PersonDetailAddressKey";
@dynamic country, detailAddress;
-(NSString *) country{
return objc_getAssociatedObject(self, &kcountryKey);
}
-(NSString *)detailAddress{
return objc_getAssociatedObject(self, &kdetailAddressKey);
}
-(void) setCountry:(NSString *)country{
objc_setAssociatedObject(self, &kcountryKey, country, OBJC_ASSOCIATION_COPY);
}
-(void) setDetailAddress:(NSString *)detailAddress{
objc_setAssociatedObject(self, &detailAddress, detailAddress, OBJC_ASSOCIATION_COPY);
}
@end
@interface Person (Property)
@property (nonatomic, readwrite, copy) NSString* country;
@property (nonatomic, readwrite, copy) NSString* detailAddress;
@end
如何应用,如下应用,拓展了person的属性,并且能很好的处理了多个AlertView的判断问题。但是采用该方案也需要注意:block可能要捕获(capture)某些常量,这样也许会造成"保留环"。笔者建议创建一个UIAlertView的子类,把块作为子类的属性。这种做法要比关联好。
#import "ViewController.h"
#import "Person+Property.h"
#import <objc/runtime.h>
typedef void (^AlertBlock)(NSInteger buttonIndex);
@interface ViewController ()<UIAlertViewDelegate>
@end
@implementation ViewController
static NSString* const kAlertViewKey = @"MyAlertViewKey";
- (void)viewDidLoad {
[super viewDidLoad];
Person* xiaoMing = [[Person alloc] initWithFirstName:@"Ren" lastName:@"XiaoMing"];
NSString* fullName = [NSString stringWithFormat:@"你输入的名字是:%@", xiaoMing.fullName];
NSLog(@"%@", fullName);
UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@"请确认" message:fullName delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alertView show];
AlertBlock nameBlock = ^(NSInteger buttonIndex){
NSLog(@"姓名确认中你选择了%ld", buttonIndex);
};
objc_setAssociatedObject(alertView, &kAlertViewKey, nameBlock, OBJC_ASSOCIATION_COPY);
xiaoMing.country = @"China";
xiaoMing.detailAddress = @"Shen Zhen";
NSString* address = [NSString stringWithFormat:@"小明居住的地址是:%@ %@", xiaoMing.country, xiaoMing.detailAddress];
NSLog(@"%@", address);
UIAlertView* addressAlertView = [[UIAlertView alloc] initWithTitle:@"请确认" message:address delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[addressAlertView show];
AlertBlock addressBlock = ^(NSInteger buttonIndex){
NSLog(@"地址确认中你选择了%ld", buttonIndex);
};
objc_setAssociatedObject(addressAlertView, &kAlertViewKey, addressBlock, OBJC_ASSOCIATION_COPY);
}
#pragma mark - UIAlertViewDelegate
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
AlertBlock tempblock = objc_getAssociatedObject(alertView, &kAlertViewKey);
tempblock(buttonIndex);
}
第三、关于objc_msgSend
1. 概述
- 用OC的话来说,这玩样叫做"传递消息(pass a message)"。消息有"名称(name)"或者"selector",可以接受的参数,而且可能还有返回值。
- C语言是“静态绑定”,也就是说,在编译期就能决定运行时所调用的函数。如果不考虑"内联"的情况,那么编译期在编译代码的时候就知道了程序中存在的函数,并且将其硬编址,这个进行过单片机开发和仿真过的,对此将深有体会,调用的函数最终将映射到某个硬件地址。
- OC语言使用"动态绑定",所需要调用的函数知道运行期才能确定。
- 对于OC语言而言,如果想某个对象传递消息,那就会使用动态绑定的机制来决定需要调用的方法。在底层,所有的方法都是普通的C语言函数,然而对象接收到消息之后,究竟该调用哪个方法则完全取决于运行期,甚至可以在程序运行时改变,这些特性使得OC成为一门真正的动态语言。
id returnValue = [someObject messageName:parameter];
- 其中someObject就是消息的接受者receiver, messageName叫做selector,而参数parameter+selector合起来就称为"消息"
- 将其转化成一条标准的C语言函数,原型如下:
void objc_msgSend(id self, SEL cmd, ...)
简单点归纳的来说,消息由接收者、selector以及参数构成的。给某个对象“发送消息”也就是相当于在该对象上“调用方法”。
发送给对象的消息都要经过"动态消息派发系统",来处理,该系统会查出对应的方法,并执行代码。
2. 消息转发的过程
1.首先通过该方法来询问是否能处理该消息
<PS:如果想通过运行期动态插入方法,那么就需要考虑在这里插入该方法了CALayer里面就应用类似的方法实现。于是开发者能够向其中新增加自定义的属性,而这些属性的存储工作由基类来完成>
+(BOOL) resolveInstanceMethod:(SEL) selector;
+(BOOL) resolveClassMethod:(SEL) selector;
2.如果不能处理,接收还有第二次机会能处理未知的子,运行系统会问它,能不能把这条消息转发给其他接收者。
-(id) forwardingTargetForSelector:(SEL) selector;
如果当前接收者找到了可以处理该部分的对象的则将它返回(备援接收者), 否者返回nil.
第四、OC业界的黑魔法
1.原理概述
OC的黑魔法通常是用来调试“黑盒方法”。
类的方法列表会把选择子的名称映射到相关的方法实现之上,使得“动态分配系统”能够据此找应该调用的方法。这些方法均以函数指针的形式表示,这种指针叫做:IMP,原型如下:
id(* IMP)(id, SEL, ...)
比如以NSString的方法为例,如下图
2. 如何实现
下面给了一个代码实现的例子
- (void)viewDidLoad {
[super viewDidLoad];
NSString* apple = @"apple";
NSLog(@"执行前===================================");
NSLog(@"Uppercase = %@", [apple uppercaseString]);
NSLog(@"LowerCase = %@", [apple lowercaseString]);
Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);
NSLog(@"执行后===================================");
NSLog(@"Uppercase = %@", [apple uppercaseString]);
NSLog(@"LowerCase = %@", [apple lowercaseString]);
}
程序运行的输出结果:
2016-12-29 14:25:00.535 OC黑魔法[14618:194380] 执行前===================================
2016-12-29 14:25:00.535 OC黑魔法[14618:194380] Uppercase = APPLE
2016-12-29 14:25:00.535 OC黑魔法[14618:194380] LowerCase = apple
2016-12-29 14:25:00.536 OC黑魔法[14618:194380] 执行后===================================
2016-12-29 14:25:00.536 OC黑魔法[14618:194380] Uppercase = apple
2016-12-29 14:25:00.537 OC黑魔法[14618:194380] LowerCase = APPLE
总之通过该方案,开发者可以为那些“完全不知道其实现的”黑盒方法增加日记功能,这非常有助于调试。但是笔者认为应该尽量少用,要不然代码将非常不好维护,不容易懂。
第四、OC中类的本质
1.OC中类的本质
- 从下图中可以才看出,每个对象的结构体的收个成员都是Class类的变量,该变量定义了对象所属的类,通常称之为"is a"指针。比如:NSString的isa就是指向NSString。
- 类的结构体存放类的“元数据(metadata)”,例如类的实现实现了几个方法,具备多少个变量等信息。此结构体的首个变量也是isa指针,这个说明Class本身也是OC对象。还有一个super_class,而且二者的类型也是Class。类对象所属的类型又是另外一个类,叫做“元类(metaclass)”,用来表述类对象所具备的元数据。可以理解为,每个类仅有一个类对象,而每个类对象仅有一个之相关的元类。
//参见:objc.h
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
//参见Objc runtime.h中
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;
2.判断类继承体系中查询类型信息
NSMutableDictionary* dict = [NSMutableDictionary new];
[dict isMemberOfClass:[NSDictionary class]]; //NO
[dict isMemberOfClass:[NSMutableDictionary class]]; //YES
[dict isKindOfClass:[NSDictionary class]]; //YES
[dict isKindOfClass:[NSArray class]]; //NO
以上这样类型查询方法是使用isa指针来获取对象所属的类,然后通过super_class在继承体系下游走。
比较两个类对象是否等同的也可以采用==操作符,而不要使用Objective-C对象常用的"isEqual:"方法来做。原因在于,类对象是“单例”,再应用程序范围内,每个类仅仅有一个实例。
总之,尽量要使用类型查询方法来确定对象类型,而不要直接比较类对象,因为某些对象实现可能实现了消息转发功能。