OC基础知识梳理

OC语法相关

1、一般什么情况下用分类

  • 用分类可以声明私有属性,但是一般情况下不会用分类(category),而是用类扩张(extention)
  • 可以用分类拆解体积庞大的类,比如常把AppDelegate 利用分类拆分成推送,分享,登录等不同功能模块的分类
  • 可以用于Hook第三方库的方法,如果你想不修改别人的代码,但是需要修改他们提供的方法,完全可以为需要修改的方法所在类编写一个分类之后hook。

2、分类和类扩张的区别

  • extension相当于一个头文件而已,extension(类扩张)只能编写方法声明,所编写的方法和属性都需要在.m文件实现。如果你在extension中书写了属性(property),那么必需要把extension文件import到.m文件中,让系统帮你生成setter和getter方法,或者导入之后自己手动生成setter方法和getter方法。
  • 分类(category)有自己的数据结构objc_category,可以编写方法和关联属性。分类编写的方法会在runtime的时候,调用remethodizeClass 把方法整合到类对象中。
  • 如果类A有两个分类并且有同名的分类方法,那么后编译的分类的方法会插入到类对象方法列表的前面。之后如果调用该方法,会出发msg_Send方法查找,后编译的分类同名方法会被优先查找到。所以会产生类似后编译分类的同名方法覆盖其他分类的同名方法现象。
  • 分类的+load方法会在类的+load方法后调用。分类的+load方法调用顺序和编译顺序有关
  • 父类的+load方法调用之后再调用子类的+load方法,父类和子类的+load方法调用完之后再调用分类的+load方法(调用顺序只和编译顺序有关)
  • initialize方法会在类第一次接受到消息的时候调用,调用顺序是父类->子类。如果分类有重写initialize方法,则会覆盖父类的initialize。如果多个分类重写了initialize方法,那么就会调用最后编译分类的initialize方法。
  • 利用关联对象实现的setter和getter方法,可以被KVO。
