iOS课程观看笔记(三)---RunTime

问:
编译型语言与OC这种动态编译语言的区别有哪些?
消息传递与函数调用有怎样的区别?

为了很好的回答以上问题,我们开始一起学习

在这里插入图片描述

数据结构

我们主要学习四种数据结构:
objc_object
objc_class
isa指针
method_t

typedef struct objc_class *Class;
typedef struct objc_object *id;

objc_object

在这里插入图片描述

在源码文件中,截取了部分内容:

struct objc_object {
private:
    isa_t isa;

public:

	//关于isa的操作
	Class ISA();
	Class getIsa();
	void initIsa(Class cls /*nonpointer=false*/);
	void initClassIsa(Class cls /*nonpointer=maybe*/);
	
	//弱引用相关
	bool isWeaklyReferenced();
    void setWeaklyReferenced_nolock();

	//关联对象相关
	bool hasAssociatedObjects();
    void setHasAssociatedObjects();
    
	//内存管理相关
	id retain();
    void release();
    id autorelease();
    
    ....
}

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
    ......
}

isa指针

在这里插入图片描述
isa_t isa;
isa是isa_t类型的

在源码文件中,截取了部分内容:

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
    ......
}

在这里插入图片描述

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

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
    ......
}

在这里插入图片描述

struct class_data_bits_t {
public:

    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    void setData(class_rw_t *newData)
    {
        assert(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }
    ......
}

在这里插入图片描述

struct class_rw_t {
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    ......
}

其中:
class method_array_t : 
    public list_array_tt<method_t, method_list_t> 
{
    typedef list_array_tt<method_t, method_list_t> Super;

 public:
    method_list_t **beginCategoryMethodLists() {
        return beginLists();
    }
    
    method_list_t **endCategoryMethodLists(Class cls);

    method_array_t duplicate() {
        return Super::duplicate<method_array_t>();
    }
};

在这里插入图片描述

struct class_ro_t {
    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;
    }
};

method_t

在这里插入图片描述

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

在这里插入图片描述

数据类型总结

在这里插入图片描述

这个图是真好


类对象与元类对象

实例对象、类对象、元类对象
类对象存储实例方法列表等信息
元类对象存储类方法列表等信息

在这里插入图片描述

需要注意的地方:
元类对象的isa指针,都是指向根元类对象。包括根元类对象本身,也是指向根元类对象
根元类对象的superclass指针,指向的是根类对象

问:元类对象的isa指针指向哪里?

元类对象的isa指针,都是指向根元类对象。包括根元类对象本身,也是指向根元类对象

问:如果一个类方法没有实现,但是有同名的对象方法实现,会崩溃?还是会调用?

对象方法存储在类对象里面;
类方法存储在元类对象里面;

这个问题应该这样看:
如果一个类方法没有实现,根据superclass指针,会去父类里面找同名类方法,直至到根元类对象里面去查找;
如果中间找到了对应的同名类方法,则会调用。
如果中间没有找到对应的同名类方法,则根元类对象的superclass指向的是根类对象
如果根类对象里面有对应的同名实例方法(类对象(包括根类对象)只能存储实例方法),则会调用。
如果根类对象里面没有对应的同名实例方法,则根据消息传递原则,进入动态方法解析阶段。

举个例子:

@interface YZPerson : NSObject
+ (void)run;
- (void)run;
@end


#import "YZPerson.h"
@implementation YZPerson
//+ (void)run
//{
//    NSLog(@"类方法-run");
//}

- (void)run
{
    NSLog(@"实例方法-run");
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    [YZPerson run];
}

结果:崩溃
在这里插入图片描述

也就是说,person里面虽然有同名的对象方法-(void)test;,但是,不好意思,superclass指针最多也就到达根类对象里面,到达不了类对象person里面,所以,还是找不到-(void)test;

换句话说,如果类方法没有实现,只有根类对象里面有同名实例方法,才能调用。

根类对象一般指的是NSObject,如果需要在根类对象添加,也就是给NSObject添加自定义实例方法,这就需要分类了。分类可以给系统添加方法。

@interface YZPerson : NSObject
+ (void)run;
- (void)run;
@end


#import "YZPerson.h"
@implementation YZPerson
//+ (void)run
//{
//    NSLog(@"类方法-run");
//}

- (void)run
{
    NSLog(@"实例方法-run");
}
@end

NSObject+test.h文件
@interface NSObject (test)
- (void)run;
@end

