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指令

    image

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

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

  1. instance对象

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

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


      image
  3. meta-class对象

    • 获取方法
      image
    • 每个类在内存中有且只有一个meta-class对象
    • meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中主要包括:isa指针、superclass指针、类的类方法信息(class method)
      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总结

    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指针指向

    image

    image

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


    image.png

三、KVO

  1. 全称:Key-Value Observing,俗称“键值监听”,可用于监听某个对象属性值的改变。
  2. 未使用KVO监听的对象
    image
  3. 使用了KVO监听的对象
    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原理

    image

  4. valueForKey:原理

    image.png

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

五、Category

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

六、+load方法

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

七、+initialize方法

  1. 调用时间:+initialize会在类第一次接受消息时调用
  2. 调用顺序:先调用父类的+initialize,再调用子类的+initialize(先初始化父类,再初始化子类,每个类只初始化一次)
  3. +initialize是通过objc_msgSend方式进行调用
    • 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
    • 如果category实现了+initialize,则会覆盖类本身的+initialize
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. 关联策略

    image

  6. 关联对象原理

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

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

      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)
      image

九、block

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

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

    image

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

    image

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

    • __NSGlobalBlock__(_NSConcreteGlobalBlock)
    • __NSMallockBlock__(_NSConcreteMallockBlock)
    • __NSStackBlock__(_NSConcreteStackBlock)
    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变量包装成一个对象
      image
  12. __block变量的内存管理

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

    • 当block被拷贝到堆上时

      • 会调用block内部的copy函数
      • copy函数内部会调用_Block_object_assign函数
      • _Block_object_assign函数会对__block变量产生强引用(retain)
        image.png
    • 当block从堆上移除时

      • 会调用block内部的dispose函数
      • dispose函数内部会调用_Block_object_dispose函数
      • _Block_object_dispose会自动释放引用的__block变量(release)
        image.png
  13. __block的forwarding指针

    • 目的就是要确保指向的是堆上的内存
      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,成为野指针

      image

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

      • 使用__block修饰对象

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

      image
    • 解决循环引用-MRC

      • 使用__unsafe_unretained修饰对象(在MRC中没有__weak)
      • 使用__block修饰对象(在MRC中__block变量不会强引用对象)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343