类的方法属性探究下

类的属性

@interface HFStudent : NSObject

@property (nonatomic, strong) NSString *name;

@property (nonatomic, copy) NSString *nikeName;

@property (nonatomic, assign) NSInteger age;

@end


@implementation HFStudent

@end
int main(int argc, char * argv[]) {
    HFStudent *stu = [HFStudent alloc];
}

上面是我们创建的类,接下来我们把他编程的cpp文件
#ifndef _REWRITER_typedef_HFStudent
#define _REWRITER_typedef_HFStudent
typedef struct objc_object HFStudent;
typedef struct {} _objc_exc_HFStudent;
#endif

extern "C" unsigned long OBJC_IVAR_$_HFStudent$_name;
extern "C" unsigned long OBJC_IVAR_$_HFStudent$_nikeName;
extern "C" unsigned long OBJC_IVAR_$_HFStudent$_age;
struct HFStudent_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_name;
    NSString *_nikeName;
    NSInteger _age;
};


// @property (nonatomic, strong) NSString *name;

// @property (nonatomic, copy) NSString *nikeName;

// @property (nonatomic, assign) NSInteger age;

/* @end */



// @implementation HFStudent


static NSString * _I_HFStudent_name(HFStudent * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_HFStudent$_name)); }
static void _I_HFStudent_setName_(HFStudent * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_HFStudent$_name)) = name; }

static NSString * _I_HFStudent_nikeName(HFStudent * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_HFStudent$_nikeName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_HFStudent_setNikeName_(HFStudent * self, SEL _cmd, NSString *nikeName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct HFStudent, _nikeName), (id)nikeName, 0, 1); }

static NSInteger _I_HFStudent_age(HFStudent * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_HFStudent$_age)); }
static void _I_HFStudent_setAge_(HFStudent * self, SEL _cmd, NSInteger age) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_HFStudent$_age)) = age; }
// @end

int main(int argc, char * argv[]) {
    HFStudent *stu = ((HFStudent *(*)(id, SEL))(void *)objc_msgSend((id)objc_getClass("HFStudent"), sel_registerName("alloc"));
}

从上面我们可以看到类定义在底层会被编译成结构体,而定义的属性会被定义成成员变量,我们在开发的时候,属性会自动生成get和set方法,而这边我看到底层的方法如下

static NSString * _I_HFStudent_name(HFStudent * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_HFStudent$_name)); }
static void _I_HFStudent_setName_(HFStudent * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_HFStudent$_name)) = name; }

static NSString * _I_HFStudent_nikeName(HFStudent * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_HFStudent$_nikeName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_HFStudent_setNikeName_(HFStudent * self, SEL _cmd, NSString *nikeName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct HFStudent, _nikeName), (id)nikeName, 0, 1); }

static NSInteger _I_HFStudent_age(HFStudent * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_HFStudent$_age)); }
static void _I_HFStudent_setAge_(HFStudent * self, SEL _cmd, NSInteger age) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_HFStudent$_age)) = age; }
首先我们看到get方法都是通过指针平移的方式获取到对应的值,而set方法有些细微的差异,可以看到setNikeName和其他的不一样,其他都是指针平移然后设置对象,而setNikeName里多了一个objc_setProperty

我们通过llvm代码找到objc_setProperty的

llvm::FunctionCallee getSetPropertyFn() {
    CodeGen::CodeGenTypes &Types = CGM.getTypes();
    ASTContext &Ctx = CGM.getContext();
    // void objc_setProperty (id, SEL, ptrdiff_t, id, bool, bool)
    CanQualType IdType = Ctx.getCanonicalParamType(Ctx.getObjCIdType());
    CanQualType SelType = Ctx.getCanonicalParamType(Ctx.getObjCSelType());
    CanQualType Params[] = {
        IdType,
        SelType,
        Ctx.getPointerDiffType()->getCanonicalTypeUnqualified(),
        IdType,
        Ctx.BoolTy,
        Ctx.BoolTy};
    llvm::FunctionType *FTy =
        Types.GetFunctionType(
          Types.arrangeBuiltinFunctionDeclaration(Ctx.VoidTy, Params));
    return CGM.CreateRuntimeFunction(FTy, "objc_setProperty");
  }
这边创建了objc_setProperty函数,追根溯源我们找到了如下方法
void
CodeGenFunction::generateObjCSetterBody(const ObjCImplementationDecl *classImpl,
                                        const ObjCPropertyImplDecl *propImpl,
                                        llvm::Constant *AtomicHelperFn)

其中里面有段代码如下
PropertyImplStrategy strategy(CGM, propImpl);
  switch (strategy.getKind()){...}
这边根据strategy的kind来跳转
case PropertyImplStrategy::GetSetProperty:
case PropertyImplStrategy::SetPropertyAndExpressionGet:
这边我就需要知道strategy的定义,看看里面的kind有做了什么事情, 方法很长,但是我们主要去找一下有没有copy,
PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,
                                     const ObjCPropertyImplDecl *propImpl) 
