1. 程式人生 > >ios開發 之 NSObject詳解

ios開發 之 NSObject詳解

      NSObject是大部分Objective-C類繼承體系的根類。這個類遵循NSObject協議,提供了一些通用的方法,物件通過繼承NSObject,可以從其中繼承訪問執行時的介面,並讓物件具備Objective-C物件的基本能力。下面我們就詳細的介紹NSObject。

原文地址:http://blog.csdn.net/zeng_zhiming/article/details/70225456

一、使用詳解

首先建立兩個測試類

建立ZMPerson繼承NSObject
//===============ZMPerson.h檔案==================

#import <Foundation/Foundation.h>

@interface ZMPerson : NSObject

@property (nonatomic, copy) NSString *name; //!< 姓名
@property (nonatomic, assign) NSInteger age; //!< 年齡

/** 穿衣 */
- (void)dressing;

/** 吃東西 */
- (void)eating;

/** 睡覺 */
- (void)sleeping;

/** 跑 */
- (void)running;

@end

//===========ZMPerson.m檔案==============

#import "ZMPerson.h"

@implementation ZMPerson

/** 穿衣 */
- (void)dressing
{
NSLog(@"-->Person dressing");
}

/** 吃東西 */
- (void)eating
{
NSLog(@"-->Person eating");
}

/** 睡覺 */
- (void)sleeping
{
NSLog(@"-->Person sleeping");
}

/** 跑 */
- (void)running
{
NSLog(@"-->Person running");
}

@end

建立ZMStudent繼承ZMPerson
//===============ZMStudent.h檔案==================

#import "ZMPerson.h"

@interface ZMStudent : ZMPerson

@property (nonatomic, strong) NSString *school; //!< 學校
@property (nonatomic, strong) NSString *grade; //!< 年級

/** 寫字 */
- (void)writing;

/** 讀書 */
- (void)readingWithText:(NSString *)text;

/** 加法運算 */
- (NSNumber *)sumWithNum:(NSNumber *)num1 num2:(NSNumber *)num2;

@end

//===============ZMStudent.m檔案==================

#import "ZMStudent.h"

@implementation ZMStudent

/** 寫字 */
- (void)writing
{
NSLog(@"-->Student writing");
}

/** 讀書 */
- (void)readingWithText:(NSString *)text
{
NSLog(@"-->Student reading:%@", text);
}

/** 加法運算 */
- (NSNumber *)sumWithNum:(NSNumber *)num1 num2:(NSNumber *)num2;
{
return @([num1 floatValue] + [num2 floatValue]);
}

@end

1、載入及初始化類

/** 執行時載入類或分類呼叫該方法, 每個類只會呼叫一次 */
+ (void)load {

}

/** 類例項化使用前需要先初始化, 一個類呼叫一次, 如果子類沒有實現該方法則會呼叫父類方法 */
+ (void)initialize {

}
     `load`和`initialize`區別在於:`load`是隻要類所在檔案被引用就會被呼叫,而`initialize`是在類或者其子類的第一個方法被呼叫前呼叫。所以如果類沒有被引用進專案,就不會有`load`呼叫;但即使類檔案被引用進來,但是沒有使用,那麼`initialize`也不會被呼叫;`load`每個類只會呼叫一次,`initialize`也只調用一次,但是如果子類沒有實現`initialize`方法則會呼叫父類的方法,因此作為父類的`initialize`方法可能會呼叫多次。

2、分配記憶體空間及初始化物件

ZMStudent *student = [ZMStudent new];

ZMStudent *student2 = [[ZMStudent alloc] init];

ZMStudent *student3 = [[ZMStudent allocWithZone:nil] init];
      建立新物件時,首先呼叫`alloc`為物件分配記憶體空間,再呼叫`init`初始化物件,如`[[NSObject alloc] init]`;而`new`方法先給新物件分配空間然後初始化物件,因此`[NSObject new]`等同於`[[NSObject alloc] init]`;關於`allocWithZone`方法,官方文件解釋該方法的引數是被忽略的,正確的做法是傳nil或者NULL引數給它。

3、給物件傳送訊息(執行方法)

(1)直接呼叫

