iOS入门

iOS入门

NSObject

NSObject *p = [[NSObject alloc] init];

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.mm -o main_objc.cpp

NSObject *p = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));

化简后得到

NSObject *p = objc_msgSend( objc_msgSend( objc_getClass("NSObject"), sel_registerName("alloc")) , sel_registerName("init") );

objc_getClass 会返回一个 Class 类指针

typedef struct objc_class *Class;
typedef struct objc_object *id;
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

NSObject *p 就是一个指向Class的指针
所以当一个OC对象调用方法的时候,实际上就是对Class对象的isa指针进行操作

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() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
	// ...
};

struct objc_object {
private:
    isa_t isa;

public:
	// ...
}

// 8字节
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
	  }
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
	struct {
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
	  	}
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

# else
#   error unknown architecture for packed isa
# endif
    };


};

精简:


#   define ISA_MASK        0x0000000ffffffff8ULL			// 36位
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL

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

    Class cls;
    uintptr_t bits;

    struct {
	// 0代表普通的指针,存储着Class,Meta-Class对象的内存地址。
    // 1代表优化后的使用位域存储更多的信息。
      uintptr_t nonpointer        : 1
	  
	  // 是否有设置过关联对象,如果没有,释放时会更快
      uintptr_t has_assoc         : 1
	  
	  // 是否有C++析构函数,如果没有,释放时会更快
      uintptr_t has_cxx_dtor      : 1
	  
	  // 存储着Class、Meta-Class对象的内存地址信息
      uintptr_t shiftcls          : 33 					/*MACH_VM_MAX_ADDRESS 0x1000000000*/  // 地址
	  
	  // 用于在调试时分辨对象是否未完成初始化
      uintptr_t magic             : 6
	  
	  // 是否有被弱引用指向过。
      uintptr_t weakly_referenced : 1;
	  
	  // 对象是否正在释放
      uintptr_t deallocating      : 1
	  
	  // 引用计数器是否过大无法存储在isa中
      // 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
      uintptr_t has_sidetable_rc  : 1
	  
	  // 里面存储的值是引用计数器减1
      uintptr_t extra_rc          : 19
    }
};

获得真正的类对象指针 p & ISA_MASK

OC对象的分类

  • instance实例对象 由类alloc出来的对象,每次调用alloc会产生新的实例对象
  • class类对象 每个类在内存中有且只有一个类对象
  • meta-class 元类对象 每个类的内存中有且只有一个元类对象

instance实例对象的存储结构

  • isa指针
  • 其它成员变量

类对象 存储

  • isa指针
  • superClass指针
  • 成员变量(描述性信息,比如变量类型)
  • 类的对象方法
  • 类的协议信息
  • 类的属性信息

元类对象 存储

  • isa指针
  • superClass指针
  • 类方法(+方法)

isa,superclass的总结

  • instance的isa指向class
  • class的isa指向meta-class
  • meta-class的isa指向基类的meta-class
  • class的superclass指向父类的class
  • meta-class的superclass指向基类的父类的meta-class
  • instance调用方法的轨迹:isa找到父类的class,如果方法不存在就通过superclass找父类
  • class调用类方法的轨迹,isa找到meta-class,方法不存在,就通过superclass找父类。

iOS Runtime

方法调用

[person test];

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_objc.cpp

