在前面的內(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編碼固定為:弹囚。所以第一二位其實是固定的厨相。
參考: