美文网首页
类的方法属性探究下

类的方法属性探究下

作者: Kates | 来源:发表于2021-06-25 14:31 被阅读0次

    类的属性

    @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

    相关文章

      网友评论

          本文标题:类的方法属性探究下

          本文链接:https://www.haomeiwen.com/subject/kqwnyltx.html