// 呼叫無參無返回值方法
[student running];
// 呼叫有參無返回值方法
[student readingWithText:@"Hello World!"];
// 呼叫有參有返回值方法
NSNumber *sum = [student sumWithNum:@(2) num2:@(3)];
      我們通常都採用這種直接呼叫的方式,給物件發訊息執行方法。這種方式呼叫編譯時會自動校驗方法、引數、返回值是否正確。因此我們必須在標頭檔案中宣告方法的使用。

(2)使用`performSelector`執行

// 先判斷物件是否能呼叫方法,再執行呼叫方法
if ([student respondsToSelector:@selector(running)]) {
// 呼叫無參無返回值方法
[student performSelector:@selector(running)];
}
if ([student respondsToSelector:@selector(readingWithText:)]) {
// 呼叫有參無返回值方法
[student performSelector:@selector(readingWithText:) withObject:@"Hello World"];
}
if ([student respondsToSelector:@selector(sumWithNum:num2:)]) {
// 呼叫有參有返回值方法
NSNumber *sum = [student performSelector:@selector(sumWithNum:num2:) withObject:@(2) withObject:@(8)];
}
        使用`performSelector:`是執行時系統負責去找方法,在編譯時候不做任何校驗;因此在使用時必須先使用`respondsToSelector:`檢查物件是否能呼叫方法,否則可能出現執行崩潰。`performSelector:`常用於呼叫執行時新增的方法,即編譯時不存在,但是執行時候存在的方法。另外需要注意的是`performSelector:`系統提供最多接受兩個引數的方法,而且引數和返回都是`id`型別,並不支援基礎資料型別(如:int, float等)。

(3)使用IMP指標呼叫

// 建立SEL
SEL runSel = @selector(running);
SEL readSel = NSSelectorFromString(@"readingWithText:");
SEL sumSel = NSSelectorFromString(@"sumWithNum:num2:");

// 呼叫無參無返回值方法
IMP rumImp = [student methodForSelector:runSel];
void (*runFunc)(id, SEL) = (void *)rumImp;
runFunc(student, runSel);

// 呼叫有參無返回值方法
IMP readImp = [[student class] instanceMethodForSelector:readSel];
void (*speakFunc)(id, SEL, NSString *) = (void *)readImp;
speakFunc(student, readSel, @"Hello World");

// 呼叫有參有返回值方法
IMP sumImp = [student methodForSelector:sumSel];
NSNumber *(*sumFunc)(id, SEL, NSNumber *, NSNumber *) = (void *)sumImp;
NSNumber *sum3 = sumFunc(student, sumSel, @(6), @(6));
      `SEL` 是方法的索引。IMP是函式指標,指向方法的地址。`SEL`與`IMP`是一一對應的關係,因此我們可以通過修改對應關係達到執行時方法交換的目的。
建立`SEL`物件兩種方法:
1、使用`@selector()`建立
2、使用`NSSelectorFromString()`建立
獲取方法`IMP`指標兩種方法:
1、`- (IMP)methodForSelector:(SEL)aSelector;` 例項方法
2、`+ (IMP)instanceMethodForSelector:(SEL)aSelector;` 類方法

4、複製物件

// 兩個源陣列
NSArray *sourceArrayI = [NSArray arrayWithObjects:@"I", @"I", nil];
NSMutableArray *sourceArrayM = [NSMutableArray arrayWithObjects:@"M", @"M", nil];

// 兩個copy
NSArray *copyArrayI = [sourceArrayI copy];
NSArray *copyArrayM = [sourceArrayM copy];

// 兩個mutableCopy
NSMutableArray *mutableArrayI = [sourceArrayI mutableCopy];
NSMutableArray *mutableArrayM = [sourceArrayM mutableCopy];
     `copy`拷貝為不可變物件,`mutableCopy`拷貝為可變變數,`copy`和`mutableCopy`都可理解為複製了一個新物件。雖然`copy`對靜態物件只是引用計數加1,但是並不影響我們對複製前後的物件進行使用。需要注意的是對於容器物件而言,這兩個方法只是複製了容器本身,對容器中包含的物件只是簡單的指標引用,並沒有深層複製。

