1
2
3
4
5
|
objc_property_t *propertyList = class_copyPropertyList([
self
class
], &count);
for
(unsigned
int
i=0; i<count; i++) {
const
char
*propertyName = property_getName(propertyList[i]);[/i]
[i]
NSLog
(
@"property---->%@"
, [
NSString
stringWithUTF8String:propertyName]);[/i]
[i] }
|
[color=rgba(0, 0, 0, 0.6)]獲取方法列表html
1
2
3
4
5
|
ethod *methodList = class_copyMethodList([
self
class
], &count);
for
(unsigned
int
i; i<count; i++) {
Method method = methodList[i];
NSLog
(
@"method---->%@"
,
NSStringFromSelector
(method_getName(method)));
}
|
1
2
3
4
5
6
|
Ivar *ivarList = class_copyIvarList([
self
class
], &count);
for
(unsigned
int
i; i<count; i++) {
Ivar myIvar = ivarList[i];
const
char
*ivarName = ivar_getName(myIvar);
NSLog
(
@"Ivar---->%@"
, [
NSString
stringWithUTF8String:ivarName]);
}
|
1
2
3
4
5
6
|
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([
self
class
], &count);
for
(unsigned
int
i; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const
char
*protocolName = protocol_getName(myProtocal);
NSLog
(
@"protocol---->%@"
, [
NSString
stringWithUTF8String:protocolName]);
}
|
1
2
3
|
Class PersonClass = object_getClass([Person
class
]);
SEL
oriSEL =
@selector
(test1);
Method oriMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
|
1
2
3
|
Class PersonClass = object_getClass([xiaoming
class
]);
SEL
oriSEL =
@selector
(test2);
Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
|
1
|
BOOL
addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
|
1
|
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
|
1
|
method_exchangeImplementations(oriMethod, cusMethod);
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
-(
void
)answer{
unsigned
int
count = 0;
Ivar ivar = class_copyIvarList([
self
.xiaoMing
class
], &count);
for
(
int
i = 0; i<count; i++) {
Ivar var = ivar[i];
const
char
varName = ivar_getName(var);
NSString
*name = [
NSString
stringWithUTF8String:varName];
if
([name isEqualToString:@」_age」]) {
object_setIvar(
self
.xiaoMing, var, @」20」);
break
;
}
}
NSLog
(@」XiaoMing’s age is %@」,
self
.xiaoMing.age);
}
|
2.動態添加方法ios
1
|
class_addMethod([
self
.xiaoMing
class
],
@selector
(guess), (IMP)guessAnswer,
"v@: "
);
|
1
2
3
|
(IMP)guessAnswer 意思是guessAnswer的地址指針;
"v@:"
意思是,v表明無返回值
void
,若是是i則表明
int
;@表明
id
sel; : 表明
SEL
_cmd;
「v@:@@」 意思是,兩個參數的沒有返回值。
|
2.調用guess方法響應事件:
[self.xiaoMing performSelectorselector(guess)];
3.編寫guessAnswer的實現:
void guessAnswer(id self,SEL _cmd){
NSLog(@"i am from beijing");
}
這個有兩個地方留意一下:objective-c
4.代碼參考數組
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
-(
void
)[i]answer{[/i]
class_addMethod([
self
.xiaoMing
class
],
@selector
(guess), (IMP)guessAnswer,
"v@:"
);
if
([
self
.xiaoMing respondsToSelector:
@selector
(guess)]) {
[
self
.xiaoMing performSelector:
@selector
(guess)];
}
else
{
NSLog
(
@"Sorry,I don't know"
);
}
}
void
guessAnswer(
id
self
,
SEL
_cmd){
NSLog
(
@"i am from beijing"
);
}
|
3:動態交換兩個方法的實現
在程序當中,假設XiaoMing的中有test1 和 test2這兩個方法,後來被Runtime交換方法後,每次調動test1 的時候就會去執行test2,調動test2 的時候就會去執行test1, 。那麼,Runtime是如何作到的呢?緩存
1
2
3
|
Method m1 = class_getInstanceMethod([
self
.xiaoMing
class
],
@selector
(test1));
Method m2 = class_getInstanceMethod([
self
.xiaoMing
class
],
@selector
(test2));
method_exchangeImplementations(m1, m2);
|
4:攔截並替換方法
在程序當中,假設XiaoMing的中有test1這個方法,可是因爲某種緣由,咱們要改變這個方法的實現,可是又不能去動它的源代碼(正如一些開源庫出現問題的時候),這個時候runtime就派上用場了。
咱們先增長一個tool類,而後寫一個咱們本身實現的方法-change,
經過runtime把test1替換成change。網絡
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
Class PersionClass = object_getClass([Person
class
]);
Class toolClass = object_getClass([tool
class
]);
////源方法的SEL和Method
SEL
oriSEL =
@selector
(test1);
Method oriMethod = class_getInstanceMethod(PersionClass, oriSEL);
////交換方法的SEL和Method
SEL
cusSEL =
@selector
(change);
Method cusMethod = class_getInstanceMethod(toolClass, cusSEL);
////先嚐試給源方法添加實現,這裏是爲了不源方法沒有實現的狀況
BOOL
addSucc = class_addMethod(PersionClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
if
(addSucc) {
// 添加成功:將源方法的實現替換到交換方法的實現
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}
else
{
//添加失敗:說明源方法已經有實現,直接將兩個方法的實現交換即
method_exchangeImplementations(oriMethod, cusMethod);
}
|
5:在方法上增長額外功能
有這樣一個場景,出於某些需求,咱們須要跟蹤記錄APP中按鈕的點擊次數和頻率等數據,怎麼解決?固然經過繼承按鈕類或者經過類別實現是一個辦法,可是帶來其餘問題好比別人不必定會去實例化你寫的子類,或者其餘類別也實現了點擊方法致使不肯定會調用哪個,runtime能夠這樣解決:數據結構
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
@implementation
UIButton (Hook)
+ (
void
)load {
static
dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class selfClass = [
self
class
];
SEL
oriSEL =
@selector
(sendAction:to:forEvent:);
Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
SEL
cusSEL =
@selector
(mySendAction:to:forEvent:);
Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);
BOOL
addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
if
(addSucc) {
class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}
else
{
method_exchangeImplementations(oriMethod, cusMethod);
}
});
}
- (
void
)mySendAction:(
SEL
)action to:(
id
)target forEvent:(UIEvent *)event {
[CountTool addClickCount];
[
self
mySendAction:action to:target forEvent:event];
}
@end
|
6.實現NSCoding的自動歸檔和解檔
若是你實現過自定義模型數據持久化的過程,那麼你也確定明白,若是一個模型有許多個屬性,那麼咱們須要對每一個屬性都實現一遍encodeObject 和 decodeObjectForKey方法,若是這樣的模型又有不少個,這還真的是一個十分麻煩的事情。下面來看看簡單的實現方式。
假設如今有一個Movie類,有3個屬性,它的h文件這這樣的app
01
02
03
04
05
06
07
08
09
10
|
#import <Foundation/Foundation.h>
//1. 若是想要當前類能夠實現歸檔與反歸檔,須要遵照一個協議NSCoding
@interface
Movie :
NSObject
<
NSCoding
>
@property
(
nonatomic
,
copy
)
NSString
*movieId;
@property
(
nonatomic
,
copy
)
NSString
*movieName;
@property
(
nonatomic
,
copy
)
NSString
*pic_url;
@end
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
#import "Movie.h"
@implementation
Movie
- (
void
)encodeWithCoder:(
NSCoder
*)aCoder
{
[aCoder encodeObject:_movieId forKey:
@"id"
];
[aCoder encodeObject:_movieName forKey:
@"name"
];
[aCoder encodeObject:_pic_url forKey:
@"url"
];
}
- (
id
)initWithCoder:(
NSCoder
*)aDecoder
{
if
(
self
= [
super
init]) {
self
.movieId = [aDecoder decodeObjectForKey:
@"id"
];
self
.movieName = [aDecoder decodeObjectForKey:
@"name"
];
self
.pic_url = [aDecoder decodeObjectForKey:
@"url"
];
}
return
self
;
}
@end
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
#import "Movie.h"
#import <objc/runtime.h>
@implementation
Movie
- (
void
)encodeWithCoder:(
NSCoder
*)encoder
{
unsigned
int
count = 0;
Ivar *ivars = class_copyIvarList([Movie
class
], &count);
for
(
int
i = 0; i<count; i++) {
// 取出i位置對應的成員變量
Ivar ivar = ivars;
// 查當作員變量
const
char
*name = ivar_getName(ivar);
// 歸檔
NSString
*key = [
NSString
stringWithUTF8String:name];
id
value = [
self
valueForKey:key];
[encoder encodeObject:value forKey:key];
}
free(ivars);
}
- (
id
)initWithCoder:(
NSCoder
*)decoder
{
if
(
self
= [
super
init]) {
unsigned
int
count = 0;
Ivar *ivars = class_copyIvarList([Movie
class
], &count);
for
(
int
i = 0; i<count; i++) {
// 取出i位置對應的成員變量
Ivar ivar = ivars;
// 查當作員變量
const
char
*name = ivar_getName(ivar);
// 歸檔
NSString
*key = [
NSString
stringWithUTF8String:name];
id
value = [decoder decodeObjectForKey:key];
// 設置到成員變量身上
[
self
setValue:value forKey:key];
}
free(ivars);
}
return
self
;
}
@end
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
#import "Movie.h"
#import <objc/runtime.h>
#define encodeRuntime(A) \
\
unsigned
int
count = 0;\
Ivar *ivars = class_copyIvarList([A
class
], &count);\
for
(
int
i = 0; i<count; i++) {\
Ivar ivar = ivars;\
const
char
*name = ivar_getName(ivar);\
NSString
*key = [
NSString
stringWithUTF8String:name];\
id
value = [
self
valueForKey:key];\
[encoder encodeObject:value forKey:key];\
}\
free(ivars);\
\
#define initCoderRuntime(A) \
\
if
(
self
= [
super
init]) {\
unsigned
int
count = 0;\
Ivar *ivars = class_copyIvarList([A
class
], &count);\
for
(
int
i = 0; i<count; i++) {\
Ivar ivar = ivars;\
const
char
*name = ivar_getName(ivar);\
NSString
*key = [
NSString
stringWithUTF8String:name];\
id
value = [decoder decodeObjectForKey:key];\
[
self
setValue:value forKey:key];\
}\
free(ivars);\
}\
return
self
;\
\
@implementation
Movie
- (
void
)encodeWithCoder:(
NSCoder
*)encoder
{
encodeRuntime(Movie)
}
- (
id
)initWithCoder:(
NSCoder
*)decoder
{
initCoderRuntime(Movie)
}
@end
|
咱們能夠把這兩個宏單獨放到一個文件裏面,這裏之後須要進行數據持久化的模型均可以直接使用這兩個宏。
7.實現字典轉模型的自動轉換
字典轉模型的應用能夠說是每一個app必然會使用的場景,雖然實現的方式略有不一樣,可是原理都是一致的:遍歷模型中全部屬性,根據模型的屬性名,去字典中查找key,取出對應的值,給模型的屬性賦值。
像幾個出名的開源庫:JSONModel,MJExtension等都是經過這種方式實現的。
框架
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
// 建立對應模型對象
id
objc = [[
self
alloc] init];
unsigned
int
count = 0;
// 1.獲取成員屬性數組
Ivar *ivarList = class_copyIvarList(
self
, &count);
// 2.遍歷全部的成員屬性名,一個一個去字典中取出對應的value給模型屬性賦值
for
(
int
i = 0; i < count; i++) {
// 2.1 獲取成員屬性
Ivar ivar = ivarList;
// 2.2 獲取成員屬性名 C -> OC 字符串
NSString
*ivarName = [
NSString
stringWithUTF8String:ivar_getName(ivar)];
// 2.3 _成員屬性名 => 字典key
NSString
*key = [ivarName substringFromIndex:1];
// 2.4 去字典中取出對應value給模型屬性賦值
id
value = dict[key];
// 獲取成員屬性類型
NSString
*ivarType = [
NSString
stringWithUTF8String:ivar_getTypeEncoding(ivar)];
}
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
if
([value isKindOfClass:[
NSDictionary
class
]] && ![ivarType containsString:
@"NS"
]) {
// 是字典對象,而且屬性名對應類型是自定義類型
// 處理類型字符串 @\"User\" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:
@"@"
withString:
@""
];
ivarType = [ivarType stringByReplacingOccurrencesOfString:
@"\""
withString:
@""
];
// 自定義對象,而且值是字典
// value:user字典 -> User模型
// 獲取模型(user)類對象
Class modalClass =
NSClassFromString
(ivarType);
// 字典轉模型
if
(modalClass) {
// 字典轉模型 user
value = [modalClass objectWithDict:value];
}
}
if
([value isKindOfClass:[
NSArray
class
]]) {
// 判斷對應類有沒有實現字典數組轉模型數組的協議
if
([
self
respondsToSelector:
@selector
(arrayContainModelClass)]) {
// 轉換成id類型,就能調用任何對象的方法
id
idSelf =
self
;
// 獲取數組中字典對應的模型
NSString
*type = [idSelf arrayContainModelClass][key];
// 生成模型
Class classModel =
NSClassFromString
(type);
NSMutableArray
*arrM = [
NSMutableArray
array];
// 遍歷字典數組,生成模型數組
for
(
NSDictionary
*dict in value) {
// 字典轉模型
id
model = [classModel objectWithDict:dict];
[arrM addObject:model];
}
// 把模型數組賦值給value
value = arrM;
}
}
|
我本身以爲系統自帶的KVC模式字典轉模型就挺好的,假設movie是一個模型對象,dict 是一個須要轉化的 [movie setValuesForKeysWithDictionary:dict]; 這個是系統自帶的字典轉模型方法,我的感受也仍是挺好用的,不過使用這個方法的時候須要在模型裏面再實現一個方法才行,
- (void)setValue: (id)value forUndefinedKey: (NSString *)key 重寫這個方法爲了實現兩個目的:1. 模型中的屬性和字典中的key不一致的狀況,好比字典中有個id,咱們須要把它賦值給uid屬性;2. 字典中屬性比模型的屬性還多的狀況。
若是出現以上兩種狀況而沒有實現這個方法的話,程序就會崩潰。
這個方法的實現:
函數
1
2
3
4
5
6
|
- (
void
)setValue:(
id
)value forUndefinedKey:(
NSString
*)key
{
if
([key isEqualToString:
@"id"
]) {
self
.uid = value;
}
}
|
六.幾個參數概念
1.objc_msgSend
1
2
3
4
5
6
7
8
9
|
/* Basic Messaging Primitives
*
* On some architectures, use objc_msgSend_stret for some struct return types.
* On some architectures, use objc_msgSend_fpret for some float return types.
* On some architectures, use objc_msgSend_fp2ret for some float return types.
*
* These functions must be cast to an appropriate function pointer type
* before being called.
*/
|
這是官方的聲明,從這個函數的註釋能夠看出來了,這是個最基本的用於發送消息的函數。另外,這個函數並不能發送全部類型的消息,只能發送基本的消息。好比,在一些處理器上,咱們必須使用objc_msgSend_stret來發送返回值類型爲結構體的消息,使用objc_msgSend_fpret來發送返回值類型爲浮點類型的消息,而又在一些處理器上,還得使用objc_msgSend_fp2ret來發送返回值類型爲浮點類型的消息。
最關鍵的一點:不管什麼時候,要調用objc_msgSend函數,必需要將函數強制轉換成合適的函數指針類型才能調用。
從objc_msgSend函數的聲明來看,它應該是不帶返回值的,可是咱們在使用中卻能夠強制轉換類型,以便接收返回值。另外,它的參數列表是能夠任意多個的,前提也是要強制函數指針類型。
其實編譯器會根據狀況在objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, 或 objc_msgSendSuper_stret四個方法中選擇一個來調用。若是消息是傳遞給超類,那麼會調用名字帶有」Super」的函數;若是消息返回值是數據結構而不是簡單值時,那麼會調用名字帶有」stret」的函數。
2.SEL
objc_msgSend函數第二個參數類型爲SEL,它是selector在Objc中的表示類型(Swift中是Selector類)。selector是方法選擇器,能夠理解爲區分方法的 ID,而這個 ID 的數據結構是SEL:
typedef struct objc_selector *SEL;
其實它就是個映射到方法的C字符串,你能夠用 Objc 編譯器命令@selector()或者 Runtime 系統的sel_registerName函數來得到一個SEL類型的方法選擇器。
不一樣類中相同名字的方法所對應的方法選擇器是相同的,即便方法名字相同而變量類型不一樣也會致使它們具備相同的方法選擇器,因而 Objc 中方法命名有時會帶上參數類型(NSNumber一堆抽象工廠方法),Cocoa 中有好多長長的方法哦。3.id
objc_msgSend第一個參數類型爲id,你們對它都不陌生,它是一個指向類實例的指針:
typedef struct objc_object *id;
那objc_object又是啥呢:
struct objc_object { Class isa; };
objc_object結構體包含一個isa指針,根據isa指針就能夠順藤摸瓜找到對象所屬的類。
PS:isa指針不老是指向實例對象所屬的類,不能依靠它來肯定類型,而是應該用class方法來肯定實例對象的類。由於KVO的實現機理就是將被觀察對象的isa指針指向一箇中間類而不是真實的類,這是一種叫作 isa-swizzling 的技術,詳見官方文檔.4.Class
之因此說isa是指針是由於Class實際上是一個指向objc_class結構體的指針:
typedef struct objc_class *Class;
objc_class裏面的東西多着呢:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
struct
objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const
char
*name OBJC2_UNAVAILABLE;
long
version OBJC2_UNAVAILABLE;
long
info OBJC2_UNAVAILABLE;
long
instance_size OBJC2_UNAVAILABLE;
struct
objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct
objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct
objc_cache *cache OBJC2_UNAVAILABLE;
struct
objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
|
能夠看到運行時一個類還關聯了它的超類指針,類名,成員變量,方法,緩存,還有附屬的協議。在objc_class結構體中:ivars是objc_ivar_list指針;methodLists是指向objc_method_list指針的指針。也就是說能夠動態修改 *methodLists 的值來添加成員方法,這也是Category實現的原理.