Runtime 筆記

runtime 介紹

Objective-C 語言將決定盡可能的從編譯和鏈接時推遲到運行時。只要有可能,Objective-C 總是使用動態(tài) 的方式來解決問題缤至。這意味著 Objective-C 語言不僅需要一個編譯器,同時也需要一個運行時系統(tǒng)來執(zhí)行 編譯好的代碼劝贸。這兒的運行時系統(tǒng)扮演的角色類似于 Objective-C 語言的操作系統(tǒng),Objective-C 基于該系統(tǒng)來工作姨谷。

runtime 了解

方法調用的本質,就是讓對象發(fā)送消息映九。
objc_msgSend,只有對象才能發(fā)送消息梦湘,因此以objc開頭.
使用消息機制前提,必須導入#import <objc/runtime.h>

消息機制簡單使用
// 創(chuàng)建person對象
Person*p = [[Person alloc] init];
// 調用對象方法
[p eat];
// 本質:讓對象發(fā)送消息
objc_msgSend(p, @selector(eat));
// 調用類方法的方式:兩種
// 第一種通過類名調用
[Person eat];
// 第二種通過類對象調用
[[Person class]eat];
// 用類名調用類方法件甥,底層會自動把類名轉換成類對象調用
// 本質:讓類對象發(fā)送消息
objc_msgSend([Personclass],@selector(eat));
三次拯救程序的機會
Method resolution
Fast forwarding
Normal forwarding

Method Resolution
首先捌议,Objective-C 運行時會調用+resolveInstanceMethod:或者+resolveClassMethod:,讓你有機會提供一個函數實現(xiàn)引有。如果你添加了函數并返回 YES瓣颅, 那運行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程。還是以foo為例譬正,你可以這么實現(xiàn):

+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{

if(aSEL ==@selector(foo:)){
class_addMethod([selfclass], aSEL, (IMP)fooMethod,"v@:");
returnYES;
}
return[superresolveInstanceMethod];
}

Core Data 有用到這個方法宫补。NSManagedObjects 中 properties 的 getter 和 setter 就是在運行時動態(tài)添加的檬姥。
如果 resolve 方法返回 NO ,運行時就會移到下一步:消息轉發(fā)(Message Forwarding)粉怕。
PS:iOS 4.3 加入很多新的 runtime 方法健民,主要都是以 imp 為前綴的方法,比如imp_implementationWithBlock()用 block 快速創(chuàng)建一個 imp 贫贝。
上面的例子可以重寫成:

IMPfooIMP = imp_implementationWithBlock(^(id_self) {
NSLog(@"Doing foo");
});
class_addMethod([selfclass], aSEL, fooIMP,"v@:");

Fast forwarding
如果目標對象實現(xiàn)了-forwardingTargetForSelector:荞雏,Runtime 這時就會調用這個方法,給你把這個消息轉發(fā)給其他對象的機會平酿。

- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector ==@selector(foo:)){
returnalternateObject;
}
return[superforwardingTargetForSelector:aSelector];
}

只要這個方法返回的不是 nil 和 self凤优,整個消息發(fā)送的過程就會被重啟,當然發(fā)送的對象會變成你返回的那個對象蜈彼。否則筑辨,就會繼續(xù) Normal Fowarding 。
這里叫 Fast 幸逆,只是為了區(qū)別下一步的轉發(fā)機制棍辕。因為這一步不會創(chuàng)建任何新的對象,但下一步轉發(fā)會創(chuàng)建一個 NSInvocation 對象还绘,所以相對更快點楚昭。
****Normal forwarding****
這一步是 Runtime 最后一次給你挽救的機會。首先它會發(fā)送-methodSignatureForSelector:消息獲得函數的參數和返回值類型拍顷。如果-methodSignatureForSelector:返回 nil 抚太,Runtime 則會發(fā)出-doesNotRecognizeSelector:消息,程序這時也就掛掉了昔案。如果返回了一個函數簽名尿贫,Runtime 就會創(chuàng)建一個 NSInvocation 對象并發(fā)送-forwardInvocation:消息給目標對象。
NSInvocation 實際上就是對一個消息的描述踏揣,包括selector 以及參數等信息庆亡。所以你可以在-forwardInvocation:里修改傳進來的 NSInvocation 對象,然后發(fā)送-invokeWithTarget:消息給它捞稿,傳進去一個新的目標:

