Objective-C的内存管理以及消息转发

前面与C++简单的进行了类和对象的对比,比较简单的语法,后续类上会有更复杂的语法,现在老讨论一下OC的内存管理。

1、说明

其实网上对OC的内存管理讨论了很多,这里也仅仅是自己的理解与总结,语言精练简单,可能会有错误。

2、引用计数

看到这个标题,如果了解过C++,那么就会想到C++的智能指针,在C++11中引入了share_ptr。OC与该智能指针在实现思路上的相同点如下:

  1. 采用引用计数方式
  2. 存在循环引用的问题
  3. 采用弱引用解决

于是在学习的时候,我通常会以智能指针为参考。(戳我进相关文章)
现在苹果对与内存管理发布了ARC(Automatic Reference Counting),也就是自动引用计数,在这之前一直是MRC(Manual Reference Counting)。
MRC内存管理遵循以下几个原则:

  1. 只要是自己new、alloc、copy的对象,都要进行一次release,也就是释放。
  2. retain某对象,就是把该对象的引用计数增加一,也就是持有者加一。
  3. retain和release的数量是相等的。

当然还有其他原则,但是我认为仍然是围绕着这些。后来有了ARC之后,释放的操作就交给编译器。于是retain、release等操作就没有了。上述的关键字在后续还会遇到。如果熟悉share_ptr,则会很容易理解这里的内存管理,因此不再赘述。对于关键字在后续property会描述。
前面提到了C++的智能指针,顺便提一提前面说为什么OC中的对象总是以指针的形式。在传递数据的时候有两种方式,一个是传值,一个是引用。

  1. 指针就是以引用的方式在传递数据,直接传递对象的地址,如果修改或者读取直接读取该对象,减少拷贝的过程,且节省空间。
  2. 另外,我们的对象通常是创建在堆上的,以C++为例,当传值的时候,首先会创建一个临时的对象,这个过程会调用类的拷贝构造,也就是会有拷贝的过程,但是在堆上申请空间的效率是低下的,而且频繁的创建对象容易产生内存碎片。
3、 对象模型

还记得在C++中如果有虚函数,则该类的成员会增加一个序表指针。(在OC中,我们知道每一个类都会有一个超类)在OC中,每个对象都会有一个isa的指针。在前面之所以说OC更为纯粹,其实在其对象模型上就可以提现出来。(可以参考C++对象模型,但是不同)
写下如下类:

@interface Test : NSObject
{
    NSString* name_;
}
@end
@implementation Test
@end
int main(int argc, const char * argv[])
{
    Test* test = [Test new];
    return 0;
}

于是在调试的过程中,对象有如下成员:
在这里插入图片描述
可以发现多了一个来自于NSObject的成员isa。现在可以联想C++的虚表指针,C++的虚表指针指向虚表,然后找到虚函数,但是在这里isa指向的不是一个表,而是一个对象。先看一看isa的类型与作用:(后续会出现很多的源代码,层层深入,耐心阅读)

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

现在看一下Class是什么类型。

typedef struct objc_class *Class;

再找objc_class。下面代码除了前两行其余都是无效的,后面的是OBJC2_UNAVAILABLE,也就是说在OC2.0中已经不可用。

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class  //指向父类            OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

在这里可进入查询新的objc_class类【戳我进入】
目前已经这样定义了。

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;//指向父类
    cache_t cache; //消息缓存 formerly cache pointer and vtable
    class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
    class_rw_t *data(){……}
    // ....
}

下面把重要的成员列举出来

struct class_data_bits_t {

