runtime是一個iOS開發(fā)必須要了解的一個特別厲害的技術, 以前總是認為靠近底層的東西在實際中并不實用, 可是最近在項目中真真的用到...眼淚拌飯的感覺, 查找一個網(wǎng)上的資料做了個惡補, 同時也記錄一下吧.
首先說道官方的文字翻譯:iOS中的黑魔法, RunTime簡稱運行時, 運行時的機制是OC的一大特點, 簡單的說就是動態(tài)調用, 我們在編譯的階段并不能決定真正調用哪個函數(shù), 只有在運行的時候才能決定調用的函數(shù), 在C中, 在編譯的時候如果函數(shù)并沒有實現(xiàn), 我們是不能編譯通過的, 在OC中如果沒有實現(xiàn), 我們可以編譯通過(前提是我們聲明過), 但是在運行的過程中會崩潰,RunTime最重要的機制就是消息機制, 本質就是對象發(fā)送消息objc_msgSend
接下來我們創(chuàng)建一個工程, 命名隨便吧... 使用消息機制, 我們要先導入#import<objc/message.h>頭文件
我們可能會出現(xiàn) Too many arguments to function call, expected 0, have 3 之類的報錯
修改成如上 默認是YES我們該為NO, 報錯不存在了
消息收發(fā)機制
我們先看它的第一個功能吧, 就是消息收發(fā)機制
新建一個工具類, 名稱叫做ContactBook吧(聯(lián)系本, 原諒我考了四次沒考過的四級)
[ContactBook call]; //的本質就是下面這個在運行時轉變?yōu)橄旅孢@段代碼
objc_msgSend(ContactBook, @selector(call));
// 類方法
// 第一種通過類名調用
[ContactBook Dididi];
// 第二種通過類對象調用
[[ContactBook class] Dididi];
// 用類名調用, 底層會自動把類型轉變?yōu)轭悓ο笳{用
objc_msgSend([ContactBook class], @selector(Dididi));
ContactBook類中
@implementation ContactBook
- (void)eat { NSLog(@"吃人, 放血"); }
+ (void)Dididi { NSLog(@"上車小伙子, 開車啦"); }
@end
我們平時創(chuàng)建的對象alloc init那種在運行的時候會轉換成objc_msgsend這種形式; 或許有人會認為, 我們通過alloc init 能夠實現(xiàn)的, 為什么要這么麻煩. 下面要說的肯定就不會這么想的,
方法交換
使用場景 :系統(tǒng)的API不能夠滿足我們的需求給系統(tǒng)擴充一些功能, 并保持原有的功能
有兩種方案 第一種也是大家經(jīng)常用的一種, 繼承系統(tǒng)的類 重寫方法
另外一種就是用RunTime交換方法
這個就是我們在項目中會用到的...
經(jīng)常會在項目中遇到展示圖片, 如果圖片是空我們要加一個站位的圖片
新建一個UIImage的Category
@implementation UIImage (Exchange)
//加載分類到內(nèi)存中調用這個api
+ (void)load { Method imageName = class_getClassMethod(self, @selector(imageNamed:)); Method imageWithName = class_getClassMethod(self, @selector(imageWithName:)); // 交換方法地址, 相當于交換實現(xiàn)方式 method_exchangeImplementations(imageName, imageWithName); }
+ (instancetype)imageWithName:(NSString *)name { // 這里調用imageWithName 相當于imageName UIImage *image = [self imageWithName:name]; if (image == nil) { [ UIImage *image = self imageWithName:@"占位圖片"] } return image; }
遍歷屬性
我們想要修改系統(tǒng)的私有屬性, 我們可以通過
unsigned int count = 0;//屬性個數(shù)!!! Ivar * ivars = class_copyIvarList( NSClassFromString(@"HKPerson"), &count); for(int i = 0 , i < count, i++) { Ivar ivar = ivars[i]; const char * name = ivar_getName(ivar); NSString * ocName = [NSString stringWithUTF8String:name]; NSLog(@"%@", ocName); }
我們可以找到想要修改的那個屬性名稱, 然后通過 KVC 給只讀屬性賦值...
這個我還用在歸檔跟反歸檔那個, 如果歸檔的屬性過多 我們還要一個一個的進行歸檔么?
利用這個完全不用
- (void)encodeWithCoder:(NSCoder *)coder { //歸檔 unsigned int count = 0;//屬性個數(shù)!!! Ivar * ivars = class_copyIvarList( [self class], &count); for (int i = 0; i < count; i ++) { Ivar ivar = ivars[i]; const char * name = ivar_getName(ivar); NSString * ocName = [NSString stringWithUTF8String:name]; [coder encodeObject:[self valueForKey:ocName] forKey:ocName]; } //在C語言里面一旦用到了 creat new copy 就需要釋放 free(ivars); }
- (instancetype)initWithCoder:(NSCoder *)coder { if (self = [super init]) { //解檔 unsigned int count = 0;//屬性個數(shù)!!! Ivar * ivars = class_copyIvarList( [self class], &count); for (int i = 0; i < count; i ++) { Ivar ivar = ivars[i]; const char * name = ivar_getName(ivar); NSString * ocName = [NSString stringWithUTF8String:name]; id value = [coder decodeObjectForKey:ocName]; [self setValue:value forKey:ocName]; } free(ivars); } return self; }
解決
動態(tài)添加方法
如果一個類方法非常多, 加載到內(nèi)存中的時候也比較耗費資源, 需要給每個方法生成映射表, 可以使用類動態(tài)的給某個類添加方法解決
新建一個Car類,
@implementation Car
void drive(id self, SEL sel){ NSLog(@"老司機帶帶我"); }
//當一個對象調用未實現(xiàn)的方法, 會調用這個處理并且會把對應的方法列表傳遞過來, 剛好我們可以用來判斷是不是我們想要動態(tài)添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{ if (sel == @selector(drive)) { // 動態(tài)添加drink方法 /* *參數(shù) *@param 參數(shù)1給哪個類添加方法 *@param 添加方法的方法編號 *@param 添加函數(shù)的地址(或者說是實現(xiàn)) *@param 函數(shù)地址的類型(返回值 + 參數(shù)類型) v代表void @代表對象 self :表示SEL->CMD */ class_addMethod(self, @selector(drive), drive, "v@:"); } return [super resolveInstanceMethod:sel]; }
@end
在控制器中這樣調用
[[[Car alloc] init] performSelector:@selector(drive)];
這樣就完成了在運行的時候添加一個方法
動態(tài)添加屬性
Extension跟Category大家都知道區(qū)別吧, 當我們需要在NSObject添加一個屬性, 并且每個子類都要有, 我們可以通過動態(tài)添加到NSObject中 Category(因為Extension中的子類并不能繼承, 不能完成需求)
新建一個NSObject的Category
static const char *key = "car"; @implementation NSObject (Property) *- (NSString *)car { // 關聯(lián)key return objc_getAssociatedObject(self, key); } *- (void)setCar:(NSString *)car { /* *@parma 參數(shù) *參數(shù)1 給那個對象添加關聯(lián) *參數(shù)2 關聯(lián)的key通過這個key獲取 *參數(shù)3 關聯(lián)的value *參數(shù)4 關聯(lián)的策略 */ objc_setAssociatedObject(self, key, car, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
然后在.h中寫上屬性名
@property (nonatomic, strong)NSString *car;
然后就可以在別的地方調用了
先到這里吧, 另外我還看到一些模型轉換的方法, 下次再整理一下吧...
各位加油, 向自己向往的境界前進