OC语法

一、OC本质

  1. 模拟器(i386)、32bit(armv7)、64bit(arm64)

  2. 查看C语言实现的指令

    • 不包含__weak关键字
      xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
    • 包含__weak关键字
      xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios8.0.0 main.m -o main.cpp
  3. 常用的LLDB指令

    OC语法_第1张图片
    image

  4. iOS的内存显示是小端模式,也就是读取内存值是从高地址到低地址

二、OC对象的分类:instance对象(实例对象)、class对象(类对象)、meta-class对象(元类对象)

  1. instance对象

    • 通过类alloc出来的对象,每次调用alloc都会产生新的instance对象
    • instance对象在内存中存储的信息包括:isa指针、其他成员变量的值
  2. class对象

    • 获取方法
      OC语法_第2张图片
      image
    • 每个类在内存中有且只有一个class对象
    • class对象在内存中存储的信息包括:isa指针、superclass指针、类的属性信息(@property)、类的对象方法信息(instance method)、类的协议信息(protocol)、类的成员变量信息(ivar的类型和名称)


      OC语法_第3张图片
      image
  3. meta-class对象

    • 获取方法
      image
    • 每个类在内存中有且只有一个meta-class对象
    • meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中主要包括:isa指针、superclass指针、类的类方法信息(class method)
      OC语法_第4张图片
      image
    • 查看是否为meta-class对象
      BOOL class_isMetaClass(Class cls);
    • 补充
    1. Class objc_getClass(const char *aClassName)
      * 传入字符串类名
      * 返回对应的类对象
    1. Class object_getClass(id obj)
      * 传入的obj可能是instance对象、class对象、meta-class对象
      * 返回值
      * 如果是instance对象,返回class对象
      * 如果是class对象,返回meta-class对象
      * 如果是meta-class对象,返回NSObject(基类)的meta-class对象
    1. - (Class)class、+ (Class)class
      * 返回的就是类对象
  4. isa总结

    OC语法_第5张图片
    image.png

    • instance的isa指向class
    • class的isa指向meta-class
    • meta-class的isa指向基类的meta-class
      • 基类meta-class的isa指向自己
    • class的superclass指向父类class
      • 如果没有父类,superclass为nil
    • meta-class的superclass指向父类meta-class
      • 基类meta-class的superclass指向元类class
  5. isa指针指向

    OC语法_第6张图片
    image

    OC语法_第7张图片
    image

    • 从64bit开始,isa需要进行一次位运算,才能计算出真实地址
    • class、meta-class对象的本质结构都是struct objc_class
  6. struct objc_class 的内部结构图


    OC语法_第8张图片
    image.png

三、KVO

  1. 全称:Key-Value Observing,俗称“键值监听”,可用于监听某个对象属性值的改变。
  2. 未使用KVO监听的对象
    OC语法_第9张图片
    image
  3. 使用了KVO监听的对象
    OC语法_第10张图片
    image
    • 补充:重写的-class方法内部直接返回了它父类的类对象,属于指针混淆,隐藏了NSKVONotifying_ClassName的存在。
  4. KVO实现的过程
    当添加监听方法以后,被监听对象的isa指针会指向一个由Runtime动态生成的类:NSKVONotifying_ClassName,它是被监听类的子类;当被监听属性发生变化时,新生成子类的属性setter方法中会调用Foundation框架里的函数_NSSet*ValueAndNotify,在该函数中会依次调用-willChangeValueForKey:,父类的属性setter方法,-didChangeValueForKey:,在-didChangeValueForKey:中会调用-observeValueForKeyPath:ofObject:change:context:,完成对属性的监听。

四、KVC

  1. 全称:Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性。

  2. 常见API

    • - (void)setValue:(id)value forKey:(NSString *)key
    • - (void)setValue:(id)value forKeyPath:(NSString *)keyPath
    • - (id)valueForKey:(NSString *)key
    • - (id)valueForKeyPath:(NSString *)keyPath
    • + (BOOL)accessInstanceVariablesDirectly,默认YES
  3. setValue:forKey原理

    OC语法_第11张图片
    image

  4. valueForKey:原理

    OC语法_第12张图片
    image.png

  5. 对于被KVO监听的属性,使用KVC赋值,会触发监听事件,因为KVC内部调用了-willChangeValueForKey:-didChangeValueForKey:

五、Category

  1. category在runtime中的结构(定义在objc-runtime-new.h中)
    OC语法_第13张图片
    image
  2. category的加载处理过程
    • 通过runtime加载某个类的所有category数据
    • 把所有category的方法、属性、协议数据,合并到一个大数组中
      • 后参与编译的category数据,会放在数组的前边
    • 将合并后的分类数据(方法、属性、协议),插入到类原来数据前面
      OC语法_第14张图片
      image

六、+load方法

  1. +load方法会在runtime加载类、分类的时候调用
  2. 每个类、分类的+load,在程序运行过程中只调用一次
  3. 调用顺序
    • 先调用类的+load
      • 按照编译先后顺序调用(先编译,先调用)
      • 调用子类的+load方法之前会先调用父类的+load(但是每个类只调用一次)
    • 再调用分类的+load
      • 按照编译先后顺序调用(先编译,先调用)
OC语法_第15张图片
image

七、+initialize方法

  1. 调用时间:+initialize会在类第一次接受消息时调用
  2. 调用顺序:先调用父类的+initialize,再调用子类的+initialize(先初始化父类,再初始化子类,每个类只初始化一次)
  3. +initialize是通过objc_msgSend方式进行调用
    • 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
    • 如果category实现了+initialize,则会覆盖类本身的+initialize
OC语法_第16张图片
image

八、关联对象

  1. 因为category的底层结构限制,不能直接在category添加成员变量。

  2. category添加属性后,不会自动生成成员变量和setter,getter方法。尝试的方案及不足:

    • category添加全局变量,手动生成settergetter。问题:不同的实例对象访问到的属性值是相同的。
    • category添加全局字典,使用self的地址作为key来存取属性值。问题:存在线程问题;每添加一个属性,就需要重新增加一个全局字典,比较麻烦。
  3. 关联对象API

    • 添加关联对象
      void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nonnull value, objc_AssociationPolicy policy)
    • 获得关联对象
      id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * key)
    • 移除所有的关联对象
      id objc_removeAssociatedObjects(id _Nonnull object)
  4. 设置关联对象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")
    
    • 使用getter方法的@selector作为key(推荐写法)
    objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    objc_getAssociatedObject(obj, @selector(getter))
    
  5. 关联策略

    OC语法_第17张图片
    image

  6. 关联对象原理

    • 实现关联对象技术的核心对象有

      • AssociationsManager
      • AssociationsHashMap
      • ObjectAssociationMap
      • ObjcAssociation
    • 源码解读

      OC语法_第18张图片
      image

    • 关联对象图解

      • void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nonnull value, objc_AssociationPolicy policy)
      • 关联对象并不是存储在被关联对象本身内存中
      • 关联对象统一存储在一个全局的AssociationsManager中
      • 设置关联对象的值为nil,就会移除该关联对象
      • 移除某个对象的所有关联对象,使用id objc_removeAssociatedObjects(id _Nonnull object)
      OC语法_第19张图片
      image

