OC基础部分

老早看了小码哥iOS底层班的视频,现在翻过来回顾一下.顺便做下笔记:

-----------------------------xcode控制台操作-----------------------------

1.OC文件转为C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件

在使用clang转换OC为C++代码时,可能会遇到以下问题
cannot create __weak reference in file using manual reference

解决方案:支持ARC、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

2.查看内存数据
Debug -> Debug Workfllow -> View Memory (Shift + Command + M)

创建一个实例对象,至少需要多少内存?
#import <objc/runtime.h>
class_getInstanceSize([NSObject class]);

创建一个实例对象,实际上分配了多少内存?
#import <malloc/malloc.h>
malloc_size((__bridge const void *)obj);

常用LLDB指令:
print、p:打印
po:打印对象

读取内存
memory read/数量格式字节数 内存地址
x/数量格式字节数 内存地址
x/3xw 0x10010

修改内存中的值
memory write 内存地址 数值
memory write 0x0000010 10

格式
x是16进制,f是浮点,d是10进制

字节大小
b:byte 1字节,h:half word 2字节
w:word 4字节,g:giant word 8字节

#-----------------------------NSObject探究-----------------------------

Class obejct = [[NSObject class] class];
//判断是否是元类对象
BOOL result = class_isMetaClass(obejct);//result = 0
//获取元类对象
Class meta = object_getClass(obejct);
BOOL result1 = class_isMetaClass(meta);//result = 1

isa、superclass的指向.

instance的isa指向class
class的isa指向meta-class
meta-class的isa指向基类的meta-class
class的superclass指向父类的class,如果没有父类,superclass指针为nil

meta-class的superclass指向父类的meta-class,基类的meta-class的superclass指向基类的class(即NSObject的元类对象的superclass指针指向NSObejct类对象)

instance调用对象方法的轨迹:isa找到class,方法不存在,就通过superclass找父类
class调用类方法的轨迹:isa找meta-class,方法不存在,就通过superclass找父类

image.png

NSObject对象结构:

/* 类对象 */
struct mj_objc_class {
    Class isa;
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
}; &FAST_DATA_MASK(0x00007ffffffffff8UL)
   ↓
struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 方法列表
    property_list_t *properties;    // 属性列表
    const protocol_list_t * protocols;  // 协议列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

class_ro_t :
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance对象占用的内存空间
#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;
};

#-----------------------------KVO,KVC探究-----------------------------
KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变
KVO实现相关:
1.调用willChangeValueForKey:
2.调用原来的setter实现
3.调用didChangeValueForKey:
4.didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法

KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性

常见的API有
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key; 

category的底层结构

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

category的加载过程
1.通过Runtime加载某个类的所有Category数据
2.把所有Category的方法、属性、协议数据,合并到一个大数组中(后参与编译的Category数据,会在数组的前面)
3.将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

+load方法的调用顺序:
1.先调用类的load,按照编译先后调用的load(先编译,后调用,调用子类的load之前会调用父类的load)
2.在调用分类的load(先编译,先调用)

+initialize方法会在类第一次接收到消息时调用
调用顺序:
先调用父类的+initialize,再调用子类的initialize(先初始化父类,在初始化子类,每个类只会初始化一次)

+initialize和+load的区别:
+initialize是通过objc_msgSend进行调用的,
如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
如果分类实现了+initialize,就覆盖类本身的+initialize调用

如何给分类添加成员变量:
默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现

关联对象提供了一下API:

//添加关联对象
void objc_setAssociatedObject(id object, const void * key,
                                id value, objc_AssociationPolicy policy)
//获得关联对象
id objc_getAssociatedObject(id object, const void * key)
//移除所有关联对象
void objc_removeAssociatedObjects(id object)
                             

key的常见用法

static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)

static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)

使用属性名作为key
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");

使用get方法的@selecor作为key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))

objc_AssociationPolicy对应的修饰符
OBJC_ASSOCIATION_ASSIGNassign
OBJC_ASSOCIATION_RETAIN_NONATOMICstrong, nonatomic
OBJC_ASSOCIATION_COPY_NONATOMICcopy, nonatomic
OBJC_ASSOCIATION_RETAINstrong, atomic
OBJC_ASSOCIATION_COPYcopy, atomic

关联对象设计的核心类:
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation


class AssociationsManager {
    static AssociationsHashMap *_map;
};

class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *>

class ObjectAssociationMap : public std::map<void *, ObjcAssociation>

class ObjcAssociation {
        uintptr_t _policy;
        id _value;
        }

关联对象原理

AssociationsManager
AssociationsHashMap *_map;

AssociationsHashMap
disguised_ptr_t ObjectAssociationMap
disguised_ptr_t ObjectAssociationMap

AssociationsMap
void * ObjectAssociation
void * ObjectAssociation

ObjectAssociation
uintptr_t _policy ; id _value;

image.png

#-----------------------------Block探究-----------------------------
block的底层结构探究,首先定义一个block

 int age = 10; 
