runtime
在iOS中是「運行時」的含義,是一套用c語言寫的api,不少人會用可是也僅僅用過最最經常使用的幾個函數,此次,我將詳細的帶着你們探索下runtime
的API,這一章就說下<objc/runtime.h>
這個文件裏的API
,而且我會把不適用於ARC
和不支持64位的API剔除掉。git
首先,咱們先看一個簡單的函數:github
const char * _Nonnull
class_getName(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼
這個函數是經過傳入Class
類型的cls
來獲得Class
的名字。那咱們測試下這個函數:api
-(void)getName {
const char* name = class_getName([Person class]);
NSLog(@"name = %s",name);
}
複製代碼
其中[Person class]
OC中得到Class
的方法,固然,你也能夠用runtime
裏面的objc_getClass
等函數,後面我也會講到。 運行結果:數組
name = Person
複製代碼
咱們能夠看到打印出來的結果就是類的名字。 上面既然用到了[Person class]
,那咱們就看下在runtime
中[Person class]
的替代函數,都是經過名字來得到Class
bash
Class _Nullable
objc_getClass(const char * _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
Class _Nullable
objc_lookUpClass(const char * _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
Class _Nonnull
objc_getRequiredClass(const char * _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
複製代碼
那這三個有什麼區別,從結論上講,objc_getClass
和objc_lookUpClass
的效果是一致的,在最新的源碼裏面,這兩個方法調用的底層也是一致的,當你要找的類不存在的話,就返回nil,而objc_getRequiredClass
裏你要找的類不存在的話,就會崩潰。下面咱們來測試下,咱們建立一個Person
類。函數
-(void)getClass {
const char* name = "Person1";
const char* name_exist = "Person";
Class class1_exist = objc_getClass(name_exist );
NSLog(@"class1_exist = %@",class1_exist);
Class class1 = objc_getClass(name);
NSLog(@"class1 = %@",class1);
Class class2_exist = objc_lookUpClass(name_exist );
NSLog(@"class2_exist = %@",class2_exist);
Class class2 = objc_lookUpClass(name );
NSLog(@"class2 = %@",class2);
Class class3_exist = objc_getRequiredClass(name_exist );
NSLog(@"class3_exist = %@",class3_exist);
Class class3 = objc_getRequiredClass(name );
NSLog(@"class3 = %@",class3);
}
複製代碼
運行結果:測試
2019-02-21 16:58:39.173892+0800 Runtime-Demo[91840:2890084] class1_exist = Person
2019-02-21 16:58:39.173939+0800 Runtime-Demo[91840:2890084] class1 = (null)
2019-02-21 16:58:39.173951+0800 Runtime-Demo[91840:2890084] class2_exist = Person
2019-02-21 16:58:39.173960+0800 Runtime-Demo[91840:2890084] class2 = (null)
2019-02-21 16:58:39.173969+0800 Runtime-Demo[91840:2890084] class3_exist = Person
objc[91840]: link error: class 'Person1' not found.
複製代碼
最後也確實崩潰了,因此你們使用objc_getRequiredClass
這個函數時候要慎重當心。 除了用名字得到類對象之外,還能夠用實例對象來獲取:ui
Class _Nullable
object_getClass(id _Nullable obj)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼
咱們測試下:atom
-(void)getClassWithObjc {
Person* person = [Person new];
Class class = object_getClass(person);
NSLog(@"class = %@",class);
}
複製代碼
運行結果:spa
class = Person
複製代碼
徹底沒問題。 Class
不只能夠表明類對象,也能夠表明元類對象,下面這個函數就是經過名字獲取元類對象。
Class _Nullable
objc_getMetaClass(const char * _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
複製代碼
若是你讀過源碼的話,你就會清楚元類對象儲存的是類方法,類對象儲存的是實例方法,在後面講到Method相關的API的時候,咱們在具體講他們之間的區別。
講到元類對象,咱們還要關注下這個函數,
BOOL
class_isMetaClass(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼
這個函數是用來判斷是不是元類對象。
-(void)isMetaClass {
const char* name = "Person";
BOOL isMetaClass1 = class_isMetaClass(objc_getMetaClass(name ));
BOOL isMetaClass2 = class_isMetaClass(objc_getClass(name));
NSLog(@"objc_getMetaClass = %d,objc_getClass = %d",isMetaClass1,isMetaClass2);
}
複製代碼
運行結果:
objc_getMetaClass = 1,objc_getClass = 0
複製代碼
咱們能夠看到objc_getMetaClass
生成纔是元類對象,objc_getClass
生成的只是類對象。 那麼有沒有函數區分類(元類)對象和實例對象呢?固然有:
BOOL
object_isClass(id _Nullable obj)
OBJC_AVAILABLE(10.10, 8.0, 9.0, 1.0, 2.0);
複製代碼
這個方法只要是類對象或者元類對象都會返回YES:
-(void)isClass {
Person* person = [Person new];
BOOL isClass_objc = object_isClass(person);
BOOL isClass_class = object_isClass(objc_getClass("Person"));
BOOL isClass_metaClass = object_isClass(objc_getMetaClass("Person"));
NSLog(@"isClass_objc = %d isClass_class = %d isClass_metaClass = %d",isClass_objc,isClass_class,isClass_metaClass);
}
複製代碼
運行結果:
isClass_objc = 0 isClass_class = 1 isClass_metaClass = 1
複製代碼
固然也能夠得到父類對象。
Class _Nullable
class_getSuperclass(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼
咱們新建一個繼承Person
的類Student
,而後咱們經過Student
類來得到Person
類。
-(void)getSuperclass {
Class class = class_getSuperclass(objc_getClass("Student"));
NSLog(@"class = %@",class);
}
複製代碼
運行結果:
class = Person
複製代碼
Student
的父類確實是Person
。
咱們知道OC裏面能夠強轉類型,固然,runtime
裏面也有相關方法
Class _Nullable
object_setClass(id _Nullable obj, Class _Nonnull cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼
這個方法的意思是給一個實例對象設置新的類,返回舊的類
-(void)setClass {
Student* student = [Student new];
Class class = object_setClass(student, objc_getClass("Person"));
NSLog(@"oldClass = %@",class);
NSLog(@"newStudent = %@",student);
}
複製代碼
運行結果:
2019-02-21 17:38:17.388341+0800 Runtime-Demo[92493:2904857] oldClass = Student
2019-02-21 17:38:17.388413+0800 Runtime-Demo[92493:2904857] newStudent = <Person: 0x282dd8b50>
複製代碼
咱們能夠看出開始的時候student
的類是Student
,用了object_setClass
後就是Person
類了。 runtime
的動態性還能夠動態新增類,下面四個函數分別表示爲一個類分配內存,註冊一個類,複製一個類,銷燬一個類
Class _Nullable
objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name,
size_t extraBytes)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼
建立一個新類,superclass
是新類所繼承的類,若是爲nil
,superclass
就默認爲根類,也就是NSObject
,extraBytes
是在類和元類對象的末尾爲索引ivars分配的字節數。這通常是0,name
是新類的名字。
void
objc_registerClassPair(Class _Nonnull cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼
註冊類,若是這個類objc_allocateClassPair
好了,就必須objc_registerClassPair
才能使用。
Class _Nonnull
objc_duplicateClass(Class _Nonnull original, const char * _Nonnull name,
size_t extraBytes)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼
這個方法在系統KVO的底層用過,系統不推薦咱們本身用。
void
objc_disposeClassPair(Class _Nonnull cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼
objc_disposeClassPair
只能銷燬經過objc_allocateClassPair
建立的類。 咱們寫個demo來測試這些方法,objc_duplicateClass
官方不建議使用,那麼咱們就不測試這函數。
-(void)classLifeCycle {
Class class = objc_allocateClassPair(objc_getClass("Person"), "Teacher" , 0);
const char* name = class_getName(class);
Class allocateClass = objc_getClass(name);
NSLog(@"allocateClass = %@",allocateClass);
objc_registerClassPair(class);
Class registerClass = objc_getClass(name);
NSLog(@"registerClass = %@",registerClass);
objc_disposeClassPair(class);
Class disposeClass = objc_getClass(name);
NSLog(@"disposeClass = %@",disposeClass);
}
複製代碼
運行結果:
2019-02-22 09:37:52.705001+0800 Runtime-Demo[99587:3143177] allocateClass = (null)
2019-02-22 09:37:52.705049+0800 Runtime-Demo[99587:3143177] registerClass = Teacher
2019-02-22 09:37:52.705071+0800 Runtime-Demo[99587:3143177] disposeClass = (null)
複製代碼
咱們能夠知道若是僅僅只是objc_allocateClassPair
的話,你是找不到這個類的,必須再objc_registerClassPair
才能夠找到,objc_disposeClassPair
則是把類銷燬掉,因此再實際開發中,若是咱們再也不使用自建類的時候,就要及時銷燬,節省內存。
下面兩個函數是關於整個工程的類列表的函數:
Class _Nonnull * _Nullable
objc_copyClassList(unsigned int * _Nullable outCount)
OBJC_AVAILABLE(10.7, 3.1, 9.0, 1.0, 2.0);
複製代碼
這個函數是得到全部註冊類的列表,咱們試用下:
-(void)copyClassList {
unsigned int outCount;
Class *classes = objc_copyClassList(&outCount);
NSLog(@"outCount = %d",outCount);
for (int i = 0; i < outCount; i++) {
NSLog(@"%s", class_getName(classes[i]));
}
free(classes);
}
複製代碼
運行結果:
2019-02-22 09:52:12.218871+0800 Runtime-Demo[99840:3149922] outCount = 15765
2019-02-22 09:52:12.218939+0800 Runtime-Demo[99840:3149922] _CNZombie_
2019-02-22 09:52:12.218953+0800 Runtime-Demo[99840:3149922] JSExport
2019-02-22 09:52:12.218963+0800 Runtime-Demo[99840:3149922] NSLeafProxy
......
......
複製代碼
咱們看到註冊的類有15765個。 objc_getClassList
也是獲取註冊類的方法.
int
objc_getClassList(Class _Nonnull * _Nullable buffer, int bufferCount)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
複製代碼
第一個參數buffer
已分配好內存空間的數組指針,bufferCount
是數組的個數,若是bufferCount
的數量小於實際的數組數量,那麼buffer
返回的是全部數組集合的任意一個子類。若是buffer
爲NULL,那麼bufferCount
爲0。不管那種狀況,返回結果都是當前註冊類的總數。
-(void)getClassList {
int bufferCount = 4;
Class* buffer = (Class*)malloc(sizeof(Class)* bufferCount);
int count1 = objc_getClassList(buffer, bufferCount);
for (unsigned int i =0; i <bufferCount; i++) {
NSLog(@"name = %s",class_getName(buffer[i]));
}
NSLog(@"count1 = %d",count1);
int count2 = objc_getClassList(NULL, 0);
NSLog(@"count2 = %d",count2);
}
複製代碼
運行結果:
2019-02-22 10:14:34.487051+0800 Runtime-Demo[354:3159864] name = _CNZombie_
2019-02-22 10:14:34.487145+0800 Runtime-Demo[354:3159864] name = JSExport
2019-02-22 10:14:34.487158+0800 Runtime-Demo[354:3159864] name = NSLeafProxy
2019-02-22 10:14:34.487173+0800 Runtime-Demo[354:3159864] name = NSProxy
2019-02-22 10:14:34.487186+0800 Runtime-Demo[354:3159864] count1 = 15765
2019-02-22 10:14:34.493662+0800 Runtime-Demo[354:3159864] count2 = 15765
複製代碼
size_t
class_getInstanceSize(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製代碼
返回類實例的大小。
-(void)getInstanceSize {
size_t size = class_getInstanceSize(objc_getClass("Person"));
NSLog(@"size = %zu",size);
}
複製代碼
運行結果
size = 8
複製代碼
一個沒有變量或屬性的繼承於NSObject的類佔有8個字節。 還有個方法是:
id _Nullable
class_createInstance(Class _Nullable cls, size_t extraBytes)
OBJC_RETURNS_RETAINED
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
複製代碼
這是一個建立實例的方法,cls
是要建立的類,extraBytes
是額外的字節內存,用來存儲類定義中的實例變量以外的其餘實例變量。在源碼中alloc
方法底層就是用的這個函數。那麼,咱們用這個函數來初始化Person
類:
-(void)createInstance {
Person* person = class_createInstance(objc_getClass("Person"), 0);
NSLog(@"%@",person);
}
複製代碼
運行結果:
<Person: 0x60000343d2f0>
複製代碼
確實可以成功建立出來。 最後剩下兩個方法:
int
class_getVersion(Class _Nullable cls)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
void
class_setVersion(Class _Nullable cls, int version)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
複製代碼
這兩個方法都和version
有關,這個version在實際中我也沒發現用處,多是在改變類的變量或者方法時給定一個標識.
-(void)version {
int verson = class_getVersion(objc_getClass("Person"));
NSLog(@"version = %d",verson);
class_setVersion(objc_getClass("Person"), 10);
int newVersion = class_getVersion(objc_getClass("Person"));
NSLog(@"newVersion = %d",newVersion);
}
複製代碼
運行結果
2019-02-22 11:29:57.325309+0800 Runtime-Demo[526:167322] version = 0
2019-02-22 11:29:57.325349+0800 Runtime-Demo[526:167322] newVersion = 10
複製代碼
下面咱們將使用runtime裏面最最經常使用的api,也就是給分類綁定對象,這裏,咱們先了解下,一個枚舉:
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_AssociationPolicy
是一個枚舉,裏面的枚舉值分別表明要添加的屬性的修飾類型。 OBJC_ASSOCIATION_ASSIGN
至關於weak
OBJC_ASSOCIATION_RETAIN_NONATOMIC
至關於strong
和nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC
至關於copy
和nonatomic
OBJC_ASSOCIATION_RETAIN
至關於strong
和atomic
OBJC_ASSOCIATION_COPY
至關於copy
和atomic
關於分類的runtime函數,主要有下面3個:
void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
void
objc_removeAssociatedObjects(id _Nonnull object)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
複製代碼
含義分別爲設置關聯對象,得到關聯對象,刪除關聯對象。 咱們知道若是在分類的.h
文件設置屬性並無用,調用的時候會發生閃退,這是由於系統並無自動爲屬性生成Set
和Get
方法,因此,咱們用上面三個方法來手動關聯對象。 咱們建立一個 Person
的分類Person+Actor.h
,在.h文件裏新建一個新屬性@property(nonatomic, assign)float actingSkill
而不作其餘任何處理,這時候,.m
文件就會有警告。
setActingSkill:
和
actingSkill
方法: .m文件
#import "Person+Actor.h"
#import <objc/runtime.h>
static const char* key = "actingSkill";
@implementation Person (Actor)
-(void)setActingSkill:(float)actingSkill {
NSNumber *actingSkillObjc = [NSNumber numberWithFloat:actingSkill];
objc_setAssociatedObject(self, key, actingSkillObjc, OBJC_ASSOCIATION_RETAIN);
}
-(float)actingSkill {
NSNumber *actingSkillObjc = objc_getAssociatedObject(self, key);
return [actingSkillObjc floatValue];
}
@end
複製代碼
這時候就綁定好了。 在ViewController
裏面去使用下這個屬性
-(void)testCategory {
_person = [Person new];
_person.actingSkill = 0.1;
NSLog(@"actingSkill = %f",_person.actingSkill);
}
複製代碼
運行結果:
actingSkill = 0.100000
複製代碼
說明set和get方法都成功了。 那麼還有一個objc_removeAssociatedObjects
方法還沒用,這個方法是解除綁定,爲了測試這個效果,咱們在ViewController裏面touchesBegan
裏面去調用這個方法。
-(void)testCategory {
_person = [Person new];
_person.actingSkill = 0.1;
NSLog(@"actingSkill = %f",_person.actingSkill);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
if (_person) {
objc_removeAssociatedObjects(_person);
NSLog(@"actingSkill2 = %f",_person.actingSkill);
}
}
複製代碼
運行結果:
2019-02-23 13:21:13.090961+0800 Runtime-Demo[2964:201009] actingSkill = 0.100000
2019-02-23 13:24:24.585347+0800 Runtime-Demo[2964:201009] actingSkill2 = 0.000000
複製代碼
以前綁定的結果被移除了。 今天咱們這一篇就講到這,runtime
還有不少其餘的用法咱們下一篇見。 對了,這個是demo,喜歡的能夠點個星。