3~5年开发经历 的 iOS工程师 应该知道的常识点~本篇总结了以下内容
- 目标
- 类目标
- 分类
- runtime
- 音讯与音讯转发
导航
iOS 进阶常识总结(一)
- 目标
- 类目标
- 分类
- runtime
- 音讯与音讯转发
iOS 进阶常识总结(二)
- KVO
- KVC
- 多线程
- 锁
- runloop
- 计时器
iOS 进阶常识总结(三)
- 视图烘托和离屏烘托
- 事情传递和呼应链
- crash处理和功用优化
- 编译流程和发动流程
iOS 进阶常识总结(四)
- 内存办理
- 野指针处理
- autoreleasePool
- weak
- 单例、告诉、block、承继和调集
iOS 进阶常识总结(五)
- 网络基础
- AFNetWorking
- SDWebImage
目标
id
和NSObject *
的差异?
-
id
是struct objc_object
结构体指针,能够指向任何OC目标
,理解为万能指针 -
NSObject *
是指向NSObject
目标的指针
id
类型为什么无法用点语法
id
类型无法确认所指的类型,无法查找setter、getter
。
Null、nil、Nil、NSNull
-
NULL
,指针是空值,用来判别C指针
-
nil
是指一个OC目标
,指针为空 -
Nil
是指一个OC类
为空 -
NSNull
则用于填充调集元素;这个类只有一个办法null
==、 isEqualToString、isEqual差异?
- 1、
==
比较的是两个内存地址是否相同。 - 2、
isEqualToString
比较的是两个字符串是否持平。 - 3、
isEqual
判别两个目标在类型和值上是否都相同。
isEqual和hash的联系
-
isEqual
一般用于比较目标是否持平,能够重写,经过判别地址指针,判别类型,判别特点等方式。 -
hash
,哈希值,hash
或许冲突。能够重写,一般用地址或许唯一标识组成。 -
hash
,在目标增加到NSSet
和NSDictionary
的时分被调用,查找效率高 -
hash
和isEqual
没有必然联系
iOS中内省的几个办法?
-
isMemberOfClass
:obj
是否某类的目标 -
isKindOfClass
:obj
是否某类及其子类的目标 -
isSubclassOfClass
某类是否是另一个类的子类 -
isAncestorOfObject
某类是否是另一个类的父类 -
respondsToSelector
是否能呼应某办法 -
conformsToProtocol
是否遵从某协议
深复制和浅复制
- 1、浅复制是引证的复制
- 2、深复制是值的复制,会新建一个目标
- 3、
copy
出来的目标是不可变类型,mutableCopy
出来的目标是可变类型
阻挠编译器主动组成,有哪几种方式呢?
@dynamic
-
readonly
关键字
NSString
类型为什么要用copy
润饰 ?
- 首要避免
NSString
被修正。 - 当
NSString
的赋值来源是NSString
时,strong
和copy
效果相同。 - 当
NSString
的赋值来源是NSMutableString
,copy
会做深复制从头生成一个新的目标,修正赋值来源不会影响NSString
的值。
int * const p 、int const *p 、const int *p 、 const int * const p
-
int const *p 、const int *p
,p 能够改,*p 不能够改 -
int * const p
,p 不能够改,*p 能够改 -
const int * const p
p 和 *p 都不能够改
@public、@protected、@private、@package
声明各有什么含义?
-
@public
任何地方都能拜访; -
@protected
该类和子类中拜访,是默许的; -
@private
只能在本类中拜访; -
@package
本包内运用,跨包不能够。
@synthesize
和 @dynamic
分别有什么效果
-
@property
默许运用@syntheszie, var = _var;
-
@synthesize
:假如没有手动完结setter & getter
,编译器会主动加上这两个办法。 -
@dynamic
:告知编译器,setter & getter
由程序员完结,不需求主动生成。假如没有完结setter & getter
,编译的时分不会报错,可是运用到的时分会因找不到办法完结crash
static
有什么效果?
-
static
润饰的变量能够被本文件此语句之后的所有函数拜访,外部文件无法拜访 -
static
的变量能且只能被初始化一次,默许为0 -
static
润饰的函数能够被类办法拜访
类
@property
的实质是什么?ivar、getter、setter
是怎么生成并增加到这个类中的?
- 声明特点,体系给当时类主动生成一个带下划线的成员变量,
setter、getter
办法。@property = ivar(实例变量) + getter + setter
- 经过主动组成(
autosynthesis
),编译器在编译期主动编写拜访这些特点所需的办法。除了生成getter、setter
外,编译器主意向类中增加适当类型的实例变量,并且在特点名前面加下划线,以此作为实例变量的姓名。
final
关键字有什么用
- 这个关键字能够用来润饰
class、func、var
。 - 被润饰的目标无法被承继,无法被重写。
实例办法和类办法的差异?
- 1、实例办法能拜访成员变量。
- 2、类办法中必须创立或许传入目标才干调用目标办法。
- 3、实例办法存储在类目标的办法列表里,类办法存在于元类的办法列表里。
- 4、类办法能够和目标办法重名。
假如在类办法中调用self
会产生什么?
- 拜访到的是类目标而不是实例目标。能够经过
self
调用其他的类办法,可是无法调用目标办法。
讲一下目标,类目标,元类结构体的组成以及他们是怎么相相关的?
- 1、实例目标的结构体是
objc_object
,首要存储的是isa
以及相关的一些函数,例如getIsa
、initIsa
……还有一些关于目标内存办理的相关办法,isTaggedPointer
、isWeaklyReferenced
…… - 2、类目标和元类目标其实都是
Class
,其结构体都是objc_class
,里边存储了isa
、superClass
、成员变量调集、办法调集、协议调集、cache
(办法缓存)等等…… - 3、目标的
isa
指向其类目标、类目标的isa
指向元类、元类的isa
指向根元类,也便是NSObject
的元类。
为什么目标办法中没有保存在目标结构体里边,而是保存在类目标的结构体里边?
- 每个目标都存储同一份实例办法列表太浪费。调用的时分目标只需求经过
isa
找到类目标,在其办法列表里查找就能够了。
类办法存在哪里? 为什么要有元类的存在?
- 1、类办法存储在元类的办法列表里,元类保存了类目标以及类办法的信息。
- 2、为了调用类办法,这个类目标的
isa
指针必须指向一个包含类办法的objc_class
结构体。
objc_getClass
、object_getClass
、class
办法有什么差异?
-
objc_getClass
- 传入类姓名符串,回来类目标(
Class
)
- 传入类姓名符串,回来类目标(
-
class
办法- 实例目标调用
class
实际上调用object_getClass(self)
,回来的是类目标 - 类目标调用
class
回来的是本身
- 实例目标调用
-
object_getClass
- 传入实例目标(
instance
),回来类目标(Class
) - 传入类目标(
Class
),回来元类(meta-class
)目标 - 传入元类(
meta-class
),回来基类的元类(NSObject
的meta-class
)
- 传入实例目标(
类与类之间的音讯传递,有哪几种方式呢?
- 托付署理
delegate
- 音讯告诉
Notification
-
KVO
键值监听 block
什么情况运用 weak
关键字,和 assign
有什么不同?
-
weak
-
weak
关键字解决了一些循环引证的问题, 比方delegate
,block
,xib
连线出来的控件一般也是weak
(也能够用strong
) -
weak
表明了一种“非拥有的联系”,不保存新值,也不开释旧值。weak
只能润饰OC目标。
-
-
assign
-
assign
一般用于润饰非OC目标,常用于根本数据类型,MRC时能够润饰OC目标。 -
assign
润饰目标时,当目标开释后(由于不存在强引证,离开效果域目标内存或许被回收),指针的地址还是存在的。指针并没有被置为nil,下次再拜访该目标就会造成野指针异常。 -
assign
润饰根本数据类型时,由于根本数据类型是分配在栈上的,由体系分配和开释,所以不会造成野指针。
-
copy assign retain weak关键字
-
copy
:树立一个索引计数为1的目标,然后开释旧目标 -
assign
:简单赋值,不更改引证计数 -
retain
:开释旧目标,将新目标赋予成员变量,再提高新目标的引证计数 -
weak
:非持有联系
assign能够用于目标吗
- 这么写大概率呈现野指针溃散。由于没有强引证目标,目标会被开释可是指针还在。
什么时分用copy
关键字?
-
NSString、NSArray、NSDictionary
等类型有可变的子类型,为了保证其不被更改,常用copy
润饰- copy润饰的实质是为了设置
setter
办法 - 例如
setName:
传进一个nameStr
,运用copy
润饰词传给成员变量_name
的其实是[nameStr copy];
。
- copy润饰的实质是为了设置
-
block
用copy
润饰,是MRC沿用下来的习惯。在 MRC 中办法内部的block
是在栈区的, 运用copy
能够把它复制到堆区。在 ARC 中能够用strong
或许copy
润饰,由于block
的retain
操作也是靠copy
完结。
不必copy
会有什么问题?
-
strong
润饰的NSString
类型的name
特点,传一个NSMutableString
,或许有后续问题。 - 例如把可变字符串
mutableString
赋值给name
,改变mutableString
的值会导致name
也跟随改变。而copy
润饰下,不会有这种改变。 - 当润饰可变类型的特点时,如
NSMutableArray、NSMutableDictionary、NSMutableString
,用strong
。当润饰不可变类型的特点时,如NSArray、NSDictionary、NSString
,用copy
。
自定义类的目标怎样才干用copy
润饰符?
- 自定义类的目标具有复制功用,需求完结
NSCopying
协议。 - 假如自定义的目标分为可变版别与不可变版别,要同时完结
NSCopying
与NSMutableCopying
协议。
MRC怎么重写带copy
关键字的setter
?
- (void)setName:(NSString *)name {
[_name release];
_name = [name copy];
}
怎样完结外部只读的特点,让它不被外部篡改?
- 头文件用
readonly
润饰并声明该特点。 - 正常情况下,特点默许是
readwrite
,可读写,假如咱们设置了只读特点,就表明不能运用setter
办法。 - 在
.m
文件中不能运用self.ivar = @"aa";
只能运用实例变量_ivar = @"aa";
- 外界想要修正只读特点的值,需求用到kvc赋值
[object setValue:@"mm" forKey:@"ivar"];
。
编程题:完结以下功用
- 1、编写一个自定义类:
Person
,父类为NSObject
。 - 2、该类有两个特点,外部只读的特点
name
,还有一个特点age
。 - 3、为该类编写一个初始化办法
initWithName:(NSString *)nameStr
,并依据该办法参数初始化name
特点。 - 4、假如两个
Person
类的name
持平,则以为两个Person
持平
@interface Person : NSObject
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithName:(NSString *)nameStr;
@end
@implementation Person
- (instancetype)initWithName:(NSString *)nameStr {
if (self = [super init]) {
_name = nameStr;
}
return self;
}
- (BOOL)isEqual:(id)object {
if ([object isMemberOfClass:[Person class]] == NO) {
return NO;
}
Person *p = (Person *)object;
NSString *name1 = self.name;
NSString *name2 = p.name;
if (!name1 && !name2) {
return YES;
}
return [name1 isEqualToString:name2];
}
@end
- 初始化的办法中为什么将参数赋给
_name
,为什么这样写就能拜访到特点声明的示例变量?- 编辑器在编译期会主动补全出
@synthesize name = _name
的代码
- 编辑器在编译期会主动补全出
- 初始化办法中的
_name
是在什么时分生成的?分配内存的时分还是初始化的时分?- 编译的时分主动的为
name
特点生成一个实例变量_name
- 编译的时分主动的为
-
_name
存放在什么地方- 成员变量存储在堆中(当时目标对应的堆得存储空间中) ,不会被体系主动开释,只能有程序员手动开释。
- 初始化
return
的self
是在上面时分生成的?-
在
alloc
时分分配内存,在init
初始化的。
-
分类
category
的效果
- 从架构上说,能够分门别类存放代码,增加代码的可读性,外部能够按需加载功用
- 为已有的类做扩展,增加特点、办法、协议
- 复写办法
- 揭露私有办法
- 模拟多承继
category
的完结原理,怎么被加载的
-
category
编译完结的时分和类是分隔的,在程序运行时才经过runtime
合并在一起。 -
_objc_init
是Objcet-C runtime
的进口函数,首要读取Mach-O
文件完结OC
的内存布局,以及初始化runtime
相关数据结构。这个函数里会调用到两外两个函数,map_images
和load_Images
-
map_images
追溯进去发现其内部调用了_read_images
函数,_read_images
会读取各种类及相关分类的信息。 - 读取到相关的信息后经过
addUnattchedCategoryForClass
函数树立类和分类的相关。 - 树立相关后经过
remethodizeClass -> attachCategories
从头规划办法列表、协议列表、特点列表,把分类的内容合并到主类 - 在
map_images
处理完结后,开始load_images
的流程。首先会调用prepare_load_methods
做加载预备,这里边会经过schedule_class_load
递归查找到NSObject
然后从上往下调用类的load
办法。 - 处理完类的
load
办法后取出非懒加载的分类经过add_category_to_loadable_list
增加到一个大局列表里 - 最后调用
call_load_methods
调用分类的load
函数
load
办法加载次序
- 类的
load
办法在其父类load
办法后履行 - 分类的
load
办法在主类load
办法后履行 - 两个分类的
load
办法履行次序遵从先编译先履行
load
、initialize
办法的差异什么?在承继联系中他们有什么差异
-
load
办法在运行时调用,加载类或分类的时分调用一次,承继联系参阅load
办法加载次序。 -
initialize
在第一次本身或子类接受objc_msgSend
音讯的时分调用。假如子类没有完结initialize
,会调用父类的。所以父类的initialize
或许被调用屡次 -
load
办法是直接经过办法调用地址调用的,initialize
则是经过isa
走位查找调用的 -
load
办法不会被掩盖,initialize
能够掩盖
代码写在load
和initialize
中会影响发动吗
-
load
办法中增加操作会影响发动速度,由于load
办法在发动时调用 -
initialize
办法在第一次调用办法时触发,对发动影响相对较小
多个category
的都有同名办法,会运用哪一个
- 从头规划办法列表的时分,后处理的分类的办法会被放在办法列表前面。
- 调用的时分只需找到考前的那个办法就会马上履行,所以就会履行后加载的那个分类的办法。
-
Build Phases -> Compile Source
靠后的分类。
category
& extension
差异,能给NSObject
增加extension
吗?
-
extension
扩展是特别的category
,称为匿名分类或许私有分类,能够为类增加成员变量和办法。 -
extension
在编译期决议,category
则是在运行时加载。 -
extension
一般用来躲藏私有信息,category
能够揭露私有信息 - 无法给体系类增加
extension
,可是能够给体系类增加category
-
extension
能够增加成员变量,而category
不能够 -
extension
和category
都能够增加特点,可是category
的特点不能主动生成成员变量、getter、setter
分类中增加实例变量和特点分别会产生什么,还是什么时分会产生问题?为什么
- 增加实例变量编译时报错。
- 增加特点没问题,可是在运行的时分运用这个特点程序crash。原因是没有实例变量也没有
set/get
办法。 - 能够经过相关目标去完结
分类中为什么不能增加成员变量(runtime
在外)?
- 类目标在创立的时分现已定好了成员变量,可是分类是运行时加载的,无法增加。
- 类目标里的
class_ro_t
类型的数据在运行期间不能改变,再增加办法和协议都是修正的class_rw_t
的数据。 - 分类增加办法、协议是把
category
中的办法,协议放在category_t
结构体中,再复制到类目标里边。可是category_t
里边没有成员变量列表。 - 虽然
category
能够写上特点,其实是经过相关目标完结的,需求手动增加setter & getter
。
分类能够增加那些内容
- 实例办法
- 类办法
- 协议
- 特点
相关目标的完结和原理
- 相关目标不存储在相关目标本身内存中,而是存储在一个大局容器中;
- 这个容器是由
AssociationsManager
办理并在它维护的一个单例Hash
表AssociationsHashMap
;- 第一层
AssociationsHashMap
:类名object :bucket(map) - 第二层
ObjectAssociationMap
:key(name):ObjcAssociation(value和policy)
- 第一层
-
AssociationsManager
运用AssociationsManagerLock
自旋锁保证了线程安全。 - 经过
objc_setAssociatedObject
给某目标增加相关值 - 经过
objc_getAssociatedObject
获取某目标的相关值 - 经过
objc_removeAssociatedObjects
移除某目标的相关值
运用相关目标,需求在主目标 dealloc 的时分手动开释么?
- 不需求,主目标经过
dealloc -> object_dispose -> object_remove_assocations
进行相关目标的开释。
能否向编译后得到的类中增加实例变量, 能否向运行时创立的类中增加实例变量?
- 不能够,编译完结的类现已生成了不可变的成员变量调集。
- 能够,经过
class_addIvar
函数给运行时创立的类增加实例变量。
主类存在了foo
办法,分类也存在foo
办法,调用时会呈现什么情况? 假如想履行主类的foo
办法,怎么去做?
-
主类的办法不会被调用,分类的办法会被调用。分类和主类的
foo
办法都存在办法列表里,只是分类办法在前,主类办法在后, 调用的时分会首先找到第一次呈现的办法。 -
假如想要要履行主类的办法,需求逆序遍历办法列表,第一次遍历到的foo办法便是主类的办法
- (void)foo{ [类 invokeOriginalMethod:self selector:_cmd]; } + (void)invokeOriginalMethod:(id)target selector:(SEL)selector { uint count; Method *list = class_copyMethodList([target class], &count); for ( int i = count - 1 ; i >= 0; i--) { Method method = list[i]; SEL name = method_getName(method); IMP imp = method_getImplementation(method); if (name == selector) { ((void (*)(id, SEL))imp)(target, name); break; } } free(list); }
runtime
OC
是动态运行时言语是什么意思?
- 动态类型:运行时确认目标的类型,编译时期能经过,但不代表运行过程中没有问题
- 动态绑定:运行时才确认目标调用的办法(音讯转发)
- 动态加载:动态库的办法完结不复制到程序中,只记录引证,直到运用相关办法的时分才到库里边查找办法完结
runtime
能做什么?
- 获取类的成员变量、办法、协议
- 为类增加成员变量、办法、协议
- 动态改变办法完结
class_copyIvarList
与class_copyPropertyList
的差异?
- 1、
class_copyIvarList
能够获取.h
和.m
中的所有特点以及@interface
大括号中声明的变量,获取的特点称号有下划线(大括号中的在外)。 - 2、
class_copyPropertyList
只能获取由@property
声明的特点(包含.m
),获取的特点称号不带下划线。
class_ro_t
和class_rw_t
的差异?
-
class_rw_t
供给了运行时对类拓展的能力,class_rw_t
结构体中存储了class_ro_t
。 -
class_ro_t
存储的是类在编译时现已确认的信息,是不可改变的。 - 二者都存有类的办法、特点(成员变量)、协议等信息,不过存储它们的列表完结方式不同。简单的说
class_rw_t
存储列表运用的二维数组,class_ro_t
运用的一维数组。 - 运行时修正类的办法,特点,协议等都存储于
class_rw_t
中
什么是 Method Swizzle(黑魔法),什么情况下会运用?
-
Method Swizzle
是改变一个已存在的选择器(SEL
)对应的完结(IMP
)的过程。 - 类的办法列表存放着
SEL
的姓名和IMP
的映射联系。 - 开发者能够运用
method_exchangeImplementations
来交换2个办法中的IMP
- 开发者能够运用
method_setImplementation
来直接设置某个办法的IMP - 这就能够在运行时改变
SEL
和IMP
的映射联系,从而完结办法替换。
Method Swizzle
注意事项
- 为了保证
Swizzle Method
办法替换必定被履行调用,能够在load
中履行 -
+load
里边运用的时分不要调用[super load]
。假如屡次调用了[super load]
,或许会呈现“Swizzle无效”的假象 - 避免调用
[super load]
导致Swizzling
屡次履行,在load
中运用dispatch_once
保证交换只被履行一次。 - 子类替换没有完结的承继办法,会替换掉父类中的完结,影响父类及其他子类
-
+initialize
里边运用要加dispatch_once
- 进行版别迭代的时分需求进行一些检验,避免体系库的函数产生了改变
怎么hook
一个目标的办法,而不影响其它目标
- 办法1:新建一个子类重写办法
- 办法2:让这个目标的类遵从某个协议,
hook
时判别。弊端是其他目标遵从了这个协议会受到影响。 - 办法3:运行时创立一个新的子类,修正目标
isa
指针指向子类,hook
时运用isKindOf
判别类型
音讯发送
音讯机制
- 1、快速查找,办法缓存
- 2、慢速查找,办法列表
- 3、音讯转发
- 3-1、办法的动态解析,
resolveInstanceMethod
- 3-2、快速音讯转发,
forwardingTargetForSelector
- 3-3、规范音讯转发,
methodSignatureForSelector & forwardInvocation
- 3-1、办法的动态解析,
objc
中向一个nil
目标发送音讯将会产生什么?
- 在寻找目标的
isa
指针时,回来地址0x0
,不回做任何操作,也不会有任何错误。
objc
在向一个目标发送音讯时,产生了什么?
- 办法调用实际上是发送音讯,经过调用
objc_msgSend()
完结的。 - 首先,经过
obj
的isa
指针找到对应的class
。 - 然后,开启快速查找流程。在
class
的缓存办法列表(objc_cache
)里查找办法,假如找到就直接回来对应IMP
。 - 假如在缓存中找不到,开始慢速查找流程。在
class
的Method List
查找对应办法,找到了回来对应IMP
。 - 都找不到就会走音讯转发流程
_objc_msgForward
函数是做什么的?
-
_objc_msgForward
用于音讯转发:向一个目标发送一条音讯,但它并没有完结的时分,就调用_objc_msgForward
尝试做音讯转发。
为什么需求做办法缓存?
- 每次履行这个办法的时分都查一遍
Method List
太消耗功用。 - 运用
objc_cache
把调用过的办法做一个缓存, 把method_name
作为key
,method_IMP
作为value
。 - 下次接收到音讯的时分,直接经过
objc_cache
去找到对应的IMP
即可, 避免每一次都去遍历objc_method_list
一直都找不到办法怎么办?
- 会触发音讯转发机制,咱们一共有三次时机弥补以避免
crash
- 办法的动态解析,经过
resolveInstanceMethod
增加一个IMP
使其履行。 - 快速音讯转发,在
forwardingTargetForSelector
回来一个能够履行该办法的目标。 - 规范音讯转发,
methodSignatureForSelector
创立相同办法类型的办法签名(NSMethodSignature
),然后重写forwardInvocation
并把拥有该签名的办法赋值到anInvocation.selector
。
音讯转发机制的优劣
- 长处:音讯转发机制供给了找不到办法时的弥补时机。
- 缺陷:一般情况下会在基类做crash处理,那么有或许把一部分的crash忽略曩昔导致无法露出问题。
IMP
、SEL
、Method
的差异和运用场景
-
SEL
相当于一个代号,方便查找办法的代号,处理告诉/定时器等都会用到 -
IMP
是指向办法完结的指针,动态办法解析的时分会用到 -
Method
是一个目标,里边就存有SEL
和IMP
,音讯转发流程获取办法签名的时分会用到