Runtime之增加(五)

在前面的內(nèi)容中曹宴,主要是介紹了Runtime所使用到的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)和消息轉(zhuǎn)發(fā)的流程。接下來將會介紹如何在運行時對代碼進行動態(tài)的修改弯屈。
這一節(jié)主要介紹添加厅缺。添加包括兩類:

  • 對所有的類生成的實例進行添加
  • 只對特定的某一個實例進行添加

類添加就是和平時在類文件中書寫的內(nèi)容一樣诀豁。它的作用域為所有由此類生成的實例對象娩践。而實例添加則是指對于當個類所生成的單個實例進行添加。例如使用對于實例(instance)使用setAssociateObject來關(guān)聯(lián)對象吨岭。對于這種只影響單個實例的添加辣辫,可以看成實例添加。本節(jié)只介紹類添加相關(guān)內(nèi)容葬馋。

添加類

如果想要生成一個類,可以使用:

objc_allocateClassPair([NSObject class],"DynamicClass",0);
//1.添加一個協(xié)議
//2.添加屬性和變量
//3.添加方法
objc_registerClassPair(DynamicClass);

動態(tài)生成一個類,僅僅只有兩步:分配空間和注冊蟀瞧。
在objc_allocateClassPair中,需要指定父類切端,類名和長度昌屉。實際使用過程中長度值一般都會填0。其他值我沒有試過竞帽,但是根據(jù)API上的解釋這個長度應(yīng)該是類中所有變量的總長度。具體參考蘋果API文檔抱虐。
在添加完協(xié)議、屬性谣沸、方法和后,需要將類注冊到Runtime中去,這個時候需要使用objc_registerClassPair举农。

添加協(xié)議

協(xié)議的生成和類的生成步驟其實也是一樣的。也是分成兩步:分配空間和注冊。

 Protocol *dynamicProtocol = objc_allocateProtocol("dynamicProcotol");
//1.添加方法聲明婚脱,包括required和optional
//2.添加屬性聲明。這里實際上還是添加的方法督函。
 objc_registerProtocol(dynamicProtocol);

協(xié)議也可以添加屬性聲明和方法聲明锋叨。這里需要區(qū)分一下在類中間添加屬性和方法與在協(xié)議中添加有什么樣的區(qū)別。

首先在討論區(qū)別前宛篇,需要區(qū)分屬性(property)娃磺、變量(variable)、合成(synthesize)這三個概念叫倍。

  • 變量是指需要實際配內(nèi)存的存儲區(qū)域偷卧。
  • 屬性是用來修飾變量的內(nèi)容。例如atomic,copy,strong,weak,assign等吆倦。
  • 合成是指為變量生成的getter和setter方法听诸。

對于一些老的OC教材里面能夠很清晰的看到這幾個的不同:

@interface MyClass:NSObject
{
  int foo;//變量
}
@property (nonatomic) int foo;//屬性
@end
@implementation MyClass
@synthesize foo; //合成
@end

在對屬性進行完區(qū)分以后,再來看下協(xié)議和類之間的區(qū)別:

  • 在Protocol中是無法有變量和屬性的蚕泽,只能夠有合成晌梨。而在Class中都可以有敛惊。
  • Protocol中都是方法聲明,沒有實現(xiàn)。而Class中需要有方法的實現(xiàn)宋税。

由此也就可以看出杰赛,Protocol就是Java中的接口确虱,只是定下了一個標準召川。但標準怎么要去實現(xiàn)就完全靠自己了使套。且Protocol只能添加方法厌杜,@property實際上也只是合成了getter和setter方法的聲明奉呛。

協(xié)議中添加合成方法聲明

實際上是合成getter和setter方法聲明计螺。