    // Values are the FAST_ flags above.
    uintptr_t bits;
    ……
    

这是上述指针的类型


/* Types for `void *' pointers.  */
#if __WORDSIZE == 64
# ifndef __intptr_t_defined
typedef long int		intptr_t;
#  define __intptr_t_defined
# endif
typedef unsigned long int	uintptr_t;
#else
# ifndef __intptr_t_defined
typedef int			intptr_t;
#  define __intptr_t_defined
# endif
typedef unsigned int		uintptr_t;
#endif

看一下后面的class_rw_t,rw就是读写的意思,也就是动态添加的一些方法,协议等会在这里进行描述和组织。

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;//动态添加的方法列表
    property_array_t properties;//动态添加的属性列表
    protocol_array_t protocols;//动态添加的协议列表

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

这是class_ro_t成员,ro就是readonly的意思,也就是一开始就写好,已经确定的方法、协议,在这里进行描述和组织

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;//已经写好的方法列表
    protocol_list_t * baseProtocols;//协议
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

有了上面的基础,带来下面的概念:示例对象,类对象,元类对象
实例对象、类对象、元类对象

isa指针isa指针isa指针
成员变量super指针super指针
消息缓存类方法
方法、属性、协议……
4、消息传递

有了上面的基础,就可以梳理消息传递了。复制一张图吧。
在这里插入图片描述
下面来说明一下消息传递:

  1. 元类中isa指针指向根元类,而根元类中isa指针则指向自身。
  2. 根元类中的superClass指针指向根类,因为根元类是通过继承根类产生的。

为了更好的理解,流程图如下:
在这里插入图片描述

  1. 如果是实例方法就会通过对象的isa指针在类对象中的方法列表中寻找,如果没有,则会通过super指针指向父类,然后在父类中寻找。
  2. 如果是类方法,则会通过类对象的isa指针在元类的方法列表中寻找,没有找到则会通过super指针在父类中寻找。
  3. 上述方式如果在根类的方法列表中都没有寻找到,就会导致对象接受到一个无法解析的,也就是找不到实现的,没法执行的方法,这个时候会启动消息转发。

如果该方法只有声明没有实现,那么该方法一定不会放在方法列表中,外界一定不能直接调用到方法

5、消息转发

在上述的消息机制中如果最终没有找到方法实现就会启动消息转发。
例如控制台出现如下的代码,说明已经启动了消息转发:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[某类名 noImplementationMuthod]: unrecognized selector sent to instance 某地址'

消息转发有两个阶段,第一阶段,回进行动态方法解析,也就是说是否有动态添加的方法来与这个无法解析的方法符合。第二阶段则是完整的消息转发机制。

5.1 动态绑定

所谓动态就是在运行时才能确定,动态绑定就是在调用函数的时候,需要在执行的时候再回知道执行的是哪个函数,例如下面的例子:

void leftPrint()
{
    NSLog(@"我是第一个函数");
}

void rightPrint()
{
    NSLog(@"我是第二个函数");
}
int main(int argc, const char * argv[]) {
    
    void (*funcptr)();
    
    int choice = 0;
    while(!choice)
    {
        NSLog(@"输入1或2选择执行的函数:");
        scanf("%d",&choice);
        if(choice == 1)
        {
            funcptr = leftPrint;
        }
        else if(choice == 2)
        {
            funcptr = rightPrint;
        }
        else
        {
            choice = 0;
            NSLog(@"输入非法");
        }
    }
    funcptr();//此时执行的函数就是在执行期选择的函数,动态绑定!
  	return 0;
  }
5.2 动态添加方法

首先需要引入<objc/message.h>头文件,然后是下面的方法。

1)+(BOOL)resolveInstanceMethod:(SEL)sel;
2)+(BOOL)resolveClassMethod:(SEL)sel

3)class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types);

然后举一个使用的例子。当启动消息转发后,会调用第一个方法,所以动态添加方法,需要实现上述的第一个方法。(如果是实例方法是第一个,类方法的话是第二个,下面以实例方法举例子)

+(BOOL)resolveInstanceMethod:(SEL)sel
{
    //if(sel == NSSelectorFromString(@"noImplementationMethod"))
    if([NSStringFromSelector(sel) isEqualToString:@"noImplementationMethod"])//这两种方式都可以进行判断
    {
        class_addMethod(self, sel,(IMP)replaceFunc,"v@:");//这里就是我们自己动态的方法
        //这里的第一个参数是类对象:self == [self class]
        //如果是类方法,则需要传元类对象:object_getClass([类名 class])
        return YES;
    }//这里可以进行else if来进行分支操作,因为可能动态添加的方法不止一个
    return [super resolveInstanceMethod:sel];
}