5、獲取Class

// 獲取類
Class curClass1 = [student class];
Class curClass2 = [ZMStudent class];

// 獲取父類
Class supClass1 = [student superclass];
Class supClass2 = [ZMStudent superclass];

6、判斷方法

// 初始化物件
ZMPerson *person = [ZMPerson new];
ZMStudent *student = [ZMStudent new];
ZMStudent *student2 = student;

// 判斷物件是否繼承NSObject
if ([student isProxy]) {
NSLog(@"student物件是繼承NSObject類");
}

// 判斷兩個物件是否相等
if ([student isEqual:student2]) {
NSLog(@"student物件與student2物件相等");
}

// 判斷物件是否是指定類
if ([person isKindOfClass:[ZMPerson class]]) {
NSLog(@"person物件是ZMPerson類");
}

// 判斷物件是否是指定類或子類
if ([student isKindOfClass:[ZMPerson class]]) {
NSLog(@"student物件是ZMPerson類的子類");
}

// 判斷是否是另一個類的子類
if ([ZMStudent isSubclassOfClass:[ZMPerson class]]) {
NSLog(@"ZMStudent類是ZMPerson類的子類");
}

// 判判斷物件是否遵從協議
if ([student conformsToProtocol:@protocol(NSObject)]) {
NSLog(@"student物件遵循NSObject協議");
}

// 判斷類是否遵從給定的協議
if ([ZMStudent conformsToProtocol:@protocol(NSObject)]) {
NSLog(@"ZMStudent類遵循NSObject協議");
}

// 判斷物件是否能夠呼叫給定的方法
if ([student respondsToSelector:@selector(running)]) {
NSLog(@"student物件可以呼叫‘running’方法");
}

// 判斷例項是否能夠呼叫給定的方法
if ([ZMStudent instancesRespondToSelector:@selector(running)]) {
NSLog(@"ZMStudent類可以呼叫‘running’方法");
}

二、NSObject.h詳解

//
// NSObject.h
// ZMHeaderFile
//
// Created by ZengZhiming on 2017/4/17.
// Copyright © 2017年 菜鳥基地. All rights reserved.
//
// 詳解 NSObject.h
// Version iOS 10.3
//

#ifndef _OBJC_NSOBJECT_H_
#define _OBJC_NSOBJECT_H_

#if __OBJC__

#include <objc/objc.h>
#include <objc/NSObjCRuntime.h>

@class NSString, NSMethodSignature, NSInvocation;

#pragma mark - 協議部分

@protocol NSObject

/** 判斷兩個物件是否相等, 如相等返回YES, 否則返回NO */
- (BOOL)isEqual:(id)object;
/** 獲取物件hash值, 兩物件相等hash值也相等 */
@property (readonly) NSUInteger hash;

/** 獲取物件的父類 */
@property (readonly) Class superclass;
/** 獲取當前物件的類 */
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");
/** 獲取當前物件 */
- (instancetype)self;

/** 傳送指定的訊息給物件, 返回訊息執行結果(相當於方法呼叫) */
- (id)performSelector:(SEL)aSelector;
/** 傳送帶一個引數的訊息給物件, 返回訊息執行結果(相當於方法呼叫) */
- (id)performSelector:(SEL)aSelector withObject:(id)object;
/** 傳送帶兩個引數的訊息給物件, 返回訊息執行結果(相當於方法呼叫) */
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

/** 判斷物件是否繼承NSObject */
- (BOOL)isProxy;

/** 判斷物件是否是給定類或給定類子類的例項 */
- (BOOL)isKindOfClass:(Class)aClass;
/** 判斷物件是否是給定類的例項 */
- (BOOL)isMemberOfClass:(Class)aClass;
/** 判斷物件是否遵從給定的協議 */
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;

/** 判斷物件是否能夠呼叫給定的方法 */
- (BOOL)respondsToSelector:(SEL)aSelector;

/** 物件引用計數加1, 在MRC下使用 */
- (instancetype)retain OBJC_ARC_UNAVAILABLE;
/** 物件引用計數減1, 在MRC下使用 */
- (oneway void)release OBJC_ARC_UNAVAILABLE;
/** 物件引用計數以推遲方式自動減1, 在MRC下使用 */
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
/** 獲取物件引用計數, 在MRC下使用 */
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
/** 獲取物件儲存空間, 在MRC下使用 */
- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;

