前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS @property探究(二): 深入理解你要知道的@property都在这里

iOS @property探究(二): 深入理解你要知道的@property都在这里

作者头像
WWWWDotPNG
发布2018-04-10 11:34:06
1K0
发布2018-04-10 11:34:06
举报
文章被收录于专栏:iOS技术杂谈

你要知道的@property都在这里

转载请注明出处 https://cloud.tencent.com/developer/user/1605429

上一篇文章iOS @property探究(一):基础详解介绍了@property的基本原理和使用方法,以及相关修饰符详解。 本文将会深入底层探究@property的本质。 在进入正题之前,先介绍一个clang编译器的命令

clang -rewrite-objc main.m 这个命令用于clang重写.m文件.cpp文件

@property深入代码理解

我们都知道

@property = ivar + getter + setter

ivar就是实例变量,编译器会帮我们自动生成名字为'_属性名'这样的实例变量,同时也会自动生成gettersetter方法。

有如下代码

代码语言:javascript
复制
#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString* cjmName;
@property (nonatomic, assign) NSUInteger cjmAge;

@end

@implementation Person

@synthesize cjmName = _cjmName;
@synthesize cjmAge = _cjmAge;

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        p.cjmName = @"JIaming Chen";
        p.cjmAge = 22;
    }
    return 0;
}

使用上述命令后生成的.cpp文件中可以查找到如下部分的代码

代码语言:javascript
复制
#ifndef _REWRITER_typedef_Person
#define _REWRITER_typedef_Person
typedef struct objc_object Person;
typedef struct {} _objc_exc_Person;
#endif

extern "C" unsigned long OBJC_IVAR_$_Person$_cjmName;
extern "C" unsigned long OBJC_IVAR_$_Person$_cjmAge;
struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        NSString *_cjmName;
        NSUInteger _cjmAge;
};


// @property (nonatomic, copy) NSString* cjmName;
// @property (nonatomic, assign) NSUInteger cjmAge;

/* @end */


// @implementation Person

// @synthesize cjmName = _cjmName;
static NSString * _I_Person_cjmName(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_cjmName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_Person_setCjmName_(Person * self, SEL _cmd, NSString *cjmName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _cjmName), (id)cjmName, 0, 1); }

// @synthesize cjmAge = _cjmAge;
static NSUInteger _I_Person_cjmAge(Person * self, SEL _cmd) { return (*(NSUInteger *)((char *)self + OBJC_IVAR_$_Person$_cjmAge)); }
static void _I_Person_setCjmAge_(Person * self, SEL _cmd, NSUInteger cjmAge) { (*(NSUInteger *)((char *)self + OBJC_IVAR_$_Person$_cjmAge)) = cjmAge; }


// @end


int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName("setCjmName:"), (NSString *)&__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_4b2631_mi_0);
        ((void (*)(id, SEL, NSUInteger))(void *)objc_msgSend)((id)p, sel_registerName("setCjmAge:"), (NSUInteger)22);
    }
    return 0;
}

以上代码就是编译器为我们生成的C代码,现在一一讲解几个比较重要的部分。

typedef struct objc_object Person;

编译器将struct objc_object重命名为我们自定义的Person类,struct objc_object结构体只有一个类型为Classisa指针变量

代码语言:javascript
复制
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

而这个Class就代表类对象。

代码语言:javascript
复制
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

/#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

(由于篇幅问题,本文不详细讲解OC类的实现细节,如有兴趣可以参考iOS 深入代码理解类对象)你只需要知道这个Person就是我们创建的类对象就好了,这个类对象包含了Person类所需的所有东西,包括属性、方法列表、版本号等一系列信息。

接下来的

代码语言:javascript
复制
extern "C" unsigned long OBJC_IVAR_$_Person$_cjmName;
extern "C" unsigned long OBJC_IVAR_$_Person$_cjmAge;

定义了两个unsigned long类型的变量,这两个变量代表一个偏移量,值这两个实例变量在内存中存储的偏移量,通过这两个值就能够在内存中定位到这两个实例变量的位置。 这两个值是运行时计算出偏移量硬编码(hard code)写入的,这样的好处在于,如果你使用了一个库,这个库的类定义比较旧,而链接的代码使用的是版本较新的代码,增加了几个实例变量,你的程序运行时也不会报错,因为偏移量是通过运行时计算出来的,仍旧能够找到相应的位置。如果不使用合成存取方法定义实例变量而使用手工的方式创建,这个偏移量就是编译器计算出硬编码写到代码中的,如果类定义和链接库的版本不一致则可能发生指针错误,因此鼓励大家尽量都使用合成存取方法。

代码语言:javascript
复制
struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        NSString *_cjmName;
        NSUInteger _cjmAge;
};