- (void)forwardInvocation:(NSInvocation*)invocation
{
SELsel = invocation.selector;
if([alternateObject respondsToSelector:sel]) {
[invocation invokeWithTarget:alternateObject];
}
else{
[selfdoesNotRecognizeSelector:sel];
}
}

Cocoa 里很多地方都利用到了消息傳遞機制來對語言進行擴展又谋,如 Proxies、NSUndoManager 跟 Responder Chain娱局。NSProxy 就是專門用來作為代理轉發(fā)消息的彰亥;NSUndoManager 截取一個消息之后再發(fā)送;而 Responder Chain 保證一個消息轉發(fā)給合適的響應者铃辖。
Objective-C 中給一個對象發(fā)送消息會經過以下幾個步驟:
在對象類的 dispatch table 中嘗試找到該消息剩愧。如果找到了,跳到相應的函數IMP去執(zhí)行實現(xiàn)代碼娇斩;
如果沒有找到仁卷,Runtime 會發(fā)送+resolveInstanceMethod:或者+resolveClassMethod:嘗試去 resolve 這個消息穴翩;
如果 resolve 方法返回 NO,Runtime 就發(fā)送-forwardingTargetForSelector:允許你把這個消息轉發(fā)給另一個對象锦积;
如果沒有新的目標對象返回芒帕, Runtime 就會發(fā)送-methodSignatureForSelector:和-forwardInvocation:消息。你可以發(fā)送-invokeWithTarget:消息來手動轉發(fā)消息或者發(fā)送-doesNotRecognizeSelector:拋出異常丰介。
runtime 方法運用
person 類為例

@interfacePerson:NSObject
{
NSString*address;
}
@property(nonatomic,strong)NSString*name;
@property(nonatomic,assign)NSIntegerage;
//遍歷一個person類的所有的成員變量IvarList
- (void) getAllIvarList {
unsignedintmethodCount =0;
Ivar * ivars = class_copyIvarList([Personclass], &methodCount);
for(unsignedinti =0; i < methodCount; i ++) {
Ivar ivar = ivars[i];
constchar* name = ivar_getName(ivar);
constchar* type = ivar_getTypeEncoding(ivar);
NSLog(@"Person擁有的成員變量的類型為%s背蟆,名字為 %s ",type, name);
}
free(ivars);
}
2016-06-1520:26:39.412demo-Cocoa之method swizzle[17798:2565569] Person擁有的成員變量的類型為@"NSString",名字為 address
2016-06-1520:26:39.413demo-Cocoa之method swizzle[17798:2565569] Person擁有的成員變量的類型為@"NSString"哮幢,名字為 _name
2016-06-1520:26:39.413demo-Cocoa之method swizzle[17798:2565569] Person擁有的成員變量的類型為q带膀,名字為 _age
//遍歷獲取所有屬性Property
- (void) getAllProperty {
unsignedintpropertyCount =0;
objc_property_t*propertyList = class_copyPropertyList([Personclass], &propertyCount);
for(unsignedinti =0; i < propertyCount; i++ ) {
objc_property_t*thisProperty = propertyList[i];
constchar* propertyName = property_getName(*thisProperty);
NSLog(@"Person擁有的屬性為: '%s'", propertyName);
}
}
2016-06-15 20:25:19.653 demo-Cocoa之method swizzle[17778:2564081] Person擁有的屬性為: 'name'
2016-06-15 20:25:19.653 demo-Cocoa之method swizzle[17778:2564081] Person擁有的屬性為: 'age'

訪問私有變量
我們知道,OC中沒有真正意義上的私有變量和方法橙垢,要讓成員變量私有垛叨,要放在m文件中聲明,不對外暴露柜某。如果我們知道這個成員變量的名稱嗽元,可以通過runtime獲取成員變量,再通過getIvar來獲取它的值喂击。

Ivar ivar = class_getInstanceVariable([Model class],"_str1");
NSString * str1 = object_getIvar(model, ivar);

runtime學習階段 (可以通過這個例子實現(xiàn) one-to-N的delegates)
1.調用一個函數,但是函數沒有實現(xiàn)

- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(doSomething)];
}

不出意外剂癌,程序崩潰