- (void)addProperty:(NSString *)propertyName toProtocol:(Protocol *)protocol
{
    //假設(shè)添加一個@property (nonatomic,copy) NSString *propertyName;的屬性
    objc_property_attribute_t type = {"T",[[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String]};//T = 類型
    objc_property_attribute_t ownership0 = {"C",""};//C = copy
    objc_property_attribute_t ownership1 = {"N",""};//N = nonatomic
    objc_property_attribute_t backingivar = {"V",[[NSString stringWithFormat:@"_%@",propertyName] UTF8String]};
    objc_property_attribute_t attrArray[] = {type,ownership0,ownership1,backingivar};
    //為協(xié)議添加一個property。
    protocol_addProperty(protocol, [propertyName UTF8String], attrArray, 4, YES, YES);
}

添加了聲明不能代表添加了方法瞧壮。所以動態(tài)添加以后還是無法使用對應(yīng)的方法登馒。由此感覺動態(tài)添加協(xié)議有一些雞肋。
看一下protocol_addProperty的聲明:

/** 
 * Adds a property to a protocol. The protocol must be under construction. 
 * 
 * @param proto The protocol to add a property to.
 * @param name The name of the property.
 * @param attributes An array of property attributes.
 * @param attributeCount The number of attributes in \e attributes.
 * @param isRequiredProperty YES if the property (accessor methods) is not optional. 
 * @param isInstanceProperty YES if the property (accessor methods) are instance methods. 
 *  This is the only case allowed fo a property, as a result, setting this to NO will 
 *  not add the property to the protocol at all. 
 */
OBJC_EXPORT void
protocol_addProperty(Protocol * _Nonnull proto, const char * _Nonnull name,
                     const objc_property_attribute_t * _Nullable attributes,
                     unsigned int attributeCount,
                     BOOL isRequiredProperty, BOOL isInstanceProperty)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);

最后的兩個參數(shù)時是否是required和是否是instanceProperty咆槽。

協(xié)議中添加方法聲明
//在協(xié)議中添加方法聲明
- (void)addMethod:(NSString *)methodName toProtocol:(Protocol *)protocol
{
    //1.相當于添加方法聲明陈轿,不需要方法的實現(xiàn)。
    //最后兩個參數(shù)是:是否是require方法罗晕,是否是實例方法济欢。
    protocol_addMethodDescription(protocol, @selector(methodName), "v@:", YES, YES);
}

和前面添加屬性也感覺差不多。也是僅僅只有一個聲明小渊,沒有實現(xiàn)。所以如果在由其對應(yīng)類中間直接使用相關(guān)方法將會奔潰茫叭。如果要使用酬屉,還是必須要類中間添加方法。

添加屬性

按照前面的內(nèi)容可以知道變量揍愁、屬性呐萨、合成三者的區(qū)別。在Runtime中莽囤,你需要分別添加這三個部分谬擦。

添加變量
- (BOOL)addVariable:(NSString *)varibleName toClass:(Class)class
{
    BOOL success;
    //1.添加指針類型的變量
    success =class_addIvar(class,[varibleName UTF8String],sizeof(NSString*),log2(sizeof(NSString *)),@encode(NSString *));
    //2.添加基礎(chǔ)類型的變量:int ,double, float等
    //添加 int basicVariable;變量
    return success && class_addIvar(class,"basicVariable",sizeof(int),sizeof(int),@encode(int));
}

現(xiàn)在我們再來看一下class_addIvar定義:

/** 
 * Adds a new instance variable to a class.
 * 
 * @return YES if the instance variable was added successfully, otherwise NO 
 *         (for example, the class already contains an instance variable with that name).
 *
 * @note This function may only be called after objc_allocateClassPair and before objc_registerClassPair. 
 *       Adding an instance variable to an existing class is not supported.
 * @note The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
 * @note The instance variable's minimum alignment in bytes is 1<<align. The minimum alignment of an instance 
 *       variable depends on the ivar's type and the machine architecture. 
 *       For variables of any pointer type, pass log2(sizeof(pointer_type)).
 */
OBJC_EXPORT BOOL
class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, 
              uint8_t alignment, const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

在添加變量的時候,一定需要注意添加基礎(chǔ)變量和添加id類型的變量是不同的朽缎。主要不同是在參數(shù)alignment上惨远。基礎(chǔ)變量直接傳sizeof即可话肖,而類型變量需要傳入log2(sizeof)的值北秽。

