iOS学习笔记【一】——对象相关

iOS学习笔记【一】——对象相关

只做简单笔记📝 详细请戳标题链接🔗

OC对象的本质

// 实例对象
struct NSObject_IMPL {
    Class isa;
    成员变量具体值...
};
// 类对象
struct _class_t {
    struct _class_t *isa;
    struct _class_t *superclass;
    void *cache;
    void *vtable;
    struct _class_ro_t *ro;
};
struct _class_ro_t {
    unsigned int flags;
    unsigned int instanceStart;
    unsigned int instanceSize;
    unsigned int reserved;
    const unsigned char *ivarLayout;
    const char *name;  //类名
    const struct _method_list_t *baseMethods; // 方法(类对象存储实例方法,元类对象存储类方法)
    const struct _objc_protocol_list *baseProtocols; // 协议
    const struct _ivar_list_t *ivars;  //成员变量(类型/名称等)
    const unsigned char *weakIvarLayout;
    const struct _prop_list_t *properties;  //属性
};

在这里插入图片描述
在这里插入图片描述

class_ro_t 和 class_rw_t

  • class_ro_t存放的是编译期间就确定的,不可改变,objc_class中的bits的data部分存放着该结构体的地址;
  • 而class_rw_t是在runtime时才确定,class_rw_t结构体内有一个指向class_ro_t结构体的指针,并将当前类的分类的这些属性、方法等拷贝到其中,并更新data的内容。所以可以说class_rw_t是class_ro_t的超集。
  • 使用class_ro_t结构体,减少Dirty Memory的使用,提升性能。
  • Clean Memory :被加载后就不会再变化的内存。例如,class_ro_t就是 Clean Memory ,因为它是只读的。
  • Dirty Memory :在进程运行时会发生变化的内存。类结构体一旦被使用就是 Dirty Memory ,因为运行时会写入新的数据,例如它的方法缓存部分。
  • Dirty Memory 比 Clean Memory 代价更昂贵,因为在进程运行的整个过程中,都需要被保留; Clean Memory 则可以为其他事情滕出空间,因为当我们需要时,系统总是可以很容易地从磁盘中重新加载它。
    在这里插入图片描述

一个NSObject对象占用多少内存

系统分配了16个字节给NSObject对象(通过malloc_size函数获得,CoreFoundation框架规定所有的对象至少要16个字节,内存对齐),但NSObject对象内部只使用了8个字节(isa指针)的空间(64bit环境下,可以通过class_getInstanceSize函数获得)

struct NSObject_IMPL {
    Class isa;
};

@property实现

合成存取方法:编译器为属性自动生成带下划线的成员变量、getter方法、setter方法三个内容;如果同时重写了setter和getter的话,那么系统就不会帮我们生成成员变量;若在分类中声明property变量,将不会自动生成上面三个内容,可以使用关联对象实现。
property相较于成员变量的好处:自动合成getter和setter方法;更好的声明一组方法,命名约定;属性关键词能够传递出相关行为的额外信息。

@synthesize、@dynamic关键字

  • @synthesize,写在@implementation中,主要用来让属性和某个成员变量绑定。声明的属性=变量,将属性的setter/getter方法作用于这个变量,系统自动生成setter和getter方法。如果没有指定成员变量的名称会自动生成一个属性同名的成员变量
  • @property声明一个属性publicName,默认的就是@synthesize publicName = _publicName;
  • @dynamic,告诉系统不要给我们自动生成setter,getter方法, 一般用在给category添加属性时用到它。

属性关键词

  • atomic/nonatomic:若atomic修饰,系统自动生成的getter/setter方法会进行加锁操作(自旋锁),可以理解为读写锁。这种安全仅仅是get/set的读写安全,但是并不保证线程安全。
  • strong表示指向并拥有该对象,其修饰的对象引用计数会增加1,该对象只要引用计数不为0则不会被销毁。
  • weak表示指向但不拥有该对象,其修饰的对象引用计数不会增加。无需手动设置,该对象会自行在内存中销毁。
  • assign表示对属性只进行简单的赋值操作,不更改新值和旧值的引用计数,常用于基本数据类型,等同于unsafe_unretained。
  • weak 一般用来修饰对象,assign一般用来修饰基本数据类型。assign和weak一样,表示的是对象的一种弱引用关系,但是当对象被释放后,指针的地址依然存在,造成野指针,在堆上容易造成崩溃,而栈上的内存系统会自动处理,不会造成野指针。
  • copy与strong类似。不同之处是strong的复制是多个指针指向同一个地址,而copy的复制每次会在内存中拷贝一份对象,指针指向不同地址,一般用来修饰有对应可变类型子类的对象,防止使用可变对象修改不可变对象。
  • Objective-C 中,基本数据类型的默认关键字是atomic, readwrite, assign;普通属性的默认关键字是atomic, readwrite, strong。