九、block

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

  2. block的变量捕获机制(capture)

    OC语法_第20张图片
    image

    • block会捕获auto变量的值,是因为auto变量会随时释放,如果block访问一个已经释放的变量,就会出现问题。
    • auto不能修饰全局变量
    • static局部变量在函数执行完后,不会释放,所以block捕获的是它的地址
    • 技巧:判断block会不会捕获变量,只需看下这个变量是否为局部变量,是则捕获,不是就不捕获
  3. 捕获auto变量后的结构图

    OC语法_第21张图片
    image

  4. block有三种类型,都是继承自NSBlock类型

    • __NSGlobalBlock__(_NSConcreteGlobalBlock)
    • __NSMallockBlock__(_NSConcreteMallockBlock)
    • __NSStackBlock__(_NSConcreteStackBlock)
    OC语法_第22张图片
    image
    • 从程序区域到栈区,地址从小到大
    • 程序区域:存放程序代码
    • 数据区域:存放全局变量
    • 堆区:动态分配的内存,需要程序员手动分配和释放
    • 栈区:存放局部变量
    • 技巧:要查看某个东西存放在什么区域,可以分别打印下已知区域的地址,然后打印要查看东西的地址,大概比较下跟哪个比较接近,一般就属于哪个区域。
  5. 不同环境下block的类型(查看的话需要在MRC环境)

    block类型 环境
    __NSGlobalBlock 没有访问auto变量
    __NSStackBlock 访问了auto变量
    __NSMallocBlock __NSStackBlock调用了copy
  6. 不同block调用copy后结果

    block的类 副本源的配置存储域 复制效果
    _NSConcreteStackBlock 从栈复制到堆
    _NSConcreteGlobalBlock 程序的数据区域 什么也不做
    _NSConcreteMallocBlock 引用计数增加
  7. ARC环境下,以下情况,block会自动进行copy操作

    • block作为函数返回值
    • 将block赋值给__strong指针时
    • block作为Cocoa API中方法名含有usingBlock方法的参数时
    • block作为GCD API方法的参数时
  8. 捕获对象类型的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被废弃时
  9. 在使用clang转换oc为c++代码时,使用以下命令:
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios 8.0.0 main.m

  10. 在block内部修改变量的方法

    • 使用static变量(缺点:会一直存在内存中)
    • 使用__block变量
  11. __block修饰符

    • 不能修饰全局变量和静态变量(static)
    • 编译器会将__block变量包装成一个对象
      OC语法_第23张图片
      image
  12. __block变量的内存管理

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

    • 当block被拷贝到堆上时

      • 会调用block内部的copy函数
      • copy函数内部会调用_Block_object_assign函数
      • _Block_object_assign函数会对__block变量产生强引用(retain)
        OC语法_第24张图片
        image.png
    • 当block从堆上移除时

      • 会调用block内部的dispose函数
      • dispose函数内部会调用_Block_object_dispose函数
      • _Block_object_dispose会自动释放引用的__block变量(release)
        OC语法_第25张图片
        image.png
  13. __block的forwarding指针

    • 目的就是要确保指向的是堆上的内存
      OC语法_第26张图片
      image.png
  14. 对象类型的auto变量和__block普通变量的比较

    • 当block在栈上时,都不会对它们产生强引用

    • 当block被拷贝到堆上时,都会通过copy函数来处理它们

      • 对象类型的auto变量(假设变量名叫p)

        _Block_object_assign((void*)&dst->p, (void*)&src->p, 3/*BLOCK_FIELD_IS_OBJECT*/)
        
      • __block变量(假设变量名叫a)

        _Block_object_assign((void*)&dst->a, (void*)&src->a, 8/*BLOCK_FIELD_IS_BYREF*/)
        
    • 当block从堆上移除时,都会通过dispose函数释放它们

      • 对象类型的auto变量(假设变量名叫p)

        _Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/)
        
      • __block变量(假设变量名叫a)

        _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/)
        
      变量类型 标识符
      对象 BLOCK_FIELD_IS_OBJECT
      __block变量 BLOCK_FIELD_IS_BYREF
  15. __block修饰的对象类型

    • 当__block对象变量在栈上时,不会对指向的对象产生强引用
    • 当__block对象变量被拷贝到堆上时
      • 会调用__block对象变量内部的__Block_byref_id_object_copy函数
      • __Block_byref_id_object_copy内部会调用_Block_object_assign函数
      • _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。(注意:这里仅限于ARC时会retain,MRC时不会retain,都是弱引用
    • 当__block对象变量从堆上移除
      • 会调用__block对象变量内部的__Block_byref_id_object_dispose函数
      • __Block_byref_id_object_dispose函数内部会调用_Block_object_dispose函数
      • _Block_object_dispose函数会自动释放指向的对象(release)
  16. block的循环引用问题

    • 出现的原因:对象引用block,block引用对象,并且都是强引用,就会产生循环引用

    • 解决循环引用-ARC

      • 使用__weak、__unsafe_unretained修饰对象

      不同:当引用的对象被释放后,__weak对象会被置为nil,__unsafe_unretained对象不会置为nil,成为野指针

      OC语法_第27张图片
      image

      注意:在block中,为防止__weak修饰的对象提前被释放(一般出现在多线程中),通常使用__strong修饰弱指针,以确保在block执行期间一直存在

      • 使用__block修饰对象

      注意:必须要在block中将__block修饰的对象置为nil;必须要调用block方法

      OC语法_第28张图片
      image
    • 解决循环引用-MRC

      • 使用__unsafe_unretained修饰对象(在MRC中没有__weak)
      • 使用__block修饰对象(在MRC中__block变量不会强引用对象)

你可能感兴趣的:(OC语法)