reason: '-[ViewController doSomething]: unrecognized selector sent to instance 0x7fe9f3736680'**
***** First throw call stack:**

2.實現(xiàn)+ (BOOL)resolveInstanceMethod:(SEL)sel,依舊崩潰
因為沒有找到doSomething這個方法,下面我們在里面實現(xiàn)+ (BOOL)resolveInstanceMethod:(SEL)sel這個方法翰绊,并且判斷如果SEL是doSomething那就輸出add method here

- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(doSomething)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if(sel ==@selector(doSomething)) {
NSLog(@"add method here");
returnYES;
}
return[super resolveInstanceMethod:sel];
}

繼續(xù)運行, NSLog

**2015-12-24 10:47:24.687 RuntimeTest1[2007:382077] add method here**
**2015-12-24 10:47:24.687 RuntimeTest1[2007:382077] -[ViewController doSomething]: unrecognized selector sent to instance 0x7f9568c331f0**
**2015-12-24 10:47:24.690 RuntimeTest1[2007:382077] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController doSomething]: unrecognized selector sent to instance 0x7f9568c331f0'**
***** First throw call stack:**

3.通過class_addMethod添加方法
可以看到程序依然是崩潰了佩谷,但是我們可以看到輸出了add method here,這說明我們+ (BOOL)resolveInstanceMethod:(SEL)sel這個方法執(zhí)行了辞做,并進入了判斷琳要,所以,在這兒秤茅,我們可以做一下操作,使這個方法得到響應童叠,不至于走到最后
- (void)doesNotRecognizeSelector:(SEL)aSelector這個方法中而崩掉了框喳,接下來,我們繼續(xù)操作厦坛,如下

- (void)viewDidLoad {
    [super viewDidLoad];
    [self performSelector:@selector(doSomething)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if(sel ==@selector(doSomething)) {
        NSLog(@"add method here");        
        class_addMethod([selfclass], sel, (IMP)dynamicMethodIMP,"v@:");
        returnYES;
    }
    return [superresolveInstanceMethod:sel];
}
void dynamicMethodIMP (idself, SEL _cmd) {
    NSLog(@"doSomething SEL");
}


導入了并且在+ (BOOL)resolveInstanceMethod:(SEL)sel中執(zhí)行了class_addMethod這個方法五垮,然后定義了一個void dynamicMethodIMP (id self, SEL _cmd)這個函數,運行工程杜秸,看log

**2015-12-2411:45:11.934RuntimeTest1[2284:478571] add method here**
**2015-12-2411:45:11.934RuntimeTest1[2284:478571] doSomething SEL**

這時候我們發(fā)現(xiàn)放仗,程序并沒有崩潰,而且還輸出了doSomething SEL
這時候就說明我們已經通過runtime成功的向我們這個類中添加了一個方法撬碟。
關于class_addMethod這個方法的定義
OBJC_EXPORTBOOLclass_addMethod(Class cls, SEL name, IMP imp,constchar*types)
cls在這個類中添加方法诞挨,也就是需要添加方法的類
name方法名
imp本質上就是一個函數指針莉撇,指向方法的實現(xiàn)
types定義該數返回值類型和參數類型的字符串,這里比如"v@:"惶傻,其中v就是void棍郎,帶表返回類型就是空,@代表參數银室,這里指的是id(self)涂佃,這里:指的是方法SEL(_cmd),比如我再定義一個函數

int newMethod (idself, SEL _cmd,NSString*str) {
return100;
}

那么添加這個函數的方法就應該是
ass_addMethod([self class], @selector(newMethod), (IMP)newMethod, "i@:@");
4.如果在+ (BOOL)resolveInstanceMethod:(SEL)sel中沒有找到或者添加方法
那么,消息繼續(xù)往下傳遞到- (id)forwardingTargetForSelector:(SEL)aSelector
重新建個工程蜈敢,然后增加一個叫SecondViewController的類辜荠,里面添加一個- (void)secondVCMethod方法,如下
在SecondViewController中

@implementationSecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)secondVCMethod {
NSLog(@"This is secondVC method !");
}

目錄結構:
在ViewController中

@implementationViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(secondVCMethod)];
}

運行結果,崩潰

reason: '-[ViewController secondVCMethod]: unrecognized selector sent to instance 0x7fc3a8535c10'**

