?NSObject作為一個基類,這個類遵守了NSObject協(xié)議,并且實現(xiàn)了NSObject協(xié)議里的所有方法,所以NSObject類及其子類都可以調(diào)用這些方法。下面主要介紹NSObject協(xié)議里的屬性和方法。
一、NSObject協(xié)議
-
- (BOOL)isEqual:(id)object;:比較兩個對像是否相同。比較的是成員變量的值是否相同,這與==符號有很大的不同:在對對象進行比較時,==符號判斷是否是同一個對象,比較的是內(nèi)存地址。
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]);
結(jié)果
imageView.image == image : 0
[imageView.image isEqual:image] : 1
@property (readonly) NSUInteger hash;:對象的哈希值,只讀屬性,具有唯一性。- (NSUInteger)hash方法只在對象被添加至NSSet和設(shè)置為NSDictionary的key時會調(diào)用。@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;:對象響應(yīng)某一方法,object是傳遞給響應(yīng)方法的參數(shù)。-
performSelector是程序 運行時 系統(tǒng)負責(zé)去找方法,在編譯時不做任何校驗。如果方法由對象直接調(diào)用,編譯時會自動校驗。Cocoa支持在運行時向某個類添加方法,即方法編譯時不存在,但是運行時候存在,這時候必然需要使用performSelector去調(diào)用。所以有時候如果使用了performSelector,為了使程序能正常運行,一般會使用檢查方法- (BOOL)respondsToSelector:(SEL)aSelector;先行檢驗一下,針對不同情況做出正確的應(yīng)對措施。 - 這三個方法,均為同步執(zhí)行,與線程無關(guān),主線程和子線程中均可調(diào)用成功,等同于直接調(diào)用該方法。
-
- (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]]);
}
結(jié)果:
isKindOfClass : 1
isMemberOfClass : 0
isMemberOfClass : 1
-
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;:是否遵循了某一協(xié)議,至于是否實現(xiàn)了協(xié)議中的方法,該方法不關(guān)心。
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
NSLog(@"UIViewAnimating : %d", [self conformsToProtocol:@protocol(UIViewAnimating)]);
NSLog(@"UITableViewDelegate : %d", [self conformsToProtocol:@protocol(UITableViewDelegate)]);
}
結(jié)果:
UIViewAnimating : 1
UITableViewDelegate : 0
-
- (BOOL)respondsToSelector:(SEL)aSelector;:是否 能響應(yīng) 某一方法,若能則返回YES,不能返回NO。這里需要特別注意:類對象(Object)只能響應(yīng)類方法(+方法),實例對象(object)只能響應(yīng)實例方法(-方法)
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
NSLog(@"respondsToSelector : %d", [self respondsToSelector:@selector(viewWillAppear:)]);
NSLog(@"respondsToSelector : %d", [self respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]);
}
結(jié)果
respondsToSelector : 1
respondsToSelector : 0
-
@property (readonly, copy) NSString *description;、@property (readonly, copy) NSString *debugDescription;:description是代碼打印輸出 實例 的時候調(diào)用的方法,debugDescription是控制臺也就是po的時候輸出實例的時候調(diào)用的方法,都可以自定義。
?關(guān)于description/debugDescription方法(下面以description為例說明):
?description方法默認返回對象的描述信息(默認實現(xiàn)是返回類名和對象的內(nèi)存地址),這樣的話,使用NSLog輸出OC對象,意義就不是很大,因為我們并不關(guān)心對象的內(nèi)存地址,比較關(guān)心的是對象內(nèi)部的一些成變量的值。因此,會經(jīng)常重寫description方法,覆蓋description方法的默認實現(xiàn)。
?description有一個實例方法-(NSString *)description;(NSLog輸出該類的實例對象時調(diào)用),一個類方法+(NSString *)description;(NSLog輸出該類的類對象時調(diào)用)。
// 未重寫description方法情況
Person * person_1 = [[Person alloc] init];
NSLog(@"%@", person_1);
NSLog(@"%@",[person_1 class]);
// 輸出結(jié)果
<Person: 0x60000003a700>
Person
// 重寫description方法情況
// Person.m
-(NSString *)description
{
return @"ASD";
}
+(NSString *)description
{
return @"QWE";
}
// 其他類調(diào)用
Person * person_1 = [[Person alloc] init];
NSLog(@"%@", person_1);
NSLog(@"%@",[person_1 class]);
// 輸出結(jié)果
ASD
QWE
特別注意:在重寫的description方法中不能同時使用%@ 和self。
// 下面這種寫法會導(dǎo)致循環(huán)引用,拋出異常
-(NSString *)description
{
return [NSString stringWithFormat:@"%@", self];
}
但是有個例外情況當在該方法內(nèi)部打印時同時使用會導(dǎo)致該方法連續(xù)調(diào)用三次后自動結(jié)束。
// Person.m
-(NSString *)description
{
NSLog(@"self = %@", self);
return @"ASD";
}
// 其他類調(diào)用
Person * person_1 = [[Person alloc] init];
NSLog(@"%@", person_1);
// 輸出結(jié)果
self = ASD
self = ASD
self = ASD
ASD
二、NSObject基類
-
+ (void)initialize;類方法,在該類收到第一條消息前初始化該類。
1、 -- > 一般情況下,每個類的這一方法只調(diào)用一次。
@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
運行打印結(jié)果只會打印一次:結(jié)果為
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];
}
運行打印結(jié)果為:
father --> self == FatherView, functionString == +[FatherView initialize]
body --> self == BodyView, functionString == +[BodyView initialize]
3、 -- > 如果子類不實現(xiàn)+(void)initialize方法或者如果子類顯式調(diào)用[super initialize],超類的實現(xiàn)可能會被多次調(diào)用 。
// 如上面2、中示例所示,將BodyView.m中的 + (void)initialize方法注釋掉再運行。
// 結(jié)果為:
/**
father --> self == FatherView, functionString == +[FatherView initialize]
father --> self == BodyView, functionString == +[FatherView initialize]
*/
4、--> 如果想使類本身+(void) initialize方法保護自己部分內(nèi)容不被多次運行,可以按照以下方式構(gòu)建實現(xiàn):
@implementation FatherView
+ (void)initialize
{
NSLog(@"會調(diào)用好幾次~~");
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
運行結(jié)果:
會調(diào)用好幾次~~
father --> self == FatherView, functionString == +[FatherView initialize]
會調(diào)用好幾次~~
可以看到在第二次調(diào)用父類的initialize方法時if內(nèi)部代碼沒有再次運行。
5、-- > 如果類包含分類,且分類重寫initialize方法,那么則會調(diào)用分類的 initialize 實現(xiàn),而原類的該方法實現(xiàn)不會被調(diào)用,這個機制同 NSObject 的其他方法一樣(除 + (void)load 方法外) 。在這不在舉例演示。
-
+ (void)load;每當將類或類別添加到Objective-C runtime時調(diào)用。該方法在+(void)initialize方法后調(diào)用。超類
+(void)load方法先于子類方法的調(diào)用;如果分類也實現(xiàn)了
+(void)load方法,那么該方法也會調(diào)用,但是是在類本身的+(void)load方法調(diào)用之后!
@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
打印結(jié)果:
body_load --> self == BodyView, functionString == +[BodyView load]
BodyView+ImageView_load --> self == BodyView, functionString == +[BodyView(ImageView) load]
?
重點注意:無論是 +(void)initialize方法還是 +(void)load方法,均是在系統(tǒng)運行時執(zhí)行的,調(diào)用會發(fā)生在-(BOOL)application:willFinishLaunchingWithOptions: 調(diào)用之前調(diào)用。可以依此對該方法進行瘦身 -- > 可以看一下Sunny的這篇文章Notification Once,在此表示感謝!。
?
+ (instancetype)alloc/+ (instancetype)allocWithZone:(struct _NSZone *)zone--在使用時參數(shù)傳nil/- (instancetype)init、+ (instancetype)new:初始化方法- (id)copy:返回NSCopying協(xié)議方法- (id)copyWithZone:(nullable NSZone *)zone;返回的對象。NSObject本身不支持NSCopying協(xié)議,其子類若想實現(xiàn)該方法就必須實現(xiàn)NSCopying協(xié)議并實現(xiàn)- (nonnull id)copyWithZone:(nullable NSZone *)zone方法,否者會報錯:-[子類 copyWithZone:]: unrecognized selector sent to instance ........-
- (id)mutableCopy;:返回NSMutableCopying協(xié)議方法- (id)mutableCopyWithZone:(nullable NSZone *)zone在參數(shù)為nil情況下返回的對象。其子類若想實現(xiàn)該方法就必須實現(xiàn)NSMutableCopying協(xié)議并實現(xiàn)- (id)mutableCopyWithZone:(nullable NSZone *)zone方法,否者會報錯:-[子類 mutabelCopyWithZone:]: unrecognized selector sent to instance .......-
在這里引申出深拷貝和淺拷貝含義:
- 淺拷貝:就是對指針的拷貝,但是目標對象指針和源對象指針指向同一片內(nèi)存空間。
- 深拷貝:是指拷貝對象的具體內(nèi)容,而內(nèi)存地址是自主分配的,拷貝結(jié)束之后,兩個對象的值雖然是相同的,但是內(nèi)存地址不一樣,兩個對象互不影響,互不干涉。
- 主要常見的涉及深拷貝淺拷貝的類有:
NSString、NSMutableString、NSArray、NSMutableArray、NSDictionary、NSMutableDictionary。它們都已經(jīng)遵循了NSCopying或者NSMutableCopying協(xié)議,對于不可變類(NS····)copy即淺拷貝,mutableCopy即為深拷貝;對于可變類(NSMutable····)無論copy還是mutableCopy都是深拷貝。 - 其實對于所有(NS *)和(NSMutable *)上述結(jié)論都適用。對于所有父類是NSObject的自定義類而言,無論copy還是mutableCopy都是深拷貝。
-
-
- (void)dealloc:釋放系統(tǒng)無法釋放的對象占用的資源。常見關(guān)于dealloc的問題:現(xiàn)在ARC下不需要調(diào)用
[super dealloc];系統(tǒng)會自動幫你調(diào)用;可以用來釋放通知或者KVO的觀察者:通知中心是系統(tǒng)的單例,當在注冊通知的觀察者時,實際上是在通知中心注冊的,這樣在界面dismiss后即使ARC下系統(tǒng)幫我們釋放了對象,但是在通知中心的觀察還是沒有移除,那么當有該通知時,依然會嘗試調(diào)用該對象的接受通知的方法,這可能會導(dǎo)致一些問題。在控制器中也可以在
- (void)viewDidDisappear:(BOOL)animated中釋放。-
在控制器dismiss或者pop后dealloc不調(diào)用問題:
- 可能是使用了NSTimer導(dǎo)致的,需要在
- (void)viewWillDisappear:(BOOL)animated;或者- (void)viewDidDisappear:(BOOL)animated;方法中調(diào)用[timer invalidate]即可。 - 代理屬性設(shè)置為strong的情況下可能發(fā)生循環(huán)引用問題導(dǎo)致無法釋放,自然也不會調(diào)用該方法。
- 控制器中存在Block的循環(huán)引用問題。也會造成dealloc方法不調(diào)用問題。常見block中使用self的問題 --- >
__weak Viewcontroller * weakSelf = self;
- 可能是使用了NSTimer導(dǎo)致的,需要在
+ (BOOL)isSubclassOfClass:(Class)aClass:返回一個布爾值,該值指示接收類是否是給定類的子類或完全相同。+ (NSUInteger)hash:類對象的哈希值。+ (Class)superclass:返回接收者超類的類對象。+ (Class)class:返回類對象。
?
下面是與函數(shù)調(diào)用和runtime相關(guān)方法:OC是一門動態(tài)語言,一個函數(shù)是由一個selector(SEL),和一個implement(IMP)組成的。Selector相當于索引,而Implement才是真正的函數(shù)實現(xiàn)。
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;:返回一個布爾值,指示類本身是否能夠響應(yīng)給定的選擇器。注意與- (BOOL)respondsToSelector:(SEL)aSelector;方法的區(qū)別?。。∮梅▍⒖忌厦鎟espondsToSelector用法。+ (BOOL)conformsToProtocol:(Protocol *)protocol是否遵循了某一協(xié)議。詳細見上面object協(xié)議中方法描述,唯一不同即這是一個類方法。-
- (IMP)methodForSelector:(SEL)aSelector;、+ (IMP)instanceMethodForSelector:(SEL)aSelector;定位并返回接收方執(zhí)行方法的地址,以便將其作為函數(shù)調(diào)用。IMP是真正的函數(shù)指針,SEL只是索引。
參數(shù)SEL必須是有效的,所以可以在調(diào)用此方法之前使用
- (BOOL)respondsToSelector:(SEL)aSelector;或者+ (BOOL)instancesRespondToSelector:(SEL)aSelector進行檢驗。如果調(diào)用者是一個實例,應(yīng)該參考一個實例方法; 如果調(diào)動者是一個類,它應(yīng)該引用一個類的方法!!!
為了方便了解下面的方法,下面先給出選擇器執(zhí)行函數(shù)的過程圖:

添加:
+ (BOOL)resolveClassMethod:(SEL)sel:解析類方法,動態(tài)地為類方法的給定選擇器提供實現(xiàn)。-
+ (BOOL)resolveInstanceMethod:(SEL)sel:解析實例方法,動態(tài)地為實例方法的給定選擇器提供實現(xiàn)。?對于上面兩個方法而言:
- 如果參數(shù)方法被發(fā)現(xiàn)并且被添加到接收者,則返回YES,否則,返回NO;
- 這個函數(shù)在運行時(runtime)如果沒有找到SEL的IML,就會執(zhí)行。這個函數(shù)是給類利用
class_addMethod添加函數(shù)的機會。 - 如果類重寫了該函數(shù),并使用
class_addMethod()添加了相應(yīng)的selector,并返回YES,那么后面forwardingTargetForSelector等方法就不會被調(diào)用,如果在該函數(shù)中沒有添加相應(yīng)的selector,那么不管返回什么,后面都會繼續(xù)調(diào)用相關(guān)方法或者爆出異常。
#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:方法,這兩個方法參數(shù)SEL在類中如果沒有實現(xiàn),
// 也會引起resolveInstanceMethod方法的調(diào)用!?。? if (sel == @selector(forwardMethod)) {
class_addMethod([self class], sel, (IMP)addNewMethod, "v@:");
NSLog(@"記錄次數(shù)");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
轉(zhuǎn)發(fā):
-
- (id)forwardingTargetForSelector:(SEL)aSelector返回?zé)o法識別的消息首先被指向的對象。當某個對象不能接受某個selector時,將對該selector的調(diào)用轉(zhuǎn)發(fā)給另一個對象即返回的對象。但是該方法不建議返回nil(若返回nil則當消息無法識別時會拋出異常)或者self(會造成死循環(huán))。這個方法在這一消息被一個更昂貴的
forwardInvocation:方法接管之前,給了一個對象一個機會來重定向發(fā)送給它的未知消息。如果你在一個非root類中實現(xiàn)這個方法,如果你的類對于給定的選擇器沒有任何返回值,那么你應(yīng)該返回調(diào)用super的實現(xiàn)的結(jié)果。
僅支持一個對象的返回,也就是說消息只能被轉(zhuǎn)發(fā)給一個對象。
@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。一般復(fù)寫用于runtime提供方法簽名。+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;同樣是方法簽名,但是一般不復(fù)寫,只是調(diào)用,用于獲取類方法簽名。-
- (void)forwardInvocation:(NSInvocation *)anInvocation:由子類覆蓋以將消息轉(zhuǎn)發(fā)給其他對象。為了響應(yīng)你的對象本身不能識別的方法,除了
forwardInvocation:方法外,還必須重寫- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法,轉(zhuǎn)發(fā)消息的機制是使用從methodSignatureForSelector:方法返回的信息創(chuàng)建要轉(zhuǎn)發(fā)的NSInvocation對象。-
forwardInvocation:方法的實現(xiàn)有兩個任務(wù):- 查找可以響應(yīng)參數(shù)
anInvocation中編碼的消息的對象。 - 向使用參數(shù)
anInvocation的對象發(fā)送消息。參數(shù)anInvocation將會保存結(jié)果,運行時系統(tǒng)將提取并將此結(jié)果傳遞給原始發(fā)件人。 - 可以實現(xiàn)向多個目標轉(zhuǎn)發(fā)消息。
- 查找可以響應(yīng)參數(shù)
@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(@"執(zhí)行轉(zhuǎn)換對象的方法 == %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
// 調(diào)用
BodyView * bodyView = [[BodyView alloc] init];
[bodyView performSelector:@selector(forwardMethod)];
打印結(jié)果:
-[BodyView methodSignatureForSelector:]
執(zhí)行轉(zhuǎn)換對象的方法 == -[BodyView forwardInvocation:]
fatherView ---- forwardMethod
-
- (void)doesNotRecognizeSelector:(SEL)aSelector;處理接收器無法識別的消息,一般不要重寫!?。?/strong>