編輯:關於Android編程
Runtime是一套比較底層的純C語言API,包含了很多底層的C語言API。在我們平時編寫的OC代碼中,程序運行時,其實最終都是轉成了Runtime的C語言代碼。Runtime是開源的,你可以去這裡下載Runtime的源碼。
本文主要分為兩個章節,第一部分主要是理論和原理,第二部分主要是使用實例。簡書文章地址,文章的最後會附上本文的demo下載鏈接。
描述Objective-C對象所用的數據結構定義都在Runtime的頭文件裡,下面我們逐一分析。
運行期系統如何知道某個對象的類型呢?對象類型並不是在編譯期就知道了,而是要在運行期查找。Objective-C有個特殊的類型id,它可以表示Objective-C的任意對象類型,id類型定義在Runtime的頭文件中:
struct objc_object { Class isa; } *id;
由此可見,每個對象結構體的首個成員是Class類的變量。該變量定義了對象所屬的類,通常稱為isa指針。
Class對象也定義在Runtime的頭文件中:
typedef struct objc_class *Class; 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 }
下面說下Class的結構體中的幾個主要變量:
* 1.isa:
結構體的首個變量也是isa指針,這說明Class本身也是Objective-C中的對象。
* 2.super_class:
結構體裡還有個變量是super_class,它定義了本類的超類。類對象所屬類型(isa指針所指向的類型)是另外一個類,叫做“元類”。
* 3.ivars:
成員變量列表,類的成員變量都在ivars裡面。
* 4.methodLists:
方法列表,類的實例方法都在methodLists裡,類方法在元類的methodLists裡面。methodLists是一個指針的指針,通過修改該指針指向指針的值,就可以動態的為某一個類添加成員方法。這也就是Category實現的原理,同時也說明了Category只可以為對象添加成員方法,不能添加成員變量。
* 5.cache:
方法緩存列表,objc_msgSend(下文詳解)每調用一次方法後,就會把該方法緩存到cache列表中,下次調用的時候,會優先從cache列表中尋找,如果cache沒有,才從methodLists中查找方法。提高效率。
上圖中:superclass指針代表繼承關系,isa指針代表實例所屬的類。
類也是一個對象,它是另外一個類的實例,這個就是“元類”,元類裡面保存了類方法的列表,類裡面保存了實例方法的列表。實例對象的isa指向類,類對象的isa指向元類,元類對象的isa指針指向一個“根元類”(root metaclass)。所有子類的元類都繼承父類的元類,換而言之,類對象和元類對象有著同樣的繼承關系。
1.Class是一個指向objc_class結構體的指針,而id是一個指向objc_object結構體的指針,其中的isa是一個指向objc_class結構體的指針。其中的id就是我們所說的對象,Class就是我們所說的類。
2.isa指針不總是指向實例對象所屬的類,不能依靠它來確定類型,而是應該用isKindOfClass:方法來確定實例對象的類。因為KVO的實現機制就是將被觀察對象的isa指針指向一個中間類而不是真實的類。
3.SEL
SEL是選擇子的類型,選擇子指的就是方法的名字。在Runtime的頭文件中的定義如下:
typedef struct objc_selector *SEL;
它就是個映射到方法的C字符串,SEL類型代表著方法的簽名,在類對象的方法列表中存儲著該簽名與方法代碼的對應關系,每個方法都有一個與之對應的SEL類型的對象,根據一個SEL對象就可以找到方法的地址,進而調用方法。
4.Method
Method代表類中的某個方法的類型,在Runtime的頭文件中的定義如下:
typedef struct objc_method *Method;
objc_method的結構體定義如下:
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
1.method_name:方法名。 2.method_types:方法類型,主要存儲著方法的參數類型和返回值類型。 3.IMP:方法的實現,函數指針。(下文詳解)
class_copyMethodList(Class cls, unsigned int *outCount)可以使用這個方法獲取某個類的成員方法列表。
5.Ivar
Ivar代表類中實例變量的類型,在Runtime的頭文件中的定義如下:
typedef struct objc_ivar *Ivar;
objc_ivar的定義如下:
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
class_copyIvarList(Class cls, unsigned int *outCount) 可以使用這個方法獲取某個類的成員變量列表。
6.objc_property_t
objc_property_t是屬性,在Runtime的頭文件中的的定義如下:
typedef struct objc_property *objc_property_t;
class_copyPropertyList(Class cls, unsigned int *outCount) 可以使用這個方法獲取某個類的屬性列表。
7.IMP
IMP在Runtime的頭文件中的的定義如下:
typedef id (*IMP)(id, SEL, ...);
IMP是一個函數指針,它是由編譯器生成的。當你發起一個消息後,這個函數指針決定了最終執行哪段代碼。
8.Cache
Cache在Runtime的頭文件中的的定義如下:
typedef struct objc_cache *Cache
objc_cache的定義如下:
struct objc_cache {
unsigned int mask OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
每調用一次方法後,不會直接在isa指向的類的方法列表(methodLists)中遍歷查找能夠響應消息的方法,因為這樣效率太低。它會把該方法緩存到cache列表中,下次的時候,就直接優先從cache列表中尋找,如果cache沒有,才從isa指向的類的方法列表(methodLists)中查找方法。提高效率。
二、發送消息(objc_msgSend)
在Objective-C中,調用方法是經常使用的。用Objective-C的術語來說,這叫做“傳遞消息”(pass a message)。消息有“名稱”(name)或者“選擇子”(selector),也可以接受參數,而且可能還有返回值。
如果向某個對象傳遞消息,在底層,所有的方法都是普通的C語言函數,然而對象收到消息之後,究竟該調用哪個方法則完全取決於運行期決定,甚至可能在運行期改變,這些特性使得Objective-C變成一門真正的動態語言。
給對象發送消息可以這樣來寫:
id returnValue = [someObject message:parm];
someObject叫做“接收者”(receiver),message是“選擇子”(selector),選擇子和參數結合起來就叫做“消息”(message)。編譯器看到此消息後,將其轉換成C語言函數調用,所調用的函數乃是消息傳遞機制中的核心函數,叫做
objc_msgSend,其原型如下:
id objc_msgSend (id self, SEL _cmd, ...);
後面的…表示這是個“參數個數可變的函數”,能接受兩個或兩個以上的參數。第一個參數是接收者(receiver),第二個參數是選擇子(selector),後續參數就是消息中傳遞的那些參數(parm),其順序不變。
編譯器會把上面的那個消息轉換成:
id returnValue objc_mgSend(someObject, @selector(message:), parm);
傳遞消息的幾種函數:
objc_msgSend:普通的消息都會通過該函數發送。
objc_msgSend_stret:消息中有結構體作為返回值時,通過此函數發送和接收返回值。
objc_msgSend_fpret:消息中返回的是浮點數,可交由此函數處理。
objc_msgSendSuper:和
objc_msgSend類似,這裡把消息發送給超類。
objc_msgSendSuper_stret:和
objc_msgSend_stret類似,這裡把消息發送給超類。
objc_msgSendSuper_fpret:和
objc_msgSend_fpret類似,這裡把消息發送給超類。
編譯器會根據情況選擇一個函數來執行。
objc_msgSend發送消息的原理:
* 第一步:檢測這個selector是不是要被忽略的。
* 第二步:檢測這個target對象是不是nil對象。(nil對象執行任何一個方法都不會Crash,因為會被忽略掉)
* 第三步:首先會根據target對象的isa指針獲取它所對應的類(class)。
* 第四步:優先在類(class)的cache裡面查找與選擇子(selector)名稱相符,如果找不到,再到methodLists查找。
* 第五步:如果沒有在類(class)找到,再到父類(super_class)查找,再到元類(metaclass),直至根metaclass。
* 第六步:一旦找到與選擇子(selector)名稱相符的方法,就跳至其實現代碼。如果沒有找到,就會執行消息轉發(message forwarding)。(下節會詳解)
三、消息轉發(message forwarding)
上面說了消息的傳遞機制,下面就來說一下,如果對象在收到無法解讀的消息之後會發生上面情況。
當一個對象在收到無法解讀的消息之後,它會將消息實施轉發。轉發的主要步驟如下:
消息轉發步驟
第一步:對象在收到無法解讀的消息後,首先調用resolveInstanceMethod:方法決定是否動態添加方法。如果返回YES,則調用class_addMethod動態添加方法,消息得到處理,結束;如果返回NO,則進入下一步; 第二步:當前接收者還有第二次機會處理未知的選擇子,在這一步中,運行期系統會問:能不能把這條消息轉給其他接收者來處理。會進入forwardingTargetForSelector:方法,用於指定備選對象響應這個selector,不能指定為self。如果返回某個對象則會調用對象的方法,結束。如果返回nil,則進入下一步; 第三步:這步我們要通過methodSignatureForSelector:方法簽名,如果返回nil,則消息無法處理。如果返回methodSignature,則進入下一步; 第四步:這步調用forwardInvocation:方法,我們可以通過anInvocation對象做很多處理,比如修改實現方法,修改響應對象等,如果方法調用成功,則結束。如果失敗,則進入doesNotRecognizeSelector方法,拋出異常,此異常表示選擇子最終未能得到處理。
/**
消息轉發第一步:對象在收到無法解讀的消息後,首先調用此方法,可用於動態添加方法,方法決定是否動態添加方法。如果返回YES,則調用class_addMethod動態添加方法,消息得到處理,結束;如果返回NO,則進入下一步;
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
return NO;
}
/**
當前接收者還有第二次機會處理未知的選擇子,在這一步中,運行期系統會問:能不能把這條消息轉給其他接收者來處理。會進入此方法,用於指定備選對象響應這個selector,不能指定為self。如果返回某個對象則會調用對象的方法,結束。如果返回nil,則進入下一步;
*/
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return nil;
}
/**
這步我們要通過該方法簽名,如果返回nil,則消息無法處理。如果返回methodSignature,則進入下一步。
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if ([NSStringFromSelector(aSelector) isEqualToString:@"study"])
{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
/**
這步調用該方法,我們可以通過anInvocation對象做很多處理,比如修改實現方法,修改響應對象等,如果方法調用成功,則結束。如果失敗,則進入doesNotRecognizeSelector方法。
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation setSelector:@selector(play)];
[anInvocation invokeWithTarget:self];
}
/**
拋出異常,此異常表示選擇子最終未能得到處理。
*/
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"無法處理消息:%@", NSStringFromSelector(aSelector));
}
接收者在每一步中均有機會處理消息,步驟越靠後,處理消息的代價越大。最好在第一步就能處理完,這樣系統就可以把此方法緩存起來了。
四、關聯對象 (AssociatedObject)
有時我們需要在對象中存放相關信息,Objective-C中有一種強大的特性可以解決此類問題,就是“關聯對象”。
可以給某個對象關聯許多其他對象,這些對象通過“鍵”來區分。存儲對象值時,可以指明“存儲策略”,用以維護相應地“內存管理語義”。存儲策略由名為“objc_AssociationPolicy” 的枚舉所定義。下表中列出了該枚舉值得取值,同時還列出了與之等下的@property屬性:假如關聯對象成為了屬性,那麼他就會具備對應的語義。
關聯類型
等效的@property屬性
OBJC_ASSOCIATION_ASSIGN
@property (assign) or @ property (unsafe_unretained)
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)
下列方法可以管理關聯對象:
// 以給定的鍵和策略為某對象設置關聯對象值。
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
// 根據給定的鍵從某對象中獲取對應的對象值。
id objc_getAssociatedObject(id object, void *key)
// 移除指定對象的全部關聯對象。
void objc_removeAssociatedObjects(id object)
五、方法交換(method swizzing)
在Objective-C中,對象收到消息之後,究竟會調用哪種方法需要在運行期才能解析出來。查找消息的唯一依據是選擇子(selector),選擇子(selector)與相應的方法(IMP)對應,利用Objective-C的動態特性,可以實現在運行時偷換選擇子(selector)對應的方法實現,這就是方法交換(method swizzling)。
類的方法列表會把每個選擇子都映射到相關的IMP之上
我們可以新增選擇子,也可以改變某個選擇子所對應的方法實現,還可以交換兩個選擇子所映射到的指針。
Objective-C中提供了三種API來動態替換類方法或實例方法的實現:
1.class_replaceMethod替換類方法的定義。
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
2.method_exchangeImplementations交換兩個方法的實現。
method_exchangeImplementations(Method m1, Method m2)
3.method_setImplementation設置一個方法的實現
method_setImplementation(Method m, IMP imp)
先說下這三個方法的區別:
* class_replaceMethod:當類中沒有想替換的原方法時,該方法調用
class_addMethod來為該類增加一個新方法,也正因如此,
class_replaceMethod在調用時需要傳入types參數,而其余兩個卻不需要。
* method_exchangeImplementations:內部實現就是調用了兩次
method_setImplementation方法。
再來看看他們的使用場景:
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(willMoveToSuperview:);
SEL swizzledSelector = @selector(myWillMoveToSuperview:);
Method originalMethod = class_getInstanceMethod(self, originalSelector);
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
BOOL didAddMethod = class_addMethod(self,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(self,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)myWillMoveToSuperview:(UIView *)newSuperview
{
NSLog(@"WillMoveToSuperview: %@", self);
[self myWillMoveToSuperview:newSuperview];
}
總結
1.
class_replaceMethod,當需要替換的方法有可能不存在時,可以考慮使用該方法。
2.method_exchangeImplementations,當需要交換兩個方法的時使用。
3.method_setImplementation是最簡單的用法,當僅僅需要為一個方法設置其實現方式時實現。
使用實例
前面講的全部是理論知識,比較枯燥,下面說一些實際的栗子。
一、動態的創建一個類
// 創建一個名為People的類,它是NSObject的子類
Class People = objc_allocateClassPair([NSObject class], "People", 0);
// 為該類添加一個eat的方法
class_addMethod(People, NSSelectorFromString(@"eat"), (IMP) eatFun, "v@:");
// 注冊該類
objc_registerClassPair(People);
// 創建一個People的實例對象p
id p = [[People alloc] init];
// 調用eat方法
[p performSelector:@selector(eat)];
二、動態的給某個類添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if ([NSStringFromSelector(sel) isEqualToString:@"doSomething"])
{
class_addMethod(self, sel, (IMP) doSomething, "v@:@");
}
return YES;
}
動態的給某個類添加方法,
class_addMethod的參數:
self:給哪個類添加方法
sel:添加方法的方法編號(選擇子)
IMP:添加方法的函數實現(函數地址)
types 函數的類型,(返回值+參數類型) v:void @:對象->self :表示SEL->_cmd
三、關聯對象
類別不可以添加屬性,我們可以在類別中設置關聯,舉個栗子:
Person+Category.h 文件
#import "Person.h"
@interface Person (Category)
@property (nonatomic, copy) NSString *name;
@end
Person+Category.m 文件
#import "Person+Category.h"
#import
@implementation Person (Category)
static char *key;
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self,
key,
name,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, key);
}
@end
當然你也可以這麼寫
Person+Category.m 文件
#import "Person+Category.h"
#import
@implementation Person (Category)
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self,
@selector(name),
name,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, _cmd);
}
@end
objc_setAssociatedObject和
objc_getAssociatedObject傳入的參數key:要求是唯一並且是常量,可以使用static char,然而一個更簡單方便的方法就是:使用選擇子。由於選擇子是唯一並且是常量,你可以使用選擇子作為關聯的key。(PS:_cmd表示當前調用的方法,它就是一個方法選擇器SEL,類似self表示當前對象)
四、方法交換
1.如果我現在想檢查一下項目中有沒有內存循環,怎麼辦?是不是要重寫dealloc函數,看下dealloc有沒有執行,項目小的時候,一個一個controller的寫,還不麻煩,如果項目大,要是一個一個的寫,估計你會瘋掉的。這時候方法交換就派上用場了,你就可以嘗試用自己的方法交換系統的dealloc方法,幾句代碼就搞定了。
#import "UIViewController+Dealloc.h"
#import
@implementation UIViewController (Dealloc)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method method1 = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
Method method2 = class_getInstanceMethod(self, @selector(my_dealloc));
method_exchangeImplementations(method1, method2);
});
}
- (void)my_dealloc
{
NSLog(@"%@銷毀了", self);
[self my_dealloc];
}
@end
2.數組越界,向數組中添加一個nil對象等等,都會造成閃退,我們可以用自己的方法交換數組相對應的方法。下面是一個交換數組addObject:方法的栗子:
#import "NSMutableArray+Category.h"
#import
@implementation NSMutableArray (Category)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(addObject:);
SEL swizzledSelector = @selector(lj_AddObject:);
// NSMutableArray是類簇,真正的類名是__NSArrayM
Method originalMethod = class_getInstanceMethod(objc_getClass("__NSArrayM"), originalSelector);
Method swizzledMethod = class_getInstanceMethod(objc_getClass("__NSArrayM"), swizzledSelector);
BOOL didAddMethod = class_addMethod(self,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod)
{
class_replaceMethod(self,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
}
else
{
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)lj_AddObject:(id)object
{
if (object != nil)
{
[self lj_AddObject:object];
}
}
@end
PS:我不太建議大家平時開發的時候使用這類數組安全操作的做法,不利於代碼的調試,如果真的加入了nil對象,你可能就不會那麼容易找出問題在哪,還是在項目發布的時候使用比較合適。
五、歸檔
大家都知道在歸檔的時候,需要先將屬性一個一個的歸檔,然後再將屬性一個一個的解檔,3-5個屬性還好,假如100個怎麼辦,那不得寫累死。有了Runtime,就不用擔心這個了,下面就是如何利用Runtime實現自動歸檔和解檔。
NSObject+Archive.h文件:
#import
@interface NSObject (Archive)
/**
* 歸檔
*/
- (void)encode:(NSCoder *)aCoder;
/**
* 解檔
*/
- (void)decode:(NSCoder *)aDecoder;
/**
* 這個數組中的成員變量名將會被忽略:不進行歸檔
*/
@property (nonatomic, strong) NSArray *ignoredIvarNames;
@end
NSObject+Archive.m文件:
#import "NSObject+Archive.h"
#import
@implementation NSObject (Archive)
- (void)encode:(NSCoder *)aCoder
{
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);
for (unsigned int i = 0; i < outCount; i++)
{
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
if ([self.ignoredIvarNames containsObject:key])
{
continue;
}
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
free(ivars);
}
- (void)decode:(NSCoder *)aDecoder
{
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);
for (unsigned int i = 0; i < outCount; i++)
{
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
if ([self.ignoredIvarNames containsObject:key])
{
continue;
}
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
}
- (void)setIgnoredIvarNames:(NSArray *)ignoredIvarNames
{
objc_setAssociatedObject(self,
@selector(ignoredIvarNames),
ignoredIvarNames,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSArray *)ignoredIvarNames
{
return objc_getAssociatedObject(self, _cmd);
}
@end
然後再去需要歸檔的類實現文件裡面寫上這幾行代碼:
@implementation Person
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[self encode:aCoder];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init])
{
[self decode:aDecoder];
}
return self;
}
@end
這幾行代碼都是固定寫法,你也可以把它們定義成宏,這樣就可以實現一行代碼就歸檔了,思路源自MJExtension!
六、字典轉模型
利用Runtime,遍歷模型中所有成員變量,根據模型的屬性名,去字典中查找key,取出對應的value,給模型的屬性賦值,實現的思路主要借鑒MJExtension。
NSObject+Property.h文件:
#import
@protocol KeyValue
@optional
/**
* 數組中需要轉換的模型類
*
* @return 字典中的key是數組屬性名,value是數組中存放模型的Class(Class類型或者NSString類型)
*/
+ (NSDictionary *)objectClassInArray;
/**
* 將屬性名換為其他key去字典中取值
*
* @return 字典中的key是屬性名,value是從字典中取值用的key
*/
+ (NSDictionary *)replacedKeyFromPropertyName;
@end
@interface NSObject (Property)
+ (instancetype)objectWithDictionary:(NSDictionary *)dictionary;
@end
NSObject+Property.m文件:
#import "NSObject+Property.h"
#import
@implementation NSObject (Property)
+ (instancetype)objectWithDictionary:(NSDictionary *)dictionary
{
id obj = [[self alloc] init];
// 獲取所有的成員變量
unsigned int count;
Ivar *ivars = class_copyIvarList(self, &count);
for (unsigned int i = 0; i < count; i++)
{
Ivar ivar = ivars[i];
// 取出的成員變量,去掉下劃線
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
NSString *key = [ivarName substringFromIndex:1];
id value = dictionary[key];
// 當這個值為空時,判斷一下是否執行了replacedKeyFromPropertyName協議,如果執行了替換原來的key查值
if (!value)
{
if ([self respondsToSelector:@selector(replacedKeyFromPropertyName)])
{
NSString *replaceKey = [self replacedKeyFromPropertyName][key];
value = dictionary[replaceKey];
}
}
// 字典嵌套字典
if ([value isKindOfClass:[NSDictionary class]])
{
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
NSRange range = [type rangeOfString:@"\""];
type = [type substringFromIndex:range.location + range.length];
range = [type rangeOfString:@"\""];
type = [type substringToIndex:range.location];
Class modelClass = NSClassFromString(type);
if (modelClass)
{
value = [modelClass objectWithDictionary:value];
}
}
// 字典嵌套數組
if ([value isKindOfClass:[NSArray class]])
{
if ([self respondsToSelector:@selector(objectClassInArray)])
{
NSMutableArray *models = [NSMutableArray array];
NSString *type = [self objectClassInArray][key];
Class classModel = NSClassFromString(type);
for (NSDictionary *dict in value)
{
id model = [classModel objectWithDictionary:dict];
[models addObject:model];
}
value = models;
}
}
if (value)
{
[obj setValue:value forKey:key];
}
}
// 釋放ivars
free(ivars);
return obj;
}
@end
我們在開發中,往往會覺得unity自帶的日志系統不是那麼好用,比如,不能篩選日志,不能打印在屏幕上,想生成自己的日志文件,等等等等的需求,這時候就需要自己來擴展編輯器的相
對現有控件進行擴展1、繪制如下所示的兩層背景的TextView創建BorderTextView繼承TextView在構造函數中初始化一些基本數據 //外邊框 mPa
render進程中 一.webkit模塊 webkit引擎會為網頁內容同時創建Dom tree, Render tree和RenderLayer tree. 這三棵樹之間
之前學習了Pulltorefresh,pinnedheaderexpanablelistview 但是結合起來還是有點麻煩的。尤其是像QQ這種。他不是單純的第一個當做分組