在此函数中我们找到如下代码
IsCopy = (setterKind == ObjCPropertyDecl::Copy);
if (IsCopy) {
    Kind = GetSetProperty;
    return;
  }

通过分析llvm代码,我们终于知道objc_setProperty函数的由来,当我们的属性由copy修饰时,llvm编译的时候会创建objc_setProperty来处理我们的set方法。

类方法归属分析

这边通过runtime提供的api来打印类和实例对象的方法

@interface HFPerson : NSObject
{
    NSObject *objc; 
    NSString *nickName;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSObject *obj;
- (void)sayHello;
+ (void)sayHappy;
- (void)sayHello{
    NSObject *obj;
    NSLog(@"HFPerson say : Hello!!!");
}
+ (void)sayHappy{
    NSLog(@"HFPerson say : Happy!!!");
}
以上是类的定义
通过class_copyMethodList我们可以打印出类里面的方法
void lgObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[I];
        //获取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        LGLog(@"Method, name: %@", key);
    }
    free(methods);
}
LGPerson *person = [LGPerson alloc];
Class pClass     = object_getClass(person);
lgObjc_copyMethodList(pClass);

const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
lgObjc_copyMethodList(metaClass);
结果:
Method, name: sayHello
Method, name: .cxx_destruct
Method, name: name
Method, name: setName:
Method, name: obj
Method, name: setObj:
***********************************
Method, name: sayHappy 
从结果中我们看到并没有sayHappy方法,因为sayHappy方法存放在元类里面

打印类里面是否有该方法
void lgInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
结果:lgInstanceMethod_classToMetaclass - 0x100004508-0x0-0x0-0x1000044a0
class_getInstanceMethod:获取实例方法,

void lgClassMethod_classToMetaclass(Class pClass){
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
结果输出:lgClassMethod_classToMetaclass-0x0-0x0-0x1000044a0-0x1000044a0

class_getClassMethod:获取类的方法,这边就有一个疑问了,为什么Method method4 = class_getClassMethod(metaClass, @selector(sayHappy)); 这边method4有值,而且跟method3一样。这边我们就要去看class_getClassMethod的方法实现了

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

// NOT identical to this->ISA when this is a metaclass
 Class getMeta() {
        if (isMetaClassMaybeUnrealized())
            return (Class)this;
        else
            return this->ISA();
  }

这边cls调用了getMeta方法,看这个方法名似乎是获取元类的,但是跟进去发现 NOT identical to this->ISA when this is a metaclass 意思是如果是元类,返回的是元类本身,所以这边的cls->getMeta() 还是HFPerson元类,返回的结果跟method3一样

void lgIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));// 0
    // sel -> imp 方法的查找流程 imp_farw
    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy)); // 0
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);

}
HFPerson *person = [HFPerson alloc];
Class pClass     = object_getClass(person);
lgIMP_classToMetaclass(pClass);
class_getMethodImplementation 获取方法实现
结果:
0x1000010e0-0x7fff6f9de300-0x7fff6f9de300-0x100001120

这边可以看到imp2和imp3是一样的,据我们刚刚class_getInstanceMethod的了解元类里面并不会有实例方法,而类里面也不会有类方法,这时候又需要去看源码了

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    lockdebug_assert_no_locks_locked_except({ &loadMethodLock });

    imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}

从源码中我们看到如果lookUpImpOrNilTryCache 返回的为null,则结果会返回_objc_msgForward,这也我们就清楚为什么会返回一样的地址了

补充 类编码

void lgTypes(void){
    NSLog(@"char --> %s",@encode(char));
    NSLog(@"int --> %s",@encode(int));
    NSLog(@"short --> %s",@encode(short));
    NSLog(@"long --> %s",@encode(long));
    NSLog(@"long long --> %s",@encode(long long));
    NSLog(@"unsigned char --> %s",@encode(unsigned char));
    NSLog(@"unsigned int --> %s",@encode(unsigned int));
    NSLog(@"unsigned short --> %s",@encode(unsigned short));
    NSLog(@"unsigned long --> %s",@encode(unsigned long long));
    NSLog(@"float --> %s",@encode(float));
    NSLog(@"bool --> %s",@encode(bool));
    NSLog(@"void --> %s",@encode(void));
    NSLog(@"char * --> %s",@encode(char *));
    NSLog(@"id --> %s",@encode(id));
    NSLog(@"Class --> %s",@encode(Class));
    NSLog(@"SEL --> %s",@encode(SEL));
    int array[] = {1,2,3};
    NSLog(@"int[] --> %s",@encode(typeof(array)));
    typedef struct person{
        char *name;
        int age;
    }Person;
    NSLog(@"struct --> %s",@encode(Person));
    
    typedef union union_type{
        char *name;
        int a;
    }Union;
    NSLog(@"union --> %s",@encode(Union));

    int a = 2;
    int *b = {&a};
    NSLog(@"int[] --> %s",@encode(typeof(b)));
}
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354