第一部分:【很重要,這個必須先看】
runtime.h文件中有如下方法,該方法實現(xiàn)了動態(tài)添加屬性的功能,這里說明一下含義。
OBJC_EXPORT void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy);
解釋:
參數(shù)一:id _Nonnull object:給哪個對象添加關(guān)聯(lián)。self的話,就是給當前類添加關(guān)聯(lián)
參數(shù)二:const void * _Nonnull key:setter和getter方法中這個參數(shù)一定要保持一致,就是圖二中的userKey,【詳見圖一】。相當于字典中的key。
參數(shù)三:id _Nullable value:外界傳遞過來的value,就是圖一中的userID。相當于字典中的value。
參數(shù)四:objc_AssociationPolicy policy:關(guān)聯(lián)的策略【關(guān)聯(lián)策略的枚舉值下面有界面】。
圖一:

runtime.h文件中關(guān)聯(lián)的策略有以下枚舉值,這里說明一下含義。
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. */
};
解釋:
OBJC_ASSOCIATION_ASSIGN 等價 @property(assign)
OBJC_ASSOCIATION_RETAIN_NONATOMIC 等價 @property( nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC: @property( nonatomic,copy)
OBJC_ASSOCIATION_RETAIN 等價 @property(atomic,strong)
OBJC_ASSOCIATION_COPY 等價 @property(atomic,copy)
第二部分:
方法調(diào)用本質(zhì)
-
利用runtime發(fā)送消息(即讓對象發(fā)送消息,這個對象指的是創(chuàng)建出類的對象和類對象,是兩個)
- 若想使用runtime消息,必須導入#import <objc/message.h>框架或者#import <objc/runtime.h>
- 若想知道發(fā)送消息的代碼格式:
- 先在終端中cd到運行程序的目錄
- 再輸入 clang -rewrite-objc main.m 查看最終生成代碼
-
消息機制的使用場景:
- 調(diào)用私有方法
- 調(diào)用系統(tǒng)底層的(沒有暴露出來的)方法
- Runtime是在不得不使用的時候采用的。
第三部分:
(Runtime)消息機制
- 對象調(diào)用對象方法||類對象調(diào)用類方法。
- 當使用后者時,類是一個類對象,所以用[類名 class]獲取類對象,再用類對象調(diào)用類方法.
- 區(qū)分OC語言哦,OC語言直接用類名調(diào)用類方法即可。不需要向上面那么用創(chuàng)建出來的類對象去調(diào)用

第四部分:
利用Runtime動態(tài)添加方法
- 為什么動態(tài)添加方法?
- 因為有些方法可能會就不會用到,所以O(shè)C都是懶加載機制。例如:會員機制,只有是會員,才具有某些功能(才會在代碼的懶加載方法中讓你成為會員),你不是會員,你永遠不會被執(zhí)行懶加載方法,不會讓你在懶加載中實現(xiàn)成為會員的功能

第五部分:
普通添加屬性
- 僅僅通過分類給NSObject中添加name屬性

利用Runtime動態(tài)添加屬性
- 給分類只能擴充方法,一般情況不能擴充屬性。
- 如果想要擴充屬性,必須用到RunTime添加屬性,也就是動態(tài)添加屬性。
- 普通添加屬性的那個截圖不屬于擴充屬性的范疇,_name不是.h屬性底層的_name,而是定義的靜態(tài)全局變量。(后面的可不看)因為在分類中添加屬性,不會生成方法的實現(xiàn)和下劃線開頭的變量_name,你只能通過重寫setter和getter方法的方式完成,但是_name不會生成,所以在setter方法中,等號左邊的_name不存在,getter方法中的_name我們也不存在。通過定義一個Static修飾的下劃線開頭的這個全局變量_name,可以解決問題,但是這個_name是我們自己定義的,不是系統(tǒng)自動成的。
- NSObjct這個基類本身沒有name屬性,可以通過分類+Runtime相結(jié)合的方式動態(tài)添加屬性


第六部分:
利用Runtime交換方法
- 有的時候,系統(tǒng)的類不能滿足要求時,例如系統(tǒng)類(NSString,UIImage)可能并不能滿足我們的要求,解決辦法:
- 1.往往是給系統(tǒng)自帶的類添加分類,就是對原有的類進行擴充方法,但是切記擴充的方法不要和系統(tǒng)的類相同.
- 2.或者自定義一個類繼承系統(tǒng)的類,再重寫父類底層的方法??梢赃_到給系統(tǒng)的類自定義某個功能的目的。
- 3.如果老板要求外界調(diào)用的類方法必須是系統(tǒng)的類方法,即給imageNamed方法提供功能,每次加載圖片就判斷下圖片是否加載成功。那么上述兩種情況就滿足不了條件,可以使用Runtime運行時機制,即 調(diào)用imageNamed:的方法,實際上調(diào)用了ZBimageNamed:方法
- 3.1:創(chuàng)建分類
- 3.2:寫一個這樣功能的方法
- 3.3:用系統(tǒng)的方法與有這個功能的方法交換
- 3.4:調(diào)用imageNamed,先會調(diào)用分類的load方法,在load方法實現(xiàn)交換,然后才會去調(diào)用分類的ZBimageNamed
- 具體步驟:在分類中調(diào)用load方法,導入runtime框架,load方法中寫上獲取兩個交換的類的類名,然后寫上method_exchangexxxxx,實現(xiàn)交換。外界調(diào)用imageNamed:的方法,實際上調(diào)用了ZBimageNamed。代碼如下:
ViewController.h文件
// 需求:給imageNamed方法提供功能,每次加載圖片就判斷下圖片是否加載成功。
// 步驟一:先搞個分類,定義一個能加載圖片并且能打印的方法+ (instancetype)ZBimageNamed:(NSString *)name;
// 步驟二:交換imageNamed和ZBimageNamed的實現(xiàn),就能調(diào)用ZBimageNamed,間接調(diào)用ZBimageNamed的實現(xiàn)。
//表面上調(diào)用imageNamed,實際上跑到了分類中調(diào)用了ZBimageNamed,
//在ZBimageNamed方法中又調(diào)用了ZBimageNamed,實際上調(diào)用了系統(tǒng)底層的imageNamed
//注意:分類中的ZBimageNamed方法中的ZBimageNamed不能換成imageNamed,否則真實會調(diào)用ZBimageNamed,從而造成死循環(huán)
// 既能加載圖片又能打印.方法為ZBimageNamed,不是ZBimageNamed11
#import "ViewController.h"
#import "UIImage+Image.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[UIImage imageNamed:@"123"];//內(nèi)部調(diào)用ZBimageNamed方法
UIImage+Image.h文件
#import <UIKit/UIKit.h>
@interface UIImage (Image)
// 加載圖片 只要是系統(tǒng)底層不存在的類一定要聲明
//+ (UIImage *)ZBimageNamed:(NSString *)name;
@end
UIImage+Image.m文件
@implementation UIImage (Image)
// 加載分類到內(nèi)存的時候調(diào)用
+ (void)load
{
// 交換方法
// 獲取ZBimageNamed方法地址
//ZBimageNamed11只是用來交換地址用的,交換萬完之后,就沒有任何意義了,即以后用到的是ZBimageNamed,而不是ZBimageNamed11
Method ZBimageNamed11 = class_getClassMethod(self, @selector(ZBimageNamed:));
// 獲取imageNamed方法地址
Method imageName11 = class_getClassMethod(self, @selector(imageNamed:));
// 交換方法地址,相當于交換實現(xiàn)方式
method_exchangeImplementations(ZBimageNamed11, imageName11);
}
// 不能在分類中重寫系統(tǒng)方法imageNamed,因為會把系統(tǒng)的功能給覆蓋掉,而且分類中不能調(diào)用super.
+ (instancetype)ZBimageNamed:(NSString *)name
{
// 這里調(diào)用ZBimageNamed,本質(zhì)調(diào)用的是imageNamed
UIImage *image = [self ZBimageNamed:name];
if (image == nil) {
NSLog(@"加載空的圖片");
}
return image;
}
@end
在.h中要不要寫方法的聲明 大解析:
- 只要是系統(tǒng)底層不存在某個方法,當我們要調(diào)用這個方法時,一定要聲明。
- 只要是A類不具有B方法或者B屬性,但是C類具有B方法或者B屬性,當我們用A類或者A的對象調(diào)用B的方法或者屬性時(調(diào)用屬性是調(diào)用set方法),必須要在A類的.h文件中聲明B方法或者屬性,還要在A的.m文件實現(xiàn)對應的方法(在本頁搜 動態(tài)添加屬性、普通添加屬性)
- 1.子類繼承父類的類,重寫父類的方法,不需要聲明該方法,只需要實現(xiàn)該方法.
- 2.給系統(tǒng)的類擴充方法,擴充的方法如果系統(tǒng)底層不存在,那么不僅要聲明擴充的方法,還要實現(xiàn)擴充的方法.
- 對1和2的詳細解釋:
-
ZBImage子類繼承父類UIImage,如果子類重寫父類(系統(tǒng)底層)的+ (UIImage *)imageNamed:(NSString *)name方法,那么ZBImage類不需要在自己的.h文件中聲明系統(tǒng)的這個方法,只需在.m文件中重寫父類的這個方法,最后只需要在外界用UIImage調(diào)用這個類方法即可. - 給
UIImage添加分類,為的是給UIImage類擴充新功能.那么不僅要在分類的.h文件聲明方法+ (UIImage *)ZBimageNamed:(NSString *)name;,還要在.m文件中實現(xiàn)該方法,最后只需在外界用UIImage調(diào)用這個類方法即可.否則提示在接口中沒有類方法(接口就是.h文件中聲明的方法)
-
拓展:基礎(chǔ)知識點
- 類的本質(zhì):
- 類本身也是一個對象,是class類型的對象,簡稱“類對象”。類名就代表著類對象,每個類只有一個類對象
- 獲取內(nèi)存中的類對象.例如[Person class]