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

你要知道的@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方法。

有如下代码

#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文件中可以查找到如下部分的代码

#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指针变量

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

而这个Class就代表类对象。

/// 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类所需的所有东西,包括属性、方法列表、版本号等一系列信息。

接下来的

extern "C" unsigned long OBJC_IVAR_$_Person$_cjmName;
extern "C" unsigned long OBJC_IVAR_$_Person$_cjmAge;

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

struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        NSString *_cjmName;
        NSUInteger _cjmAge;
};

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

static NSString * _I_Person_cjmName(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_cjmName)); }

这句代码就是属性cjmName的getter方法,可以看出,使用了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); 
}

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

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实例变量的。

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

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的实例变量描述。

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方法的描述。

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的处理方法,以及几种修饰符的实现方式。也能够对其有一个更深入的理解。

备注

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

Map排序

Map排序的方式有很多种,这里记录下自己总结的两种比较常用的方式:按键排序(sort by key), 按值排序(sort by value)。 按键排序(...

23280
来自专栏liukaili_666888999

@property (copy) NSMutableArray *array; 这个写法会出什么问题

11260
来自专栏Java帮帮-微信公众号-技术文章全总结

Java基础-day06-知识点回顾与练习

Java基础-day06-知识点回顾与练习 1.求和案例 ? 实现代码: package StudentJavaSEday06; public class De...

32930
来自专栏iOS技术杂谈

iOS block探究(一): 基础详解你要知道的block都在这里

你要知道的block都在这里 转载请注明出处 https://cloud.tencent.com/developer/user/1605429 本文大纲 blo...

34080
来自专栏wOw的Android小站

[Objective-C] id类型和instancetype类型

id数据类型可以存储任何类型的对象。可以说,它是一般对象类型。 例如可以声明一个为id类型的变量:

11210
来自专栏数据结构与算法

4927 线段树练习5

时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解  查看运行结果 题目描述 Description 有n个数和5种操作...

371140
来自专栏西安-晁州

TypeScript学习笔记之类

TypeScript的类,简单地定义如下: class Person { x: number; // 默认为public类型 y: ...

22200
来自专栏TechBox

浅析对象等同性判断

23040
来自专栏赵俊的Java专栏

记一道 Java 综合面试题

21420
来自专栏数据结构与算法

codevs4919 线段树练习4

 时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解  查看运行结果 题目描述 Description 给你N个数,有两...

28350

扫码关注云+社区

领取腾讯云代金券