NSObject作为一个基类,这个类遵守了NSObject协议,并且实现了NSObject协议里的所有方法,所以NSObject类及其子类都可以调用这些方法。下面主要介绍NSObject协议里的属性和方法。
一、NSObject协议
-
- (BOOL)isEqual:(id)object;
:比较两个对像是否相同。比较的是成员变量的值是否相同,这与==
符号有很大的不同:在对对象进行比较时,==
符号判断是否是同一个对象,比较的是内存地址。
UIImageView * imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"loli"]];
UIImage * image = [UIImage imageNamed:@"loli"];
NSLog(@"imageView.image == image : %d", (imageView.image == image));
NSLog(@"[imageView.image isEqual:image] : %d",[imageView.image isEqual:image]);
结果
imageView.image == image : 0
[imageView.image isEqual:image] : 1
@property (readonly) NSUInteger hash;
:对象的哈希值,只读属性,具有唯一性。- (NSUInteger)hash
方法只在对象被添加至NSSet和设置为NSDictionary的key时会调用。@property (readonly) Class superclass;
:获取父类。- (Class)class
:获取自身所属的类。- (instancetype)self;
:获取对象自己。-
- (id)performSelector:(SEL)aSelector;
、- (id)performSelector:(SEL)aSelector withObject:(id)object;
、- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
:对象响应某一方法,object是传递给响应方法的参数。-
performSelector
是程序 运行时 系统负责去找方法,在编译时不做任何校验。如果方法由对象直接调用,编译时会自动校验。Cocoa支持在运行时向某个类添加方法,即方法编译时不存在,但是运行时候存在,这时候必然需要使用performSelector
去调用。所以有时候如果使用了performSelector
,为了使程序能正常运行,一般会使用检查方法- (BOOL)respondsToSelector:(SEL)aSelector;
先行检验一下,针对不同情况做出正确的应对措施。 - 这三个方法,均为同步执行,与线程无关,主线程和子线程中均可调用成功,等同于直接调用该方法。
-
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(sendSomeThing:antherImage:) withObject:[UIImage imageNamed:@"boy"] withObject:[UIImage imageNamed:@"poem"]];
}
- (void)sendSomeThing:(UIImage *)aImage antherImage:(UIImage *)bImage
{
NSLog(@"%@, %@", aImage, bImage);
}
- (BOOL)isProxy;
:判断是否是NSProxy的实例。- (BOOL)isKindOfClass:(Class)aClass;
:判断某个对象是否是某个类或者子类的实例。- (BOOL)isMemberOfClass:(Class)aClass;
:判断某个对象是否是某个类的实例。
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UIImageView * imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"loli"]];
NSLog(@"isKindOfClass : %d", [imageView isKindOfClass:[UIView class]]);
NSLog(@"isMemberOfClass : %d", [imageView isMemberOfClass:[UIView class]]);
NSLog(@"isMemberOfClass : %d", [imageView isMemberOfClass:[UIImageView class]]);
}
结果:
isKindOfClass : 1
isMemberOfClass : 0
isMemberOfClass : 1
-
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
:是否遵循了某一协议,至于是否实现了协议中的方法,该方法不关心。
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
NSLog(@"UIViewAnimating : %d", [self conformsToProtocol:@protocol(UIViewAnimating)]);
NSLog(@"UITableViewDelegate : %d", [self conformsToProtocol:@protocol(UITableViewDelegate)]);
}
结果:
UIViewAnimating : 1
UITableViewDelegate : 0
-
- (BOOL)respondsToSelector:(SEL)aSelector;
:是否 能响应 某一方法,若能则返回YES,不能返回NO。这里需要特别注意:类对象(Object)只能响应类方法(+方法),实例对象(object)只能响应实例方法(-方法)
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
NSLog(@"respondsToSelector : %d", [self respondsToSelector:@selector(viewWillAppear:)]);
NSLog(@"respondsToSelector : %d", [self respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]);
}
结果
respondsToSelector : 1
respondsToSelector : 0
-
@property (readonly, copy) NSString *description;
、@property (readonly, copy) NSString *debugDescription;
:description
是代码打印输出 实例 的时候调用的方法,debugDescription
是控制台也就是po的时候输出实例的时候调用的方法,都可以自定义。
关于description/debugDescription方法(下面以description为例说明):
description方法默认返回对象的描述信息(默认实现是返回类名和对象的内存地址),这样的话,使用NSLog输出OC对象,意义就不是很大,因为我们并不关心对象的内存地址,比较关心的是对象内部的一些成变量的值。因此,会经常重写description方法,覆盖description方法的默认实现。
description有一个实例方法-(NSString *)description;
(NSLog输出该类的实例对象时调用),一个类方法+(NSString *)description;
(NSLog输出该类的类对象时调用)。
// 未重写description方法情况
Person * person_1 = [[Person alloc] init];
NSLog(@"%@", person_1);
NSLog(@"%@",[person_1 class]);
// 输出结果
<Person: 0x60000003a700>
Person
// 重写description方法情况
// Person.m
-(NSString *)description
{
return @"ASD";
}
+(NSString *)description
{
return @"QWE";
}
// 其他类调用
Person * person_1 = [[Person alloc] init];
NSLog(@"%@", person_1);
NSLog(@"%@",[person_1 class]);
// 输出结果
ASD
QWE
特别注意:在重写的description方法中不能同时使用%@ 和self。
// 下面这种写法会导致循环引用,抛出异常
-(NSString *)description
{
return [NSString stringWithFormat:@"%@", self];
}
但是有个例外情况当在该方法内部打印时同时使用会导致该方法连续调用三次后自动结束。
// Person.m
-(NSString *)description
{
NSLog(@"self = %@", self);
return @"ASD";
}
// 其他类调用
Person * person_1 = [[Person alloc] init];
NSLog(@"%@", person_1);
// 输出结果
self = ASD
self = ASD
self = ASD
ASD
二、NSObject基类
-
+ (void)initialize;
类方法,在该类收到第一条消息前初始化该类。
1、 -- > 一般情况下,每个类的这一方法只调用一次。
@implementation FatherView
+ (void)initialize
{
NSLog(@"father --> self == %@, functionString == %s", [self class], __FUNCTION__);
}
@end
@implementation OneViewController
- (void)viewWillAppear:(BOOL)animated
{
FatherView * fatherView = [[FatherView alloc] init];
}
- (void)viewDidLoad {
[super viewDidLoad];
FatherView * fatherView = [[FatherView alloc] init];
}
@end
运行打印结果只会打印一次:结果为
father --> self == FatherView, functionString == +[FatherView initialize]
2、 -- > 超类在它们的子类之前收到这个消息。
@implementation FatherView
+ (void)initialize
{
NSLog(@"father --> self == %@, functionString == %s", [self class], __FUNCTION__);
}
@end
@implementation BodyView
+ (void)initialize
{
NSLog(@"body --> self == %@, functionString == %s", [self class], __FUNCTION__);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
BodyView * bodyView = [[BodyView alloc] init];
}
运行打印结果为:
father --> self == FatherView, functionString == +[FatherView initialize]
body --> self == BodyView, functionString == +[BodyView initialize]
3、 -- > 如果子类不实现+(void)initialize
方法或者如果子类显式调用[super initialize]
,超类的实现可能会被多次调用 。
// 如上面2、中示例所示,将BodyView.m中的 + (void)initialize方法注释掉再运行。
// 结果为:
/**
father --> self == FatherView, functionString == +[FatherView initialize]
father --> self == BodyView, functionString == +[FatherView initialize]
*/
4、--> 如果想使类本身+(void) initialize
方法保护自己部分内容不被多次运行,可以按照以下方式构建实现:
@implementation FatherView
+ (void)initialize
{
NSLog(@"会调用好几次~~");
if (self == [FatherView self]) {
NSLog(@"father --> self == %@, functionString == %s", [self class], __FUNCTION__);
}
}
@end
@implementation BodyView
// 将该方法注销掉
//+ (void)initialize
//{
// NSLog(@"body --> self == %@, functionString == %s", [self class], __FUNCTION__);
//}
@end
@implementation OneViewController
- (void)viewDidLoad {
[super viewDidLoad];
BodyView * bodyView = [[BodyView alloc] init];
}
@end
运行结果:
会调用好几次~~
father --> self == FatherView, functionString == +[FatherView initialize]
会调用好几次~~
可以看到在第二次调用父类的initialize
方法时if内部代码没有再次运行。
5、-- > 如果类包含分类,且分类重写initialize
方法,那么则会调用分类的 initialize 实现,而原类的该方法实现不会被调用,这个机制同 NSObject 的其他方法一样(除 + (void)load 方法外) 。在这不在举例演示。
-
+ (void)load;
每当将类或类别添加到Objective-C runtime时调用。该方法在+(void)initialize
方法后调用。超类
+(void)load
方法先于子类方法的调用;如果分类也实现了
+(void)load
方法,那么该方法也会调用,但是是在类本身的+(void)load
方法调用之后!
@implementation BodyView
+ (void)load
{
NSLog(@"body_load --> self == %@, functionString == %s", [self class], __FUNCTION__);
}
@end
@implementation BodyView (ImageView)
+(void)load
{
NSLog(@"BodyView+ImageView_load --> self == %@, functionString == %s", [self class], __FUNCTION__);
}
@end
打印结果:
body_load --> self == BodyView, functionString == +[BodyView load]
BodyView+ImageView_load --> self == BodyView, functionString == +[BodyView(ImageView) load]
重点注意:无论是 +(void)initialize
方法还是 +(void)load
方法,均是在系统运行时执行的,调用会发生在-(BOOL)application:willFinishLaunchingWithOptions:
调用之前调用。可以依此对该方法进行瘦身 -- > 可以看一下Sunny的这篇文章Notification Once,在此表示感谢!。
+ (instancetype)alloc
/+ (instancetype)allocWithZone:(struct _NSZone *)zone
--在使用时参数传nil
/- (instancetype)init
、+ (instancetype)new
:初始化方法- (id)copy
:返回NSCopying协议方法- (id)copyWithZone:(nullable NSZone *)zone;
返回的对象。NSObject本身不支持NSCopying协议,其子类若想实现该方法就必须实现NSCopying协议并实现- (nonnull id)copyWithZone:(nullable NSZone *)zone
方法,否者会报错:-[子类 copyWithZone:]: unrecognized selector sent to instance ........
-
- (id)mutableCopy;
:返回NSMutableCopying协议方法- (id)mutableCopyWithZone:(nullable NSZone *)zone
在参数为nil情况下返回的对象。其子类若想实现该方法就必须实现NSMutableCopying协议并实现- (id)mutableCopyWithZone:(nullable NSZone *)zone
方法,否者会报错:-[子类 mutabelCopyWithZone:]: unrecognized selector sent to instance .......
-
在这里引申出深拷贝和浅拷贝含义:
- 浅拷贝:就是对指针的拷贝,但是目标对象指针和源对象指针指向同一片内存空间。
- 深拷贝:是指拷贝对象的具体内容,而内存地址是自主分配的,拷贝结束之后,两个对象的值虽然是相同的,但是内存地址不一样,两个对象互不影响,互不干涉。
- 主要常见的涉及深拷贝浅拷贝的类有:
NSString
、NSMutableString
、NSArray
、NSMutableArray
、NSDictionary
、NSMutableDictionary
。它们都已经遵循了NSCopying或者NSMutableCopying协议,对于不可变类(NS····)copy即浅拷贝,mutableCopy即为深拷贝;对于可变类(NSMutable····)无论copy还是mutableCopy都是深拷贝。 - 其实对于所有(NS *)和(NSMutable *)上述结论都适用。对于所有父类是NSObject的自定义类而言,无论copy还是mutableCopy都是深拷贝。
-
-
- (void)dealloc
:释放系统无法释放的对象占用的资源。常见关于dealloc的问题:现在ARC下不需要调用
[super dealloc];
系统会自动帮你调用;可以用来释放通知或者KVO的观察者:通知中心是系统的单例,当在注册通知的观察者时,实际上是在通知中心注册的,这样在界面dismiss后即使ARC下系统帮我们释放了对象,但是在通知中心的观察还是没有移除,那么当有该通知时,依然会尝试调用该对象的接受通知的方法,这可能会导致一些问题。在控制器中也可以在
- (void)viewDidDisappear:(BOOL)animated
中释放。-
在控制器dismiss或者pop后dealloc不调用问题:
- 可能是使用了NSTimer导致的,需要在
- (void)viewWillDisappear:(BOOL)animated;
或者- (void)viewDidDisappear:(BOOL)animated;
方法中调用[timer invalidate]
即可。 - 代理属性设置为strong的情况下可能发生循环引用问题导致无法释放,自然也不会调用该方法。
- 控制器中存在Block的循环引用问题。也会造成dealloc方法不调用问题。常见block中使用self的问题 --- >
__weak Viewcontroller * weakSelf = self;
- 可能是使用了NSTimer导致的,需要在
+ (BOOL)isSubclassOfClass:(Class)aClass
:返回一个布尔值,该值指示接收类是否是给定类的子类或完全相同。+ (NSUInteger)hash
:类对象的哈希值。+ (Class)superclass
:返回接收者超类的类对象。+ (Class)class
:返回类对象。
下面是与函数调用和runtime相关方法:OC是一门动态语言,一个函数是由一个selector(SEL),和一个implement(IMP)组成的。Selector相当于索引,而Implement才是真正的函数实现。
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
:返回一个布尔值,指示类本身是否能够响应给定的选择器。注意与- (BOOL)respondsToSelector:(SEL)aSelector;
方法的区别!!!用法参考上面respondsToSelector用法。+ (BOOL)conformsToProtocol:(Protocol *)protocol
是否遵循了某一协议。详细见上面object协议中方法描述,唯一不同即这是一个类方法。-
- (IMP)methodForSelector:(SEL)aSelector;
、+ (IMP)instanceMethodForSelector:(SEL)aSelector;
定位并返回接收方执行方法的地址,以便将其作为函数调用。IMP是真正的函数指针,SEL只是索引。
参数SEL必须是有效的,所以可以在调用此方法之前使用
- (BOOL)respondsToSelector:(SEL)aSelector;
或者+ (BOOL)instancesRespondToSelector:(SEL)aSelector
进行检验。如果调用者是一个实例,应该参考一个实例方法; 如果调动者是一个类,它应该引用一个类的方法!!!
为了方便了解下面的方法,下面先给出选择器执行函数的过程图:
添加:
+ (BOOL)resolveClassMethod:(SEL)sel
:解析类方法,动态地为类方法的给定选择器提供实现。-
+ (BOOL)resolveInstanceMethod:(SEL)sel
:解析实例方法,动态地为实例方法的给定选择器提供实现。对于上面两个方法而言:
- 如果参数方法被发现并且被添加到接收者,则返回YES,否则,返回NO;
- 这个函数在运行时(runtime)如果没有找到SEL的IML,就会执行。这个函数是给类利用
class_addMethod
添加函数的机会。 - 如果类重写了该函数,并使用
class_addMethod()
添加了相应的selector,并返回YES,那么后面forwardingTargetForSelector
等方法就不会被调用,如果在该函数中没有添加相应的selector,那么不管返回什么,后面都会继续调用相关方法或者爆出异常。
#import <objc/runtime.h>
void addNewMethod()
{
NSLog(@"method == %s", __FUNCTION__);
}
@implementation BodyView
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"解析实例方法 == %s", __FUNCTION__);
// if (![self respondsToSelector:sel]) { //该方法中不可使用respondsToSelector:
// 或者instancesRespondToSelector:方法,这两个方法参数SEL在类中如果没有实现,
// 也会引起resolveInstanceMethod方法的调用!!!
if (sel == @selector(forwardMethod)) {
class_addMethod([self class], sel, (IMP)addNewMethod, "v@:");
NSLog(@"记录次数");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
转发:
-
- (id)forwardingTargetForSelector:(SEL)aSelector
返回无法识别的消息首先被指向的对象。当某个对象不能接受某个selector时,将对该selector的调用转发给另一个对象即返回的对象。但是该方法不建议返回nil(若返回nil则当消息无法识别时会抛出异常)或者self(会造成死循环)。这个方法在这一消息被一个更昂贵的
forwardInvocation:
方法接管之前,给了一个对象一个机会来重定向发送给它的未知消息。如果你在一个非root类中实现这个方法,如果你的类对于给定的选择器没有任何返回值,那么你应该返回调用super的实现的结果。
仅支持一个对象的返回,也就是说消息只能被转发给一个对象。
@interface FatherView : UIView
- (void)forwardMethod;
@end
@implementation FatherView
- (void)forwardMethod
{
NSLog(@"fatherView ---- forwardMethod");
}
@end
@implementation BodyView
- (id)forwardingTargetForSelector:(SEL)aSelector
{
FatherView * aView = [FatherView new];
if ([aView respondsToSelector:@selector(forwardMethod)]) {
return aView;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
@implementation OneViewController
- (void)viewDidLoad {
[super viewDidLoad];
BodyView * bodyView = [[BodyView alloc] init];
[bodyView performSelector:@selector(forwardMethod)];
}
@end
如果上述例子中将BodyView中的- (id)forwardingTargetForSelector:(SEL)aSelector
方法注释掉,再次运行会抛出异常-[BodyView forwardMethod]: unrecognized selector sent to instance *******
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
;方法签名。返回一个包含由给定选择器方法标识的描述的NSMethodSignature对象,如果aSelector没找到就返回nil。一般复写用于runtime提供方法签名。+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
;同样是方法签名,但是一般不复写,只是调用,用于获取类方法签名。-
- (void)forwardInvocation:(NSInvocation *)anInvocation
:由子类覆盖以将消息转发给其他对象。为了响应你的对象本身不能识别的方法,除了
forwardInvocation:
方法外,还必须重写- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法,转发消息的机制是使用从methodSignatureForSelector:
方法返回的信息创建要转发的NSInvocation对象。-
forwardInvocation:
方法的实现有两个任务:- 查找可以响应参数
anInvocation
中编码的消息的对象。 - 向使用参数
anInvocation
的对象发送消息。参数anInvocation
将会保存结果,运行时系统将提取并将此结果传递给原始发件人。 - 可以实现向多个目标转发消息。
- 查找可以响应参数
@implementation FatherView
- (void)forwardMethod
{
NSLog(@"fatherView ---- forwardMethod");
}
@end
@implementation BodyView
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"%s", __FUNCTION__);
if (aSelector == @selector(forwardMethod)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return nil;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"执行转换对象的方法 == %s", __FUNCTION__);
SEL aSelector = [anInvocation selector];
FatherView * aView = [FatherView new];
if ([aView respondsToSelector:aSelector]){
[anInvocation invokeWithTarget:aView];
}else{
[super forwardInvocation:anInvocation];
}
// if (anInvocation.selector == @selector(forwardMethod)) {
// FatherView * aView = [FatherView new];
// [aView performSelector:@selector(forwardMethod)];
// }else{
// [super forwardInvocation:anInvocation];
// }
}
@end
// 调用
BodyView * bodyView = [[BodyView alloc] init];
[bodyView performSelector:@selector(forwardMethod)];
打印结果:
-[BodyView methodSignatureForSelector:]
执行转换对象的方法 == -[BodyView forwardInvocation:]
fatherView ---- forwardMethod
-
- (void)doesNotRecognizeSelector:(SEL)aSelector;
处理接收器无法识别的消息,一般不要重写!!!