添加屬性
//動態(tài)添加一個屬性
- (BOOL)addProperty:(NSString *)propertyName toClass:(Class )class
{
    //假設(shè)添加一個@property (nonatomic,copy) NSString *propertyName;
    objc_property_attribute_t type = {"T",[[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String]};//T = 類型
    objc_property_attribute_t ownership0 = {"C",""};//C = copy
    objc_property_attribute_t ownership1 = {"N",""};//N = nonatomic
    objc_property_attribute_t backingivar = {"V",[[NSString stringWithFormat:@"_%@",propertyName] UTF8String]};
    objc_property_attribute_t attrArray[] = {type,ownership0,ownership1,backingivar};
    return class_addProperty(class, [propertyName UTF8String], attrArray, 4);
}

和在協(xié)議中添加屬性的方式一樣。

添加方法

無論是getter最筒、setter方法還是普通方法贺氓,它們的添加方式都是相同的。具體的例子:

//C語言方法
void dynamicMethod(id class,SEL cur,id para){
    if ([para isKindOfClass:[NSString class]]){
        NSLog(@"這是一個動態(tài)添加的方法,參數(shù)為:%@",(NSString *)para);
    }
}

- (void)addMethod:(NSString *)methodName toClass:(Class)class
{
    //1.在添加一個方法之前床蜘,必要要能夠知道函數(shù)地址和函數(shù)簽名辙培。
    //如果沒有函數(shù)地址和簽名,就沒辦法添加方法邢锯。因為方法是依靠IMP執(zhí)行的扬蕊。
    class_addMethod(class, @selector(methodName), (IMP)dynamicMethod, "v@:@");
}

在看一下定義:

/** 
 * Adds a new method to a class with a given name and implementation.
 * 
 * @param cls The class to which to add a method.
 * @param name A selector that specifies the name of the method being added.
 * @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
 * @param types An array of characters that describe the types of the arguments to the method. 
 * 
 * @return YES if the method was added successfully, otherwise NO 
 *  (for example, the class already contains a method implementation with that name).
 *
 * @note class_addMethod will add an override of a superclass's implementation, 
 *  but will not replace an existing implementation in this class. 
 *  To change an existing implementation, use method_setImplementation.
 */
OBJC_EXPORT BOOL
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

types類型 = 返回值類型編碼 + self編碼 + cmd編碼 +參數(shù)類型1編碼 + 參數(shù)類型2編碼 ...
self編碼固定為@,cmd編碼固定為:弹囚。所以第一二位其實是固定的厨相。


參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蛮穿,更是在濱河造成了極大的恐慌庶骄,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件践磅,死亡現(xiàn)場離奇詭異单刁,居然都是意外死亡,警方通過查閱死者的電腦和手機府适,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門羔飞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人檐春,你說我怎么就攤上這事逻淌。” “怎么了疟暖?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵卡儒,是天一觀的道長。 經(jīng)常有香客問我俐巴,道長骨望,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任欣舵,我火速辦了婚禮擎鸠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缘圈。我一直安慰自己劣光,他們只是感情好,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布准验。 她就那樣靜靜地躺著赎线,像睡著了一般。 火紅的嫁衣襯著肌膚如雪糊饱。 梳的紋絲不亂的頭發(fā)上垂寥,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音另锋,去河邊找鬼滞项。 笑死,一個胖子當著我的面吹牛夭坪,可吹牛的內(nèi)容都是我干的文判。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼室梅,長吁一口氣:“原來是場噩夢啊……” “哼戏仓!你這毒婦竟也來了疚宇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤赏殃,失蹤者是張志新(化名)和其女友劉穎敷待,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仁热,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡榜揖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了抗蠢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片举哟。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖迅矛,靈堂內(nèi)的尸體忽然破棺而出妨猩,到底是詐尸還是另有隱情,我是刑警寧澤秽褒,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布册赛,位于F島的核電站,受9級特大地震影響震嫉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜牡属,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一票堵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逮栅,春花似錦悴势、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至侥加,卻和暖如春捧存,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背担败。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工昔穴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人提前。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓吗货,卻偏偏與公主長得像,于是被迫代替她去往敵國和親狈网。 傳聞我的和親對象是個殘疾皇子宙搬,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容