copy 和 muteCopy方法

  • copy:对于可变对象为深复制,引用计数不改变;对于不可变对象是浅复制,相当于retain,引用计数每次加一。始终返回一个不可变对象。
  • mutableCopy:始终是深复制,引用计数不改变。始终返回一个可变对象。
    在这里插入图片描述

category分类和extension扩展

category

  • 使用好处:减少单个文件的体积;把不同的功能组织到不同的category里;由多个开发者共同完成一个类;按需加载想要的category;声明私有方法。
  • category中添加成员变量是无法编译通过的,但添加属性是可以编译通过的。
  • category只能扩充方法,不能扩充成员变量。因为 Category是在运行时期间决定的,而成员变量的内存布局已经在编译阶段确定好了,如果在运行时阶段添加成员变量的话,就会破坏原有类的内存布局。
  • category中@property只会生成setter和getter的声明,不会生成setter和getter的实现以及成员变量。
  • 如果category中的方法和类中原有方法同名,运行时会优先调用category中的方法;如果多个category中存在同名的方法,运行时到底调用哪个方法由编译器决定,最后一个参与编译的方法会被调用(运行时加载,不是在编译时)
  • Category编译之后的底层结构是struct category_t,在程序运行的时候,runtime会将Category的数据,合并到类信息中
typedef struct category_t {
    const char *name;  //类的名字
    classref_t cls;  //类
    struct method_list_t *instanceMethods;  //category中所有给类添加的实例方法的列表
    struct method_list_t *classMethods;  //category中所有添加的类方法的列表
    struct protocol_list_t *protocols;  //category实现的所有协议的列表
    struct property_list_t *instanceProperties;  //category中添加的所有属性
} category_t;
  • Category中的的方法、属性、协议附加到类上的操作,是在 + load 方法执行之前进行的。也就是说,在 + load 方法执行之前,类中就已经加载了 Category中的的方法、属性、协议。

extension

  • extension只存在于一个.h文件中,或只能寄生于一个类的.m文件中。
  • extension一般用于声明私有方法,私有属性,私有成员变量。
  • extension在编译期决议,它就是类的一部分,但是category则完全不一样,它是在运行期决议的。
  • extension可以添加实例变量,而category不可以。
  • extension和category都可以添加属性,但是category的属性不能生成成员变量和getter、setter方法的实现。

关联对象

Q:如何给分类添加成员变量?
A:通过runtime的方式间接实现添加成员变量。

objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); // 添加属性
objc_getAssociatedObject(id object, const void *key); // 获得属性
objc_removeAssociatedObjects(id object); // 移除所有关联对象
// 属性保存策略
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,  // 指定一个弱引用相关联的对象
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  // 指定相关的对象被复制,非原子性
    OBJC_ASSOCIATION_RETAIN = 01401,  // 指定相关对象的强引用,原子性
    OBJC_ASSOCIATION_COPY = 01403     // 指定相关的对象被复制,原子性   
};
AssociationsManager // 持有AssociationsHashMap
AssociationsHashMap // <obj, ObjectAssociationMap>
ObjectAssociationMap // <key,ObjcAssociation>
ObjcAssociation // (value, policy)
  • 关联对象并不是存储在被关联对象本身内存中,而是存储在一个全局的统一的AssociationsManager中。
  • 一个实例对象就对应一个ObjectAssociationMap,而ObjectAssociationMap中存储着多个此实例对象的关联对象的key以及ObjcAssociation,为ObjcAssociation中存储着关联对象的value和policy策略。
    在这里插入图片描述
  • Q:存储策略中没有weak关键字?
disguised_ptr_t disguised_object = DISGUISE(object);
// object经过DISGUISE函数被转化为了disguised_ptr_t类型的disguised_object。
  • A:weak修饰的属性,当没有拥有对象之后就会被销毁,并且指针置为nil。在map中存在值object对应的AssociationsHashMap,但是因为object地址已经被置位nil,会造成坏地址访问,而无法根据object对象的地址转化为disguised_object了。
  • 如何实现
    block、包装类

粗糙完结 撒花

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值