((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("test"));

OC的方法调用也叫消息机制,给方法的调用者发送消息。

消息发送的三个阶段

  1. 从类及父类的缓存列表查找。
  2. 动态解析,动态的添加方法实现。
  3. 消息转发阶段:如果也没有实现动态解析方法,将消息转发给处理消息的接收者来处理。
    否就 unrecognzied selector sent to instance

// objc_msgSend
// x0 是 对象指针, x1 是 sel
objc-msg-arm64.s

    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    cmp    p0, #0            				// nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        		//  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
    ldr    p13, [x0]        // p13 = isa
    GetClassFromIsa_p16 p13        // p16 = class							// x16会存放 isa
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend				// 寻找缓存

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq    LReturnZero        // nil check

    // tagged
    adrp    x10, _objc_debug_taggedpointer_classes@PAGE
    add    x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
    ubfx    x11, x0, #60, #4
    ldr    x16, [x10, x11, LSL #3]
    adrp    x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
    add    x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
    cmp    x10, x16
    b.ne    LGetIsaDone

    // ext tagged
    adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
    add    x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
    ubfx    x11, x0, #52, #8
    ldr    x16, [x10, x11, LSL #3]
    b    LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
    // x0 is already zero
    mov    x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    ret

    END_ENTRY _objc_msgSend

首先判断指针是否为0,如果不为0,进入CacheLookup NORMAL查缓存。

如果传入消息不为nil则执行CacheLookup,内部方法缓存列表进行查找,如果找到则执行CacheHit,进而调用方法。否则执行CheckMiss,CheckMiss内部调用 __objc_msgSend_uncached
__objc_msgSend_uncached 内会执行 MethodTableLookup 也就是方法列表查找,MethodTableLookup内部的核心代码 __class_lookupMethodAndLoadCache3 也就是c语言函数 _class_lookupMethodAndLoadCache3

CacheLookup


.macro CacheLookup
	//
	// Restart protocol:
	//
	//   As soon as we're past the LLookupStart$1 label we may have loaded
	//   an invalid cache pointer or mask.
	//
	//   When task_restartable_ranges_synchronize() is called,
	//   (or when a signal hits us) before we're past LLookupEnd$1,
	//   then our PC will be reset to LLookupRecover$1 which forcefully
	//   jumps to the cache-miss codepath which have the following
	//   requirements:
	//
	//   GETIMP:
	//     The cache-miss is just returning NULL (setting x0 to 0)
	//
	//   NORMAL and LOOKUP:
	//   - x0 contains the receiver
	//   - x1 contains the selector
	//   - x16 contains the isa
	//   - other registers are set as per calling conventions
	//
LLookupStart$1:

	// p1 = SEL, p16 = isa
	ldr	p11, [x16, #CACHE]				// p11 = mask|buckets

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
	and	p12, p1, p11, LSR #48		// x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	and	p10, p11, #~0xf			// p10 = buckets
	and	p11, p11, #0xf			// p11 = maskShift
	mov	p12, #0xffff
	lsr	p11, p12, p11				// p11 = mask = 0xffff >> p11
	and	p12, p1, p11				// x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif


	add	p12, p10, p12, LSL #(1+PTRSHIFT)
		             // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

	ldp	p17, p9, [x12]		// {imp, sel} = *bucket
1:	cmp	p9, p1			// if (bucket->sel != _cmd)
	b.ne	2f			//     scan more
	CacheHit $0			// call or return imp
	
2:	// not hit: p12 = not-hit bucket
	CheckMiss $0			// miss if bucket->sel == 0
	cmp	p12, p10		// wrap if bucket == buckets
	b.eq	3f
	ldp	p17, p9, [x12, #-BUCKET_SIZE]!	// {imp, sel} = *--bucket
	b	1b			// loop

3:	// wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
	add	p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
					// p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	add	p12, p12, p11, LSL #(1+PTRSHIFT)
					// p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif

	// Clone scanning loop to miss instead of hang when cache is corrupt.
	// The slow path may detect any corruption and halt later.

	ldp	p17, p9, [x12]		// {imp, sel} = *bucket
1:	cmp	p9, p1			// if (bucket->sel != _cmd)
	b.ne	2f			//     scan more
	CacheHit $0			// call or return imp
	
2:	// not hit: p12 = not-hit bucket
	CheckMiss $0			// miss if bucket->sel == 0
	cmp	p12, p10		// wrap if bucket == buckets
	b.eq	3f
	ldp	p17, p9, [x12, #-BUCKET_SIZE]!	// {imp, sel} = *--bucket
	b	1b			// loop

LLookupEnd$1:
LLookupRecover$1:
3:	// double wrap
	JumpMiss $0

.endmacro

lookupMethodInClassAndLoadCache 在类中寻找方法,

IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel)
{
    Method meth;
    IMP imp;

    // fixme this still has the method list vs method cache race 
    // because it doesn't hold a lock across lookup+cache_fill, 
    // but it's only used for .cxx_construct/destruct and we assume 
    // categories don't change them.

// 命中缓存
    // Search cache first.
    imp = _cache_getImp(cls, sel);
    if (imp) return imp;

    // Cache miss. Search method list.

// 在当前类的方法中寻找,
    meth = _class_getMethodNoSuper(cls, sel);

    if (meth) {
        // Hit in method list. Cache it.
        _cache_fill(cls, meth, sel);
        return method_getImplementation(meth);
    } else {
// 如果没找到,则转发该方法去寻找。
        // Miss in method list. Cache objc_msgForward.
        _cache_addForwardEntry(cls, sel);
        return _objc_msgForward_impcache;
    }
}

关键数据结构

方法Method 是一个objc_method结构体
SEL是一个字符串

typedef struct objc_method *Method;

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
};
在objc2里结构是:

    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };

// objc-runtime-new.h
typedef uintptr_t SEL;

class_addMethod

class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types);

// 获取其它方法指向method_t的指针
Method otherMethod = class_getInstanceMethod(self, @selector(other));

// method_getImplementation 和 method_getTypeEncoding 获取 imp 和 type

Questions:

sel_registerName 是啥

@selector()

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值