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实现的原理.