/** 獲取物件描述資訊 */
@property (readonly, copy) NSString *description;
@optional
/** 獲取物件在偵錯程式中的描述資訊 */
@property (readonly, copy) NSString *debugDescription;

@end

#pragma mark - 類部分

OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}

/** 執行時載入類或分類呼叫該方法, 每個類只會呼叫一次 */
+ (void)load;
/** 類例項化使用前需要先初始化, 一個類呼叫一次, 如果子類沒有實現該方法則會呼叫父類方法 */
+ (void)initialize;
/** 初始化物件 */
- (instancetype)init
#if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER
NS_DESIGNATED_INITIALIZER
#endif
;

/** 為新物件分配記憶體空間並初始化, 等於[[NSObject alloc] init] */
+ (instancetype)new OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
/** 為新物件分配記憶體空間, 引數傳nil */
+ (instancetype)allocWithZone:(struct _NSZone *)zone OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
/** 為新物件分配記憶體空間 */
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
/** 釋放物件, 當物件的引用計數為0時會呼叫此方法 */
- (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer");
/** 垃圾回收器呼叫此方法前處理它所使用的記憶體。 */
- (void)finalize OBJC_DEPRECATED("Objective-C garbage collection is no longer supported");

/** 複製為不可變物件 */
- (id)copy;
/** 複製為可變物件 */
- (id)mutableCopy;

/** 在指定的記憶體空間上覆製為不可變物件, 在MRC下使用 */
+ (id)copyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
/** 在指定的記憶體空間上覆製為可變物件, 在MRC下使用 */
+ (id)mutableCopyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;

/** 判斷例項是否能夠呼叫給定的方法 */
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
/** 判斷類是否遵從給定的協議 */
+ (BOOL)conformsToProtocol:(Protocol *)protocol;
/** 獲取指向方法實現IMP的指標 */
- (IMP)methodForSelector:(SEL)aSelector;
/** 獲取指向例項方法實現IMP的指標 */
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
/** 找不到函式實現的將呼叫此方法丟擲異常 */
- (void)doesNotRecognizeSelector:(SEL)aSelector;

/** 返回訊息被第一個轉發的物件, 物件沒有找到SEL的IML時就會執行呼叫該方法 */
- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
/** methodSignatureForSelector:返回不為nil則呼叫該方法, 可以重寫該方法將SEL轉發給另一個物件 */
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
/** 獲取方法簽名, 物件沒有找到SEL的IML時就會執行呼叫該方法, 可以重寫該方法丟擲一個函式的簽名,再由forwardInvocation:去執行 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");

/** 獲取例項方法簽名 */
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");

/** 允許弱引用標量, 對於所有allowsWeakReference方法返回NO的類都絕對不能使用__weak修飾符 */
- (BOOL)allowsWeakReference UNAVAILABLE_ATTRIBUTE;
/** 保留弱引用變數, 在使用__weak修飾符的變數時, 當被賦值物件的retainWeakReference方法返回NO的情況下, 該變數將使用“nil” */
- (BOOL)retainWeakReference UNAVAILABLE_ATTRIBUTE;

/** 判斷是否是另一個類的子類 */
+ (BOOL)isSubclassOfClass:(Class)aClass;

/** 動態解析一個類方法 */
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
/** 動態解析一個例項方法, 物件沒有找到SEL的IML時就會執行呼叫該方法, 可以重寫該方法給物件新增所需的SEL */
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

/** 獲取物件hash值, 兩物件相等hash值也相等*/
+ (NSUInteger)hash;
/** 獲取物件的父類 */
+ (Class)superclass;
/** 獲取類 */
+ (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");
/** 獲取物件描述資訊 */
+ (NSString *)description;
/** 獲取物件在偵錯程式中的描述資訊 */
+ (NSString *)debugDescription;

@end

#endif

#endif

原文地址:http://blog.csdn.net/zeng_zhiming/article/details/70225456