void (^block)(int, int) =  ^(int a , int b){
            NSLog(@"this is a block! -- %d", age);
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
        };

通过转换成c++代码得到结果


static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int age = 10;

        void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));


        ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
    }
    return 0;
}

可以看出block内部实现就是 __main_block_impl_0
我们点开看一下 __main_block_impl_0结构体的内部

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int age;
};

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
};

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

将main_block_impl_0抽离开来看到,block里面也会存在一个isa指针,因为是栈上的block,所以age只是值传递

struct __main_block_impl_0 {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
    struct __main_block_desc_0* Desc;
//如果是__block age会变成一个对象 __Block_byref_age_0 *age
    int age;
   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

block本质:
block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象

block变量的捕获
image.png

block类型环境
NSGlobalBlock没有访问auto变量
NSStackBlock访问了auto变量
NSMallocBlock__NSStackBlock__调用了copy

每一种类型的block调用copy后的结果如下所示:

block类型副本源的配置存储域复制效果
NSGlobalBlock程序的数据区域什么也不做
NSStackBlock从栈复制到堆
NSMallocBlock引用计数增加

以下情况,ARC会自动将栈上的block复制到堆上,比如以下情况:
1.block作为函数返回值时
2.将block赋值给__strong指针时
3.block作为Cocoa API中方法名含有usingBlock的方法参数时
4.block作为GCD API的方法参数时

当block内部访问了对象类型的auto变量时
如果block是在栈上,将不会对auto变量产生强引用

如果block被拷贝到堆上
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

如果block从堆上移除
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(release)

函数调用时机
copy函数栈上的block复制到堆时
dispose函数堆上的block废弃时

####__block对引用的影响

__block可以用于解决block内部无法修改auto变量值的问题
__block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象

当block在栈上时,并不会对__block变量产生强引用

当block被copy到堆时
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会对__block变量形成强引用(retain)

如果__block变量从堆上移除
会调用__block变量内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放指向的对象(release)

block
__Block_byref_age_0 *age

变成

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
}

__block的__forwarding指针
复制之前:
栈上的__forwarding指向自己
复制之后:
栈上的__forwarding指向堆上的block
堆上的forwarding指向自己

解决循环引用:
第一种.让一个引用改成弱引用.
第二种.在block函数里面__block将指针置位nil,但这种情况必须要调用block

面试相关:
#-----------------------------OC基础-----------------------------

一个NSObejct对象占用多少内存?
正常来说一个NSObject对象只需要8个字节的空间就行了,但实际上runtime存在一个内存对齐的一个原理.而在runtime源码里面 alignedInstanceSize 这个方法的返回可以看到,实际上返回的是不足16个字节返回的是16个字节.通过下面代码可以看到,系统分配了16个字节给NSObject对象(可以通过malloc_size函数得到);但NSObject对象内部只使用了8个字节空间(在64bit环境下,可以通过class_getInstanceSize函数获得)。

// Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

对象的isa指针指向哪里?
1.instance对象的isa指向class对象,当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用
2.class对象的isa指向meta-class(元类)对象
3.meta-class对象的isa指向基类的meta-class对象

OC的类信息存放在哪里?
1.对象的方法、属性、成员变量、协议信息,存放在class对象中
2.类方法,存放在meta-class对象中
3.成员变量的具体值,存放在instance对象中

#-----------------------------KVO-----------------------------
iOS用什么方式实现一个对象的KVO?(KVO的本质是什么?)
1.系统利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
2.当修改instance对象的属性时,会调用Foundation框架的_NSSetXXXValueAndNotify函数:
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:
内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)

如何手动触发KVO?
手动调用willChangeValueForKey:和didChangeValueForKey:

直接修改成员变量会触发KVO么?(下划线调用赋值)
不会触发KVO

KVC的赋值和取值过程是怎样的?原理是什么?会触发KVO么?
赋值原理: setValue:forKey:
1.按照按照setKey:、_setKey:顺序查找方法
2.找到方法了就直接传递参数,调用方法
3.未找到方法查看accessInstanceVariablesDirectly(默认返回YES)方法(如果返回NO直接跳到第六步,异常)
4.按照_key、_isKey、key、isKey顺序查找成员变量
5.找到了直接赋值
6.未找到调用setValue:forUndefinedKey:并抛出异常NSUnknownKeyException

取值原理:valueForKey:
1.按照getKey、key、 isKey、_key顺序查找方法
2.找到方法了就调用方法
3.未找到方法查看accessInstanceVariablesDirectly(默认返回YES)方法(如果返回NO直接跳到第六步,异常)
4.按照_key、_isKey、key、isKey顺序查找成员变量
5.找到了直接取值
6.调用valueForUndefinedKey:并抛出异常NSUnknownKeyException

-----------------------------Category-----------------------------

Category的使用场合是什么?

Category的实现原理
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

Category和Class Extension的区别是什么?
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中

Category能否添加成员变量?如果可以,如何给Category添加成员变量?
不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SeanLink

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值