5.在ViewController中實現(xiàn) forwardingTargetForSelector:(SEL)aSelector 方法后,解決崩潰

@implementationViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(secondMethod)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
//先得到第二個控制器的類
Class class =NSClassFromString(@"SecondViewController");
//創(chuàng)建新的控制器
UIViewController*VC = [classnew];
//如果方法名相同,就實現(xiàn)方法
if(aSelector ==NSSelectorFromString(@"secondMethod")) {
NSLog(@"secondMethod will do it");
returnVC;
}
returnnil;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
return[superresolveInstanceMethod:sel];
}

運行結果

**2015-12-2414:00:34.168RuntimeTest2[3284:870957] secondVCdothis!**
**2015-12-2414:00:34.169RuntimeTest2[3284:870957] This is secondVC method !**

解釋:

- (id)forwardingTargetForSelector:(SEL)aSelector {
Class class =NSClassFromString(@"SecondViewController");
UIViewController*vc = class.new;
if(aSelector ==NSSelectorFromString(@"secondVCMethod")) {
NSLog(@"secondVC do this !");
returnvc;
}
returnnil;
}

在沒有找到- (void)secondVCMethod這個方法的時候抓狭,消息繼續(xù)傳遞侨拦,直到- (id)forwardingTargetForSelector:(SEL)aSelector,然后我在里面創(chuàng)建了一個SecondViewController的對象辐宾,并且判斷如果有這個方法狱从,就返回SecondViewController的對象。這個函數就是消息的轉發(fā)叠纹,在這兒我們成功的把消息傳給了SecondViewController季研,然后讓它來執(zhí)行,所以就執(zhí)行了那個方法誉察。同時与涡,也相當于完成了一個多繼承!
可以通過message forwarding 去實現(xiàn) one-to-N的delegates
runtime的使用場景
1.字典轉模型
篇幅一:

NSDictionary*dict =@{@"name":@"itcast",@"age":@18,@"height":@1.7};
unsignedintcount =0;
Ivar*Ivars =class_copyIvarList([CZPersonclass], &count);
CZPerson*person = [CZPersonnew];
for(inti =0; i < count; ++i) {
Ivarvar = Ivars[i];
constchar*str =ivar_getName(var);
NSMutableString*strName = [NSMutableStringstringWithString:[NSStringstringWithUTF8String:str]];
NSString*name =  [strNamesubstringFromIndex:1];
[personsetValue:dict[name]forKey:name];
}
NSLog(@"%@ %d %f",person.name,person.age,person.height);

篇幅二: 小馬哥
思路:利用運行時持偏,遍歷模型中所有屬性驼卖,根據模型的屬性名,去字典中查找key鸿秆,取出對應的值酌畜,給模型的屬性賦值。
步驟:提供一個NSObject分類卿叽,專門字典轉模型桥胞,以后所有模型都可以通過這個分類轉。

@implementationViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 解析Plist文件
NSString*filePath = [[NSBundlemainBundle] pathForResource:@"status.plist"ofType:nil];
NSDictionary*statusDict = [NSDictionarydictionaryWithContentsOfFile:filePath];
// 獲取字典數組
NSArray*dictArr = statusDict[@"statuses"];
// 自動生成模型的屬性字符串
//    [NSObject resolveDict:dictArr[0][@"user"]];
_statuses = [NSMutableArrayarray];
// 遍歷字典數組
for(NSDictionary*dictindictArr) {
Status *status = [Status modelWithDict:dict];
[_statuses addObject:status];
}
// 測試數據
NSLog(@"%@ %@",_statuses,[_statuses[0] user]);
}
@end
@implementationNSObject(Model)
+ (instancetype)modelWithDict:(NSDictionary*)dict
{
// 思路:遍歷模型中所有屬性-》使用運行時
// 0.創(chuàng)建對應的對象
idobjc = [[selfalloc] init];
// 1.利用runtime給對象中的成員屬性賦值
// class_copyIvarList:獲取類中的所有成員屬性
// Ivar:成員屬性的意思
// 第一個參數:表示獲取哪個類中的成員屬性
// 第二個參數:表示這個類有多少成員屬性考婴,傳入一個Int變量地址贩虾,會自動給這個變量賦值
// 返回值Ivar *:指的是一個ivar數組,會把所有成員屬性放在一個數組中沥阱,通過返回的數組就能全部獲取到缎罢。
/* 類似下面這種寫法
Ivar ivar;
Ivar ivar1;
Ivar ivar2;
// 定義一個ivar的數組a
Ivar a[] = {ivar,ivar1,ivar2};
// 用一個Ivar *指針指向數組第一個元素
Ivar *ivarList = a;
// 根據指針訪問數組第一個元素
ivarList[0];
*/
unsignedintcount;
// 獲取類中的所有成員屬性
Ivar *ivarList = class_copyIvarList(self, &count);
for(inti =0; i < count; i++) {
// 根據角標,從數組取出對應的成員屬性
Ivar ivar = ivarList[i];
// 獲取成員屬性名
NSString*name = [NSStringstringWithUTF8String:ivar_getName(ivar)];
// 處理成員屬性名->字典中的key
// 從第一個角標開始截取
NSString*key = [name substringFromIndex:1];
// 根據成員屬性名去字典中查找對應的value
idvalue = dict[key];
// 二級轉換:如果字典中還有字典,也需要把對應的字典轉換成模型
// 判斷下value是否是字典
if([value isKindOfClass:[NSDictionaryclass]]) {
// 字典轉模型
// 獲取模型的類對象策精,調用modelWithDict
// 模型的類名已知舰始,就是成員屬性的類型
// 獲取成員屬性類型
NSString*type = [NSStringstringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 生成的是這種@"@\"User\"" 類型 -》 @"User"  在OC字符串中 \" -> ",\是轉義的意思蛮寂,不占用字符
// 裁剪類型字符串
NSRangerange = [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 modelWithDict:value];
}
}

三級轉換:NSArray中也是字典,把數組中的字典轉換成模型.

// 判斷值是否是數組
if([value isKindOfClass:[NSArrayclass]]) {
// 判斷對應類有沒有實現(xiàn)字典數組轉模型數組的協(xié)議
if([selfrespondsToSelector:@selector(arrayContainModelClass)]) {
// 轉換成id類型酬蹋,就能調用任何對象的方法
ididSelf =self;
// 獲取數組中字典對應的模型
NSString*type =  [idSelf arrayContainModelClass][key];
// 生成模型
Class classModel =NSClassFromString(type);
NSMutableArray*arrM = [NSMutableArrayarray];
// 遍歷字典數組及老,生成模型數組
for(NSDictionary*dictinvalue) {
// 字典轉模型
idmodel =  [classModel modelWithDict:dict];
[arrM addObject:model];
}
// 把模型數組賦值給value
value = arrM;
}
}
if(value) {// 有值,才需要給模型的屬性賦值
// 利用KVC給模型中的屬性賦值
[objc setValue:value forKey:key];
}
}
returnobjc;
}
@end

2.歸檔解檔
篇幅一:

//歸檔
- (void)encodeWithCoder:(NSCoder*)enCoder{
//取得所有成員變量名
NSArray*properNames = [[selfclass] propertyOfSelf];
for(NSString*propertyNameinproperNames) {
//創(chuàng)建指向get方法
SELgetSel =NSSelectorFromString(propertyName);
//對每一個屬性實現(xiàn)歸檔
[enCoderencodeObject:[selfperformSelector:getSel]forKey:propertyName];
}
}
//解檔
- (id)initWithCoder:(NSCoder*)aDecoder{
//取得所有成員變量名
NSArray*properNames = [[selfclass] propertyOfSelf];
for(NSString*propertyNameinproperNames) {
//創(chuàng)建指向屬性的set方法
// 1.獲取屬性名的第一個字符范抓,變?yōu)榇髮懽帜?NSString*firstCharater = [propertyNamesubstringToIndex:1].uppercaseString;
// 2.替換掉屬性名的第一個字符為大寫字符骄恶,并拼接出set方法的方法名
NSString*setPropertyName = [NSStringstringWithFormat:@"set%@%@:",firstCharater,[propertyNamesubstringFromIndex:1]];
SELsetSel =NSSelectorFromString(setPropertyName);
[selfperformSelector:setSelwithObject:[aDecoderdecodeObjectForKey:propertyName]];
}
returnself;
}