现在来看一看class_addMethod方法

 class_addMethod(self, sel,(IMP)replaceFunc,"v@:")
 第一个参数:给谁添加方法,一般都是self,给自己
 第二个参数:传入进来的输出参数
 第三个参数:自己实现的方法名称,自己实现的方法需要用C语言定义函数的方式
 第四个参数:方法签名,描述方法的参数个数、参数类型以及返回值类型。
 方法签名组成:
 第一个字符:返回值类型
 第二个字符:接收者(id)@
 第三个字符:选择器(SEL):
 后续字符为参数,还有具体的符号,请戳下面的链接。

【戳我进入】
下面就是实现的函数,C函数,注意格式

void replaceFunc(id self, SEL _cmd)
{
    NSLog(@"动态添加的方法");
}

除了动态添加方法,还有属性,协议等等。

1)BOOL class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size,uint8_t alignment, const char * _Nullable types) 

2)BOOL class_addProtocol(Class _Nullable cls, Protocol * _Nonnull protocol) 
3)BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes,unsigned int attributeCount)

——————————————————————————
IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) 
这种带replace分开来说,class_addMethod方法中的实现会覆盖父类的方法实现,但不影响本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。所以要修改已存在的实现,可以使用class_replaceMethod。

上述的方式可以直接调用的。

[对象 动态添加的方法]

还可以借助performSelector的调用,直接添加动态方法:

class_addMethod([类名 class], @selector(动态添加的方法), class_getMethodImplementation([另一个类名 class], @selector(实现的方法名)), "v@:");

例如:
class_addMethod([Student class], @selector(otherMethod), class_getMethodImplementation([MyClass class], @selector(implemationMethod)), "v@:");
因此,调用的时候可以
[stu performSelector:@selector(otherMethod)];

如果直接调用方法,编译是会自动校验,因此回报错。但是performSelector是运行时系统负责去找方法的,在编译时候不做任何校验。

5.3 完整消息转发

完整的消息转发又分为快速转发和慢速转发。

5.3.1 快速转发

找到一个备用的接收者,【或者说是备胎】
其方法如下:

-(id)forwardingTargetForSelector:(SEL)aSelector
{
    if([NSStringFromSelector(aSelector) isEqualToString:@"方法名"])
    {
        return [备用对象的类 new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

然后在备用对象的类实现这个方法。

5.3.2 慢速转发

在更广的地方进行处理这个无法解析的方法,类似丢漂流瓶,讲方法签名等消息信息封装在NSInvocation的对象中,最终把消息转发到备用接收者。

-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
    //返回签名信息
    if([NSStringFromSelector(aSelector) isEqualToString:@"noImplementationMethod"])
    {
        //如果知道方法签名,可以直接放回
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        /*
         或者还可以这样
         return [self methodSignatureForSelector:aSelector];
         return  [NSObject methodSignatureForSelector:aSelector];
          */
    }
    return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL sel = anInvocation.selector;
    备用接收者类* 某对象 = [备用接收者类 new];
    if([某对象 respondsToSelector:sel])
    {
        [anInvocation invokeWithTarget:某对象];
    }
    else
    {
        [super forwardInvocation:anInvocation];
    }
}
5.4 总结

通过上面的消息转发机制,这个消息最终就像邮政寄信一样的有保障,最终都会执行,不会导致程序崩溃。
继上次的流程图,现在可以连接新的流程图就是:
在这里插入图片描述

5、后记

消息是OC的一个重要概念,因此对于底层的探究是必要的。而内存管理是每个语言的一个重要部分,对象模型应该在内存管理时一并学习。通过上面的实例对象,类,元类可以对OC的运行时有一个了解,或者说是动态语言有一个了解,并且相对C++,OC的面向对象更纯粹有了体现。后面将会接触一些负责的语法,还会对内存管理进行学习。
【戳我进入本章节示例代码】

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值