该结构体就是Person类实现,struct NSObject_IMPL结构体只有一个Class isa结构体指针变量,指向类对象,用于获取Person类的方法列表、实例变量列表、属性列表、版本等信息。可以看出,在底层代码中编译器帮我们自动生成了名为_cjmName_cjmAge的两个实例变量,如果我们修改@synthesize cjmName = _cjmName为其他名称则这列会生成相应名称的实例变量。

代码语言:javascript
复制
static NSString * _I_Person_cjmName(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_cjmName)); }

这句代码就是属性cjmName的getter方法,可以看出,使用了OBJC_IVAR_$_Person$_cjmName偏移量来计算实例变量的存储位置并返回。

代码语言:javascript
复制
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_Person_setCjmName_(Person * self, SEL _cmd, NSString *cjmName) {
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _cjmName), (id)cjmName, 0, 1); 
}

这两句代码是属性cjmName的setter方法,使用__OFFSETOFIVAR__(TYPE, MEMBER)宏定义来计算偏移量,上文指的偏移量都是通过该宏定义计算而来,计算出偏移量后使用objc_setProperty来设置实例变量_cjmName的值。 将属性cjmName的修饰符改为strong后再次查看重写的setter代码:

代码语言:javascript
复制
static void _I_Person_setCjmName_(Person * self, SEL _cmd, NSString *cjmName) { 
    (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_cjmName)) = cjmName; 
}

与上文代码相比发现,没有声明objc_setProperty方法也没有使用该方法,而是直接计算出实例变量的偏移量后将指针赋给实例变量。由此就可以看出修饰符copystrong底层代码的区别。

同样的可以将修饰符改为assignunsafe_unretainedweak来查看生成的代码,结果都同Strong一致,这就解释了底层代码是如何copy实例变量的。

再来看看以下几个结构体:

代码语言:javascript
复制
struct _ivar_t {
        unsigned long int *offset;  // pointer to ivar offset location
        const char *name;
        const char *type;
        unsigned int alignment;
        unsigned int  size;
};

static struct /*_ivar_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count;
        struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_ivar_t),
        2,
        {{(unsigned long int *)&OBJC_IVAR_$_Person$_cjmName, "_cjmName", "@\"NSString\"", 3, 8},
         {(unsigned long int *)&OBJC_IVAR_$_Person$_cjmAge, "_cjmAge", "Q", 3, 8}}
};

struct _ivar_t结构体表示每一个实例变量,记录了偏移值、名称、类型、对齐方式和大小,用于描述每一个实例变量。 struct _ivar_list_t结构体表示类的实例变量列表,记录了实例变量的大小、个数、以及每一个实例变量描述。 我们每在类中加入一个属性,编译器都会在_ivar_list_t变量中加入一个_ivar_t的实例变量描述。

代码语言:javascript
复制
struct _objc_method {
        struct objc_selector * _cmd;
        const char *method_type;
        void  *_imp;
};

static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[4];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        4,
        {{(struct objc_selector *)"cjmName", "@16@0:8", (void *)_I_Person_cjmName},
        {(struct objc_selector *)"setCjmName:", "v24@0:8@16", (void *)_I_Person_setCjmName_},
        {(struct objc_selector *)"cjmAge", "Q16@0:8", (void *)_I_Person_cjmAge},
        {(struct objc_selector *)"setCjmAge:", "v24@0:8Q16", (void *)_I_Person_setCjmAge_}}
};

struct _objc_method结构体描述了每一个实例方法,包括一个SEL类型的指针、方法类型和方法实现。 struct _method_list_t结构体表示类的实例方法列表,记录了每一个实例方法的大小、实例方法个数以及具体的实例方法描述,每加入一个属性则会在_method_list_t中增加settergetter方法的描述。

代码语言:javascript
复制
struct _prop_t {
        const char *name;
        const char *attributes;
};

static struct /*_prop_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count_of_properties;
        struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        2,
        {{"cjmName","T@\"NSString\",C,N,V_cjmName"},
        {"cjmAge","TQ,N,V_cjmAge"}}
};

struct _prop_t结构体描述了每一个属性,包括名称和属性值。 struct _prop_list_t结构体表示属性列表,记录了每一个属性的大小、属性个数以及具体的属性描述,每加入一个属性则会在_prop_list_t中增加_prop_t属性描述。 从结构体中的值不难看出,属性描述中的T@表示是类型对象后接类型名称,C表示copyN表示nonatomicV_cjmName表示实例变量。

总结

通过上述代码我们可以看出编译器对@property的处理方法,以及几种修饰符的实现方式。也能够对其有一个更深入的理解。

备注

由于作者水平有限,难免出现纰漏,如有问题还请不吝赐教。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017.03.21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 你要知道的@property都在这里
    • @property深入代码理解
      • 总结
        • 备注
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档