上篇已經講過了函數執行的本質是消息機制,它包含了消息發送、消息動態解析、消息轉發這三個階段。那麼今天咱們再研究一下一些綜合題目和runtime的一些應用。ios
關鍵字super
,在調用[super init]
的時候,super
會轉化成結構體__rw_objc_super
git
struct __rw_objc_super {
struct objc_object *object; //消息接受者
struct objc_object *superClass; //父類
__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};
複製代碼
[super init]
使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 Student.m
轉化成cpp
打開cpp
大概在底部的位置找到github
(Student *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("init"))
複製代碼
簡化以後是面試
(void *)objc_msgSendSuper((__rw_objc_super){self, class_getSuperclass(objc_getClass("Student"))}, sel_registerName("init"))
複製代碼
void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
實際上是向父類發送消息,參數是struct objc_super *super, SEL op, ...
,咱們源碼中找到了該函數的實如今objc-msg-arm64.s
json
ENTRY _objc_msgSendSuper
UNWIND _objc_msgSendSuper, NoFrame
//根據結構體struct __rw_objc_super
{
//struct objc_object *object; //消息接受者
//struct objc_object *superClass; //父類
}佔用空間16字節,objc_msgSendSuper參數是__rw_objc_super,
//使x0偏移16字節,就是兩個指針的空間,賦值給p0 和p16
ldp p0, p16, [x0] // p0 = self , p16 = superclass
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
END_ENTRY _objc_msgSendSuper
複製代碼
將self
和superclass
賦值給 p0, p16
調用CacheLookup NORMAL
數組
.macro CacheLookup //.macro 是一個宏 使用 _cmd&mask 查找緩存中的方法
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
and w12, w1, w11 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp 命中 調用或者返回imp
2: // not hit: p12 = not-hit bucket 沒有命中
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0
.endmacro
複製代碼
彙編比較多,只看到第二行p1 = SEL, p16 = isa
,查找緩存是從p16
,也就是superclass
開始查找,後邊的都和objc_msgSend
同樣。 大體上比較清楚了,super
本質上調用了objc_msgSendSuper
,objc_msgSendSuper
是查找從父類開始查找方法。緩存
[super init]
就是self
直接調用父類init
的方法,可是objc_msgSend
接受者是self
,假如是[self init]
則會產生死循環。[super test]
則是執行父類的test
。 使用Debug Workflow->Always Show Disassemdly
發現super
其實調用了彙編的objc_msgSendSuper2
,進入objc_msgSendSuper2 objc-msg-arm64.s 422 行
發現和objc_msgSendSuper
其實基本一致的sass
//_objc_msgSendSuper 開始
ENTRY _objc_msgSendSuper
UNWIND _objc_msgSendSuper, NoFrame
//x0偏移16字節,就是兩個指針的空間,賦值給p0 和p16
ldp p0, p16, [x0] // p0 = self , p16 = superclass
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
END_ENTRY _objc_msgSendSuper //_objc_msgSendSuper 結束
//objc_msgLookupSuper2 開始
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
//將存儲器地址爲x16+8的字數據讀入寄存器p16。
ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
CacheLookup NORMAL
END_ENTRY _objc_msgSendSuper2
複製代碼
也可使用LLVM
轉化成中間代碼來查看,clang -emit-llvm -S FYCat.m
查看關鍵函數bash
define internal void @"\01-[FYCat forwardInvocation:]"(%1*, i8*, %2*) #1 {
call void bitcast (i8* (%struct._objc_super*, i8*, ...)* @objc_msgSendSuper2 to void (%struct._objc_super*, i8*, %2*)*)(%struct._objc_super* %7, i8* %18, %2* %12)
}
複製代碼
這是forwardInvocation
函數的調用代碼,簡化以後是objc_msgSendSuper2(self,struct._objc_super i8*,%2*)
,就是objc_msgSendSuper2(self,superclass,@selector(forwardInvocation),anInvocation)
。app
驗證
@interface FYPerson : NSObject
@property (nonatomic,copy) NSString *name;
- (int)age;
-(void)test;
@end
@implementation FYPerson
- (void)test{
; NSLog(@"%s",__func__);
}
- (int)age{
NSLog(@"%s",__func__);
return 10;
}
- (NSString *)name{
return [_name stringByAppendingString:@" eat apple"];
}
@end
@interface FYStudent : FYPerson
@end
@implementation FYStudent
- (void)test{
[super test]; //執行父類的test
int age = [super age]; //獲取父類的方法 返回值
NSLog(@"age is %d",age);
NSString * name = [self name]; //從父類開始尋找name的值,但返回的是self.name的值
NSLog(@"%@",name);
}
-(int)age{
return 12;
}
@end
//輸出
-[FYPerson test]
-[FYPerson age]
age is 10
小李子 eat apple
複製代碼
test
是執行父類的方法,[super age]
獲取父類中固定的age
, [self name]
從父類開始尋找name
的值,但返回的是self.name
的值。
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
printf("%s %s\n",class_getName(tcls),class_getName(cls));
if (tcls == cls)
{return YES;}else{
printf("%s",class_getName(tcls));
}
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
printf(" %s %s\n",class_getName(tcls),class_getName(cls));
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isSubclassOfClass:(Class)cls {
for (Class tcls = self; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
複製代碼
- (BOOL)isMemberOfClass
和- (BOOL)isKindOfClass:(Class)cls
比較簡單,都是判斷self.class
和cls
,+ (BOOL)isMemberOfClass:(Class)cls
是判斷self.class->isa
是否和cls
相等,+ (BOOL)isKindOfClass:(Class)cls
判斷cls->isa
和cls->isa->isa
有沒有可能和cls
相等?只有基類是,其餘的都不是。
Class cls = NSObject.class;
Class pcls = FYPerson.class;
FYPerson *p=[FYPerson new];
NSObject *obj=[NSObject new];
BOOL res11 =[p isKindOfClass:pcls];
BOOL res12 =[p isMemberOfClass:pcls];
BOOL res13 =[obj isKindOfClass:cls];
BOOL res14 =[obj isMemberOfClass:cls];
NSLog(@"instance:%d %d %d %d",res11,res12,res13,res14);
//log
//instance:1 1 1 1
複製代碼
p
是pcls
的子類,obj
是cls
的子類,在明顯不過了。
//isKindOfClass cls->isa 和cls/cls->superclass相等嗎?
//元類對象和類對象不相等,可是最後一個元類的isa->superclass是指向NSObject的class 因此res1 = YES;
//cls->isa:元類對象 cls->isa->superclass: NSObject類對象
//cls:類對象
BOOL res1 =[cls isKindOfClass:cls];
//cls->isa 和cls相等嗎? 不相等 cls->isa是元類對象,cls是類對象,不可能相等。
BOOL res2 =[cls isMemberOfClass:cls];
//pcls->isa:person的元類對象 cls->isa->superclass: NSObject元類類對象 ->superclass:NSObject類對象 ->superclass:nil
//pcls:person類對象
BOOL res3 =[pcls isKindOfClass:pcls];
//pcls->isa:person的元類對象
//pcls:person類對象
BOOL res4 =[pcls isMemberOfClass:pcls];
NSLog(@"%d %d %d %d",res1,res2,res3,res4);
結果:
1 0 0 0
複製代碼
網上看到了一個比較有意思的面試題,今天咱們就藉此機會分析一下,雖然網上不少博文已經講了,可是好像都不很對,或者沒有講到根本的東西,因此今天再來探討一下究竟。 其實這道題考察了對象在內存中的佈局,類和對象的關係,和堆上的內存佈局。基礎知識不很牢固的同窗能夠看一下我歷史的博文obj_msgsend基礎、類的本質、對象的本質。
@interface FYPerson : NSObject
@property (nonatomic,copy) NSString *name;
- (void)print;
@end
@implementation FYPerson
- (void)print{
NSLog(@"my name is %@",self.name);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *fix =[NSObject new]; // 16字節 0x60000219b030
id cls = [FYPerson class];針
void * obj = &cls;
[(__bridge id)obj print];
}
複製代碼
當你們看到第二個問題的時候,不傻的話都會回答能編譯成功,不然還問結果乾嗎。咱們從以前學的只是來分析一下,調用方法成功須要有id self
和SEL sel
,如今cls
和obj
都在棧區,obj
指針指向cls
的內存地址,訪問obj
至關於直接訪問cls
內存存儲的值,cls
存儲的是Person.class
,[obj print]
至關於objc_msgSend(cls,@selector(print))
,cls
是有print
方法的,因此會編譯成功。
fix/cls/obj
這三個對象都是存儲在棧上,fix/cls/obj
地址是連續從高到低的,並且他們地址相差都是8
字節,一個指針大小是8
字節。他們三個地址以下所示:
使用圖來表示fix
和obj
:
對象 | 地址 | 地址高低 |
---|---|---|
fix | 0x7ffeec3df920 | 高 |
cls | 0x7ffeec3df918 | 中 |
obj | 0x7ffeec3df910 | 低 |
尋找屬性先是尋找isa
,而後再在isa
地址上+8
則是屬性的值,因此根據obj
尋找cls
地址是0x7ffeec3df918
,而後cls
地址+8字節則是_name
的地址,cls
地址是0x7ffeec3df918
,加上8
字節正好是fix
的地址0x7ffeec3df920
,由於都是指針,因此都是8
字節,因此最後輸出是結果是fix
對象的地址的數據。
狀況再複雜一點,FYPerson
結構改動一下
@interface FYPerson : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *name2;
@property (nonatomic,copy) NSString *name3;
- (void)print;
@end
複製代碼
則他們的_name
、_name2
、_name3
則在cls
的地址基礎上再向上尋找8*1=8/8*2=16/8*3=24
字節,就是向上尋找第1個,第2個,第3個指向對象的指針。
測試代碼:
@interface FYPerson : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *name2;
@property (nonatomic,copy) NSString *name3;
- (void)print;
@end
@implementation FYPerson
- (void)print{
NSLog(@"name1:%@ name2:%@ name3:%@",self.name1,self.name2,self.name3);
}
@end
//主函數
NSObject *fix =[NSObject new];
FYPerson *fix2 =[FYPerson new];
id cls = [FYPerson class];
void * obj = &cls;
[(__bridge id)obj print];//objc_msgSend(self,sel);
NSLog(@"fix:%p fix2:%p cls:%p obj:%p",&fix,&fix2,&cls,&obj);
//log
name1:<FYPerson: 0x6000033a38a0>
name2:<NSObject: 0x6000031f5380>
name3:<ViewController: 0x7f8307505580>
fix: 0x7ffeec3d f9 28
fix2:0x7ffeec3d f9 20
cls: 0x7ffeec3d f9 18
obj: 0x7ffeec3d f9 10
複製代碼
再變形:
- (void)viewDidLoad {
[super viewDidLoad];
/*
objc_msgSuperSend(self,ViewController,sel)
*/
NSLog(@"self:%p ViewController.class:%p SEL:%p",self,ViewController.class,@selector(viewDidLoad));
id cls = [FYPerson class];//cls 是類指針
void * obj = &cls; //obj
[(__bridge id)obj print];//objc_msgSend(self,sel);
NSLog(@"cls:%p obj:%p",&cls,&obj);
//log
name1:<ViewController: 0x7fad03e04ea0>
name2:ViewController
self: 0x7fad03e04ea0
ViewController.class: 0x10d0edf00
SEL: 0x1117d5687
cls:0x7ffee2b11908
obj:0x7ffee2b11900
}
複製代碼
_name1
是cls
地址向上+8字節,_name2
是向上移動16字節,[super viewDidLoad]
本質上是objc_msgSuperSend(self,ViewController.class,sel)
,self
、ViewController.class
、SEL
是同一塊連續內存,佈局由低到高,看了下圖的內存佈局就會頓悟, 結構體以下圖所示:
對象 | 地址高低 |
---|---|
self | 低 |
ViewController.class | 中 |
SEL | 高 |
method | desc |
---|---|
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) | 動態建立一個類(參數:父類,類名,額外的內存空間 |
void objc_registerClassPair(Class cls)) | 註冊一個類 |
void objc_disposeClassPair(Class cls) | 銷燬一個類 |
Class objcect_getClass(id obj) | 獲取isa指向的class |
Class object_setClass (id obj,Class cls) | 設置isa指向的class |
BOOL object_isClass(id class) | 判斷oc對象是否爲Class |
BOOL class_isMetaClass(Class cls) | 是不是元類 |
Class class_getSuperclass(Class cls) | 獲取父類 |
Ivar class_getInstanceVariable(Class cls ,const char * name | 獲取一個實例變量信息 |
Ivar * class_copyIvarList(Class cls,unsigned int * outCount) | 拷貝實例變量列表,須要free |
void object_setIvar(id obj,Ivar ivar,id value | 設置獲取實例變量的值 |
id object_getIvar(id obj,Ivar ivar) | 獲取實例變量的值 |
BOOL class_addIvar(Class cls,const cahr * name ,size_t size,uint_t alignment,const char * types) | 動態添加成員變量(已註冊的類不能動態添加成員變量) |
const char * ivar_getName(Ivar v) | 獲取變量名字 |
const char * ivar_getTypeEncoding(Ivar v) | 變量的encode |
objc_property_t class_getProperty(Class cls,const char* name) | 獲取一個屬性 |
objc_property_t _Nonnull * _Nullable class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount) | 拷貝屬性列表 |
objc_property_t _Nullable class_getProperty(Class _Nullable cls, const char * _Nonnull name) | 獲取屬性列表 |
BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name,const objc_property_attribute_t * _Nullable attributes,unsigned int attributeCount) | 添加屬性 |
void class_replaceProperty(Class _Nullable cls, const char * _Nonnull name,const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount) | 替換屬性 |
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,unsigned int attributeCount) | 動態替換屬性 |
const char * _Nonnull property_getName(objc_property_t _Nonnull property) | 獲取name |
const char * _Nullable property_getAttributes(objc_property_t _Nonnull property) | 獲取屬性的屬性 |
IMP imp_implementationWithBlock(id block) | 獲取block的IMP |
id imp_getBlock(IMP anIMP) | 經過imp 獲取block |
BOOL imp_removeBlock(IMP anIMP) | IMP是否被刪除 |
... | ... |
在業務上有些時候須要給系統控件的某個屬性賦值,可是系統沒有提供方法,只能靠本身了,那麼咱們 獲取class
的全部成員變量,能夠獲取Ivar
查看是否有該變量,而後能夠經過KVC
來賦值。
@interface FYCat : NSObject
@property (nonatomic,copy) NSString * name;
@property (nonatomic,assign) int age;
@end
FYCat *cat=[FYCat new];
unsigned int count = 0;
Ivar *vars= class_copyIvarList(cat.class, &count);
for (int i = 0; i < count; i ++) {
Ivar item = vars[i];
const char *name = ivar_getName(item);
NSLog(@"%s",name);
}
free(vars);
Method *m1= class_copyMethodList(cat.class, &count);
for (int i = 0; i < count; i ++) {
Method item = m1[i];
SEL name = method_getName(item);
printf("method:%s \n",NSStringFromSelector(name).UTF8String);
}
free(m1);
//log
_age
_name
method:.cxx_destruct
method:name
method:setName:
method:methodSignatureForSelector:
method:forwardInvocation:
method:age
method:setAge:
複製代碼
你們經常使用的一個功能是JsonToModel
,那麼咱們已經瞭解到了runtime
的基礎知識,如今能夠本身擼一個JsonToModel
了。
@interface NSObject (Json)
+ (instancetype)fy_objectWithJson:(NSDictionary *)json;
@end
@implementation NSObject (Json)
+ (instancetype)fy_objectWithJson:(NSDictionary *)json{
id obj = [[self alloc]init];
unsigned int count = 0;
Ivar *vars= class_copyIvarList(self, &count);
for (int i = 0; i < count; i ++) {
Ivar item = vars[i];
const char *name = ivar_getName(item);
NSString * nameOC= [NSString stringWithUTF8String:name];
if (nameOC.length>1) {
nameOC = [nameOC substringFromIndex:1];
NSString * value = json[nameOC];
if ([value isKindOfClass:NSString.class] && value.length) {
[obj setValue:value forKey:nameOC];
}else if ([value isKindOfClass:NSArray.class]){
[obj setValue:value forKey:nameOC];
}else if ([value isKindOfClass:NSDictionary.class]){
[obj setValue:value forKey:nameOC];
}else if ([value isKindOfClass:[NSNull class]] || [value isEqual:nil])
{
printf("%s value is nil or null \n",name);
}else if ([value integerValue] > 0){
[obj setValue:value forKey:nameOC];
}else{
printf("未知錯誤 \n");
}
}
}
free(vars);
return obj;
}
@end
複製代碼
而後本身定義一個字典,來測試一下這段代碼
@interface FYCat : NSObject
@property (nonatomic,copy) NSString * name;
@property (nonatomic,assign) int age;
- (void)run;
@end
NSDictionary * info = @{@"age":@"10",@"value":@10,@"name":@"小明"};
FYCat *cat=[FYCat fy_objectWithJson:info];
//log
age:10 name:小明
複製代碼
因爲業務需求須要在某些按鈕點擊事件進行記錄日誌,那麼咱們能夠利用鉤子來實現攔截全部button的點擊事件。
@implementation UIButton (add)
+ (void)load{
Method m1= class_getInstanceMethod(self.class, @selector(sendAction:to:forEvent:));
Method m2= class_getInstanceMethod(self.class, @selector(fy_sendAction:to:forEvent:));
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
method_exchangeImplementations(m1, m2);
});
}
- (void)fy_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
NSLog(@"%@ ",NSStringFromSelector(action));
/*
code here
*/
//sel IMP 已經交換過了,因此不會死循環
[self fy_sendAction:action to:target forEvent:event];
}
@end
複製代碼
能夠在code here
添加須要處理的代碼,通常記錄日誌和延遲觸發均可以處理。[self fy_sendAction:action to:target forEvent:event];
不會產生死循環,緣由是在+load
中已經將m1
和m2
已經交換過了IMP
。咱們進入到method_exchangeImplementations
內部:
void method_exchangeImplementations(Method m1, Method m2)
{
if (!m1 || !m2) return;
mutex_locker_t lock(runtimeLock);
//交換IMP
IMP m1_imp = m1->imp;
m1->imp = m2->imp;
m2->imp = m1_imp;
//刷新緩存
flushCaches(nil);
updateCustomRR_AWZ(nil, m1);
updateCustomRR_AWZ(nil, m2);
}
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
};
using MethodListIMP = IMP;
複製代碼
m1
和m2
交換了IMP
,交換的是method_t->imp
,而後刷新緩存(清空緩存),等下次調用IMP
則須要在cls->rw->data->method
中去尋找。
@implementation NSMutableArray (add)
+ (void)load{
Class cls= NSClassFromString(@"__NSArrayM");
Method m1= class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
SEL sel = @selector(fy_insertObject:atIndex:);
Method m2= class_getInstanceMethod(cls, sel);
Method m3= class_getInstanceMethod(cls, @selector(objectAtIndexedSubscript:));
Method m4= class_getInstanceMethod(cls, @selector(fy_objectAtIndexedSubscript:));
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
method_exchangeImplementations(m1, m2);
method_exchangeImplementations(m3, m4);
});
}
- (void)fy_insertObject:(id)anObject atIndex:(NSUInteger)index{
if (anObject != nil) {
[self fy_insertObject:anObject atIndex:index];
}else{
printf(" anObject is nil \n");
}
}
- (id)fy_objectAtIndexedSubscript:(NSUInteger)idx{
if (self.count > idx) {
return [self fy_objectAtIndexedSubscript:idx];
}else{
printf(" %ld is outof rang \n",(long)idx);
return nil;
}
}
@end
NSMutableArray *array=[NSMutableArray array];
id obj = nil;
[array addObject:obj];
array[1];
//log
anObject is nil
1 is outof rang
複製代碼
NSMutableArray
是類簇,使用工廠模式,NSMutableArray
不是數組實例,而是生產數組對象的工廠。 真實的數組對象是__NSArrayM
,而後給__NSArrayM
鉤子,交換objectAtIndexedSubscript:(NSUInteger)idx
和insertObject:(id)anObject atIndex:(NSUInteger)index
方法,實現崩潰避免。
@interface NSMutableDictionary (add)
@end
@implementation NSMutableDictionary (add)
+ (void)load{
Class cls= NSClassFromString(@"__NSDictionaryM");
Method m1= class_getInstanceMethod(cls, @selector(setObject:forKey:));
// __NSDictionaryM
SEL sel = @selector(fy_setObject:forKey:);
Method m2= class_getInstanceMethod(cls, sel);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
method_exchangeImplementations(m1, m2);
});
}
- (void)fy_setObject:(id)anObject forKey:(id<NSCopying>)aKey{
if (anObject) {
[self fy_setObject:anObject forKey:aKey];
}else{
NSString * key = (NSString *)aKey;
printf("key:%s anobj is nil \n",key.UTF8String);
}
}
@end
複製代碼
利用類別+load
給__NSDictionaryM
添加方法,而後交換IMP
,實現給NSMutableDictionary setObject:Key:
的時候進行nil
校驗,+load
雖然系統啓動的自動調用一次的,可是爲防止開發者再次調用形成IMP
和SEL
混亂,使用dispatch_once
進行單次運行。
super
本質上是self
調用函數,不過查找函數是從sueprclass
開始查找的+isKandOfClass
是判斷self
是不是cls
的子類,+isMemberOfClass:
是判斷self
是否和cls
相同。+load
在Category
是啓動的時候使用運行時編譯的,並且只會加載一次,而後利用objc/runtime.h
中method_exchangeImplementations
實現交換兩個函數的IMP
,能夠實現攔截nil
,下降崩潰率。NSMutableDictionary
、NSMutableArray
是類簇,先找到他們的類而後再交換該類的函數的IMP
。最怕一輩子碌碌無爲,還安慰本身平凡難得。
廣告時間