篇幅二:
原理描述:用runtime提供的函數遍歷Model自身所有屬性,并對屬性進行encode和decode操作匕垫。
核心方法:在Model的基類中重寫方法:

- (void)encodeWithCoder:(NSCoder*)aCoder {
unsignedintoutCount;
Ivar * ivars = class_copyIvarList([selfclass], &outCount);
for(inti =0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString* key = [NSStringstringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[selfvalueForKey:key] forKey:key];
}
}
- (id)initWithCoder:(NSCoder*)aDecoder {
if(self= [superinit]) {
unsignedintoutCount;
Ivar * ivars = class_copyIvarList([selfclass], &outCount);
for(inti =0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString* key = [NSStringstringWithUTF8String:ivar_getName(ivar)];
[selfsetValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
returnself;
}

3.runtime交換方法
開發(fā)使用場景:系統(tǒng)自帶的方法功能不夠僧鲁,給系統(tǒng)自帶的方法擴展一些功能,并且保持原有的功能象泵。
方式一:繼承系統(tǒng)的類寞秃,重寫方法.
方式二:使用runtime,交換方法.

@implementationViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 需求:給imageNamed方法提供功能,每次加載圖片就判斷下圖片是否加載成功偶惠。
// 步驟一:先搞個分類春寿,定義一個能加載圖片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
// 步驟二:交換imageNamed和imageWithName的實現(xiàn),就能調用imageWithName忽孽,間接調用imageWithName的實現(xiàn)绑改。
UIImage*image = [UIImageimageNamed:@"123"];
}
@end
@implementationUIImage(Image)
// 加載分類到內存的時候調用
+ (void)load
{
// 交換方法
// 獲取imageWithName方法地址
Method imageWithName = class_getClassMethod(self,@selector(imageWithName:));
// 獲取imageWithName方法地址
Method imageName = class_getClassMethod(self,@selector(imageNamed:));
// 交換方法地址,相當于交換實現(xiàn)方式
method_exchangeImplementations(imageWithName, imageName);
}
// 不能在分類中重寫系統(tǒng)方法imageNamed兄一,因為會把系統(tǒng)的功能給覆蓋掉厘线,而且分類中不能調用super.
// 既能加載圖片又能打印
+ (instancetype)imageWithName:(NSString*)name
{
// 這里調用imageWithName绎晃,相當于調用imageName
UIImage*image = [selfimageWithName:name];
if(image == nil) {
NSLog(@"加載空的圖片");
}
returnimage;
}
@end

4.動態(tài)添加方法 (可以用于 “代理”)
開發(fā)使用場景:如果一個類方法非常多立叛,加載類到內存的時候也比較耗費資源,需要給每個方法生成映射表绪商,可以使用動態(tài)給某個類蹋盆,添加方法解決费薄。
經典面試題:有沒有使用performSelector,其實主要想問你有沒有動態(tài)添加過方法栖雾。
簡單使用

@implementationViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *p = [[Person alloc] init];
// 默認person,沒有實現(xiàn)eat方法伟众,可以通過performSelector調用析藕,但是會報錯。
// 動態(tài)添加方法就不會報錯
[p performSelector:@selector(eat)];
}
@end
@implementationPerson
// void(*)()
// 默認方法都有兩個隱式參數凳厢,
voideat(idself,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 當一個對象調用未實現(xiàn)的方法账胧,會調用這個方法處理,并且會把對應的方法列表傳過來.
// 剛好可以用來判斷竞慢,未實現(xiàn)的方法是不是我們想要動態(tài)添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if(sel ==@selector(eat)) {
// 動態(tài)添加eat方法
// 第一個參數:給哪個類添加方法
// 第二個參數:添加方法的方法編號
// 第三個參數:添加方法的函數實現(xiàn)(函數地址)
// 第四個參數:函數的類型,(返回值+參數類型) v:void @:對象->self :表示SEL->_cmd
class_addMethod(self,@selector(eat), eat,"v@:");
}
return[superresolveInstanceMethod:sel];
}
@end

5.動態(tài)添加屬性
原理:給一個類聲明屬性治泥,其實本質就是給這個類添加關聯(lián)筹煮,并不是直接把這個值的內存空間添加到類存空間。

@implementationViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 給系統(tǒng)NSObject類動態(tài)添加屬性name
NSObject*objc = [[NSObjectalloc] init];
objc.name=@"小碼哥";
NSLog(@"%@",objc.name);
}
@end
// 定義關聯(lián)的key
staticconstchar*key ="name";
@implementationNSObject(Property)
- (NSString*)name
{
// 根據關聯(lián)的key居夹,獲取關聯(lián)的值败潦。
returnobjc_getAssociatedObject(self, key);
}
- (void)setName:(NSString*)name
{
// 第一個參數:給哪個對象添加關聯(lián)
// 第二個參數:關聯(lián)的key,通過這個key獲取
// 第三個參數:關聯(lián)的value
// 第四個參數:關聯(lián)的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

6.Json到Model的轉化
原理描述:用runtime提供的函數遍歷Model自身所有屬性准脂,如果屬性在json中有對應的值劫扒,則將其賦值。
核心方法:在NSObject的分類中添加方法:

- (instancetype)initWithDict:(NSDictionary*)dict {
if(self= [selfinit]) {
//(1)獲取類的屬性及屬性對應的類型
NSMutableArray* keys = [NSMutableArrayarray];
NSMutableArray* attributes = [NSMutableArrayarray];
/*
* 例子
* name = value3 attribute = T@"NSString",C,N,V_value3
* name = value4 attribute = T^i,N,V_value4
*/
unsignedintoutCount;
objc_property_t * properties = class_copyPropertyList([selfclass], &outCount);
for(inti =0; i < outCount; i ++) {
objc_property_t property = properties[i];
//通過property_getName函數獲得屬性的名字
NSString* propertyName = [NSStringstringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//通過property_getAttributes函數可以獲得屬性的名字和@encode編碼
NSString* propertyAttribute = [NSStringstringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//立即釋放properties指向的內存
free(properties);
//(2)根據類型給屬性賦值
for(NSString* keyinkeys) {
if([dict valueForKey:key] == nil)
continue;
[self set Value:[dict valueForKey:key] forKey:key];
}
}
return self;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末狸膏,一起剝皮案震驚了整個濱河市沟饥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌湾戳,老刑警劉巖贤旷,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異砾脑,居然都是意外死亡幼驶,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門拦止,熙熙樓的掌柜王于貴愁眉苦臉地迎上來县遣,“玉大人,你說我怎么就攤上這事汹族∠羟螅” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵顶瞒,是天一觀的道長夸政。 經常有香客問我,道長榴徐,這世上最難降的妖魔是什么守问? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮坑资,結果婚禮上耗帕,老公的妹妹穿的比我還像新娘。我一直安慰自己袱贮,他們只是感情好仿便,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般嗽仪。 火紅的嫁衣襯著肌膚如雪荒勇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天闻坚,我揣著相機與錄音沽翔,去河邊找鬼。 笑死窿凤,一個胖子當著我的面吹牛仅偎,可吹牛的內容都是我干的。 我是一名探鬼主播卷玉,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼哨颂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了相种?” 一聲冷哼從身側響起威恼,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寝并,沒想到半個月后箫措,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡衬潦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年斤蔓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片镀岛。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡弦牡,死狀恐怖,靈堂內的尸體忽然破棺而出漂羊,到底是詐尸還是另有隱情驾锰,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布走越,位于F島的核電站椭豫,受9級特大地震影響,放射性物質發(fā)生泄漏旨指。R本人自食惡果不足惜赏酥,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谆构。 院中可真熱鬧裸扶,春花似錦、人聲如沸搬素。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蔗蹋。三九已至何荚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間猪杭,已是汗流浹背餐塘。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留皂吮,地道東北人戒傻。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像蜂筹,于是被迫代替她去往敵國和親需纳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內容

  • 轉至元數據結尾創(chuàng)建: 董瀟偉艺挪,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,715評論 0 9
  • OC是一門動態(tài)語言不翩,它將很多靜態(tài)語言在編譯和鏈接時期做的事情推遲到了運行時來處理,這就意味著它不僅需要一個編譯器麻裳,...
    zziazm閱讀 209評論 0 0
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,557評論 33 466
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言口蝠,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,195評論 0 7
  • 我們部門要招聘一個新人津坑,掛和我一樣的title,分擔我的工作妙蔗。 剛進公司的時候這個部門只有我一個人,靠著大小bos...
    綿綿piu閱讀 404評論 3 1