NSObject+test.m文件
#import "NSObject+test.h"
@implementation NSObject (test)
- (void)run
{
    NSLog(@"NSObject-实例方法-run");
}
@end


- (void)viewDidLoad {
    [super viewDidLoad];
    [YZPerson run];
}

打印结果:
NSObject-实例方法-run

堪称是完美


消息传递

在这里插入图片描述

消息传递

void objc_msgSend(void /* id self, SEL op, ... */ )
里面是两个默认参数,self和SEL。
[self class]经过编译器转换为objc_msgSend(self, @selector(class));

void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
里面是两个默认参数,super和SEL。
[super class]经过编译器转换为objc_msgSendSuper(super, @selector(class));

而,其中struct objc_super的数据结构是:

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};

里面有句话:super_class is the first class to search
也就是,搜索方法,是从super_class开始的

objc_msgSendSuper({self, super_class}, @selector(class));
receiver是当前对象,也就是self,才是真正的消息接收者

也就是,不论是[self class]还是[super class],消息接收者都是当前对象self。
因此,上面的图片中,打印结果是:

Phone
Phone


老师的消息传递图中,在父类查找到方法后直接调用,没有缓存,这是不对的。

通过对源码的解读,在原类缓存中无对应方法的时候,去原类或父类中查找:

	// Try this class's cache.
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.
    {
    	//去查找:有序二分查找,无序遍历查找
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {//找到方法
        	//缓存
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;//取出方法的IMP
            goto done;//调用
        }
    }
    
	// Try superclass caches and method lists.
	{
        unsigned attempts = unreasonableClassCount();
        //遍历父类
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            //从父类缓存中找
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);//cls是当前类,也就是缓存到原类
                    goto done;//调用
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            //从父类方法列表中找
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);//cls代表原类,因此找到后,缓存到原类
                imp = meth->imp;
                goto done;
            }
        }
    }

	其中,log_and_fill_cache里面的部分调用
	bucket_t *bucket = cache->find(key, receiver);
    if (bucket->key() == 0) cache->incrementOccupied();
    bucket->set(key, imp);

可以看出,在调用前,会先进行缓存操作。

缓存操作有两种可能:
1.缓存到receiver的cache_t的bucket_t中
2.缓存到父类的cache_t的bucket_t中

首先,肯定是参数cls的
那么,参数cls是谁?
注释倒是有一句:

cls is the method whose cache should be filled.

跟没说一样

从上面代码可以看出,父类中缓存中有或者方法列表中有,则将方法缓存到原类中的cache_t的bucket_t中。

正确流程图:

在这里插入图片描述

1.当前缓存中查找
2.当前类中查找
3.父类缓存中查找
4.父类类中查找

1.当前缓存中查找

根据给定的值key,找到bucket_t里面的IMP
在这里插入图片描述
是一个hash查找

2.当前类中查找

对于已经排序好的列表,采用二分查找算法查找对应的执行函数
对于没有排序好的列表,采用一般遍历查找方法对应的执行函数

3.父类缓存中查找
4.父类中查找

在这里插入图片描述


消息转发

在这里插入图片描述


动态添加方法

动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

Method_Swizzing方法交换

在这里插入图片描述void method_exchangeImplementations(Method m1, Method m2)

更多学习请参考:
iOS-探究Runtime


动态方法解析

@dynamic
这句话写上,编译器将不再生成属性值的setter和getter方法
也不会生成成员变量
然后,我们可以在运行时+ (BOOL)resolveInstanceMethod:(SEL)sel方中,动态的实现setter和getter方法

在MJ课程中,动态方法解析指的是runtime中的第二个流程+ (BOOL)resolveInstanceMethod:(SEL)sel

在于海本节课程中,动态方法解析是一个具体例子@dynamic,动态实现setter和getter方法

都是指的+ (BOOL)resolveInstanceMethod:(SEL)sel的方法调用

动态运行时语言将函数决议推迟到运行时
编译时语言在编译期进行函数决议


问:[obj foo]与objc_msgSend()函数之间有什么关系?

[obj foo]编译后,就转换为了objc_msgSend()类型

问:runtime如何通过selector找到对应的IMP的?

其实就是objc_msgSend()的消息传递流程

问:能否向编译后的类中增加实例变量?

实例变量就是ivar
(成员变量 = 实例变量 + 基本数据类型的变量)

不能

可以向动态添加的类中增加实例变量
只需在注册之前加就可以

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值