继承关系 BeautyCat -> Cat - > Animal()
类和分类 Animal,Animal+bb,Animal+aa,Cat,Cat+bb,Cat+aa,BeautyCat,BeautyCat+aa
测试结果:
/*------------------------------------------------------*
2021-04-15 11:17:46.141459+0800 cccExtention[40555:7540175] +[Animal load]
2021-04-15 11:17:46.159957+0800 cccExtention[40555:7540175] +[Cat load]
2021-04-15 11:17:46.161076+0800 cccExtention[40555:7540175] +[BeautyCat load]
2021-04-15 11:17:46.161541+0800 cccExtention[40555:7540175] +[BeautyCat(aa) load]
2021-04-15 11:17:46.163238+0800 cccExtention[40555:7540175] +[Animal(aa) load]
2021-04-15 11:17:46.163901+0800 cccExtention[40555:7540175] +[Animal(bb) load]
2021-04-15 11:17:46.164747+0800 cccExtention[40555:7540175] +[Cat(aa) load]
2021-04-15 11:17:46.166138+0800 cccExtention[40555:7540175] +[Cat(bb) load]
2021-04-15 11:17:46.252135+0800 cccExtention[40555:7540175] +[Animal(bb) initialize]
2021-04-15 11:17:46.252318+0800 cccExtention[40555:7540175] +[Cat(bb) initialize]
2021-04-15 11:17:46.253647+0800 cccExtention[40555:7540175] +[BeautyCat(aa) initialize]

疑问

  • category分类的属性是存储在管理对象上的,那么为什么在attachCategories方法中还需要把property_list_t整合到类中呢?
  • 在obj4-781 版本中,attachCategories方法对一个类的分类数量做了限制(64个),如果多出64个会怎么处理?
  • xcode 14 如何编译运行obj4-818.2?

3、关联对象

  • 系统维护了一个全局 AssociationsManager专门用于管理所有关联对象
  • 通过类指针(利用哈希查找)可以快速在AssociationsManager中的AssociationHashMap找到该类的关联对象存储结构ObjectAssociationMap。
  • ObjectAssociationMap 的key就是我们调用objc_setAssociatedObject 设置的key,value 是一个ObjcAssociation 对象
  • ObjcAssociation对象包含关联属性的值和内存管理方案
  • 可以通过把objc_setAssociatedObject方法的参数value设置为nil,删除某个关联对象值.

ObjcAssociation对象 在runtime源码的定义

class ObjcAssociation {
    uintptr_t _policy; // 内存管理策略()
    id _value; // 存储的值
};

policy 可以取的值

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

示例代码

-(void)setVersion:(NSString * _Nonnull)version
{
    objc_setAssociatedObject(self, @selector(version), version, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)version
{
    NSString *ver = objc_getAssociatedObject(self, _cmd);
    return ver;
}

4、KVO

  • KVO 是key-value observing的缩写。
  • KVO是 objective-c 对观察者模式的实现。
  • 如果一个实例对象ABC添加了KVO,那么系统在运行时会为这个对象生成一个中间类——NSKVONotifying_ABC,之后把实例对象ABC的isa指针指向NSKVONotifying_ABC,NSKVONotifying_ABC的superClass指针指向原本类对象。所以当调用set方法的时候,会先调用NSKVONotifying_ABC的set方法。在NSKVONotifying_ABC 的set方法中会调用willChangeValueForKey 和didChangeValueForKey两个方法。具体实现如下
-(void)setName:(NSString *)name
{
    [self willChangeValueForKey:@"name"];
    [super setName:name]; //原来的setter方法
    [self didChangeValueForKey:@"name"]; // 调用observeValueForKeyPath 方法
}

示例代码

//添加监测网页加载进度的观察者
[self.webView addObserver:self
               forKeyPath:NSStringFromSelector(@selector(estimatedProgress))
                  options:0
                  context:nil];
         
-(void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                       change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                      context:(void *)context{
    
    if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))]
        && object == _webView) {
        NSLog(@"网页加载进度 = %f",_webView.estimatedProgress);
        }
}

5、KVC

流程图

  • +(BOOL)accessInstanceVariablesDirectly 返回YES,告诉系统寻找相似的key,返回NO告诉系统不找相应的key,默认返回YES,valueForKey方法和setValueForKey方法当没找到key都会导致崩溃
KVC.jpeg

6、isa走位图

isa走位图.png

7、 属性关键字有哪些?可以分为几类?

  • 属性关键大体上可以分为三类——读写相关,引用计数器相关,原子性操作相关。

读写相关

  • readonly,表示这个属性在外部紧紧可读
  • readwrite,表示这个属性在外部可读可写
  • 如果你要申明一个属性对外部可读,对内部可读可写,那么可以在.h文件用readonly,之后再.m文件再用readwrite

引用计数器内存管理相关

  • copy属性关键字常用于修饰不可变对象,比如NSString对象和NSArray对象。不可以对象的copy操作属于浅拷贝,所以不可变对象用strong修饰也是没问题的。So,用retain也一样。
  • retain 强引用持有对象,在ARC环境下,用strong取代retain。
  • strong 强引用持有对象,会让对象的引用计数器+1
  • weak 弱引用,不会导致对象的引用计数器增加,并且对象释放的时候,会把指针置为nil。
  • assign 一般来说,用于修饰基本数据结构,在MRC环境下也可以用于修饰对象,但是不会导致对象的引用计数器增加,所以类似 unsafe_unretain,是不安全的。
  • unsafe_unretain MRC环境下的属性关键字,引用对象,但是不会导致对象引用的计算器+1,也就是普通的指针(对比C++的智能指针)。

线程安全相关

  • nonatomic,非原子操作,默认也是这个属性。所以默认情况下,属性读写是没有线程安全的。
  • atomic,原子操作,被这个关键字修饰的属性,读写是线程安全。但是,如何是容器类对象,那么修改容器类对象的内容依然是线程不安全的,所以最好还是自己加锁实现线程安全代码,而不是用atomic。

8、objc源码常见的数据结构

isa_t

isa_t 是一个联合体,部分源码如下

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

    Class cls;
    uintptr_t bits;

#if SUPPORT_PACKED_ISA
# 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)
    };

    };

可以看出isa_t 只有两个数据成员,一个是指针类型Class cls,一个是unsigned long类型uintptr_t bits,所以整个联合体的内存只有64位。

  • nonpointer 表示是否对isa开启指针优化 。0代表是纯isa指针,1代表除了地址外,还包含了类的一些信息、对象的引用计数等。
  • has_assoc 表示是否存在关联对象。
  • has_cxx_dtor:该对象是否有C++或Objc的析构器,如果有析构函数,则需要做一些析构的逻辑处理,如果没有,则可以更快的释放对象。
  • shiftcls 存在类指针的值,开启指针优化的情况下,arm64位中有33位来存储类的指针
  • magic:判断当前对象是真的对象还是一段没有初始化的空间(有待研究)
  • weakly_referenced:是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象释放的更快
  • deallocating:是否正在释放
  • has_sidetable_rc:当对象引用计数大于10时,则需要进位
  • extra_rc:表示该对象的引用计数值,实际上是引用计数减一。例如:如果引用计数为10,那么extra_rc为9。如果引用计数大于10,则需要使用has_sidetable_rc

objc_object 结构体

struct objc_object {
private:
    isa_t isa;
public:
    .....
}

可以看到objc_object在源码中只有一个私有成员变量isa,类型是isa_t,这个结构类型是一个联合体,里面只包含64位数据,但是会根据不同的情况,64位数据表示方式有所区别。
接下来就是这个结构体提供给外部使用的方法

//部分objc_object 结构体中的方法如下
//初始化方法
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);

//isa 类型判断方法
bool hasNonpointerIsa();
bool isTaggedPointer();
bool isBasicTaggedPointer();
bool isExtTaggedPointer();

//内存管理相关方法
id retain();
void release();
id autorelease();

Class 类型

Class 类型在runtime源码定义为

typedef struct objc_class *Class;

objc_class 结构体

objc_class 继承自 objc_object,在runtime的部分代码如下

// objc_class继承于objc_object,因此
// objc_class中也有isa结构体
struct objc_class : objc_object {
    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() {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ......
    bool isARC() {
        return data()->ro->flags & RO_IS_ARC;
    }
    ......
}

objc_class 结构体其实就是我们平时所说的类对象,有指向父类的isa指针(superclass),有方法缓存cache_t,有类的方法,属性,协议(保存在class_rw_t中)等信息。

cache_t 结构体

先看看cache_t 结构体的源码

struct cache_t {
    struct bucket_t *_buckets; //散列表
    mask_t _mask;   //散列表的长度 -1
    mask_t _occupied; //已经缓存的数量
    ......
    
}

bucket_t 在arm64的定义为

struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    MethodCacheIMP _imp;
    cache_key_t _key;
#else

可以看到cache_t 是利用散列表来缓存bucket_t对象,而bucket_t保存了缓存key和方法调用的 IMP指针

class_data_bits_t 结构体

struct class_data_bits_t {

    // Values are the FAST_ flags above.
    uintptr_t bits;
    ......
}
  • 相当于 unsigned long bits; 占64位 bits实际上是一个地址(是一个对象的指针,可以指向class_ro_t,也可以指向class_rw_t),其实是对class_rw_t 结构体的一个简单封装,提供一些操作class_rw_t的方法
  • 为什么要这个设计,有待验证??

class_rw_t 结构体

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    // 方法信息
    method_array_t methods;
    // 属性信息
    property_array_t properties;
    // 协议信息
    protocol_array_t protocols;
    ......
}

可以看到class_rw_t 结构体的rw(readwrite)表示可读写的意思,这里保存了类的方法,属性,协议等信息,class_ro_t 结构体也保存了以上信息,但是他是不可读写的(ro,readonly),所以在运行时会把分类的方法,协议等信息动态添加到这里。

method_t 结构体

struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;
    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

method_t 有三个成员变量

  • SEL name 也就是我们调用方法的时候,传递的选择子selector
  • const char *types; 方法的返回值和参数,比如@v等字符串(类型编码)
  • MethodListIMP imp; 函数体(IMP)

property_t结构体

struct property_t {
    const char *name;
    const char *attributes;
};
  • name 属性名字
  • attributes 属性修饰符(nonatomic,readonly,strong等)

9、@dynamic和@synthesize

这两个关键字都是和@property 相关的,如果这两个关键字都没有写,只写了@property来声明属性,那么默认就是@synthesize var = _var;
如果添加了@dynamic 关键字,那么就是告诉编译器自己手动生成setter和getter方法(如果是readonly就只需生成getter),如何你没有生动生成,那么运行时就不会报错。

额外:
.m文件反编译cpp文件的指令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

UI相关

1、UIView和CALayer的关系

持续更新ing

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

推荐阅读更多精彩内容

  • 一、C 程序的内存结构 因为 Objetive-C 是基于 C 之上的,为了能充分了解 OC 中的 Stack 和...
    Xxxxxb_阅读 974评论 0 3
  • 从 NSObject 的初始化了解 isa 代替 isa 指针的是结构体 isa_t, 这个结构体中"包含"了当前...
    name007阅读 625评论 0 0
  • iOS 基础知识概述 基本修饰属性 assion-基本用于修饰基本数据类型 如 int 等 是弱引用 copyco...
    浮萍向北阅读 367评论 0 5
  • 技术基础 1、我们说的Objective-C是动态运行时语言是什么意思? 答:OC可以通过Runtime这个运行时...
    慌莫染阅读 1,352评论 0 4
  • 1、weak关键字的作用 weak的作用是弱引用,它修饰的对象在释放时会置为nil,避免错误的内存访问。一般用于d...
    Lorne_coder阅读 750评论 0 7