iOS知识点总结(面试向)更新中
Swift
1.Swift中struct
和class
的区别
-
Swift
中struct
和class
有什么不一样的地方?首先要先和大家提到一个观念,值类型ValueType
和引用类型ReferenceType
。其中struct
是ValueType
而class
是ReferenceType
。 -
值类型的变量直接包含他们的数据,而引用类型的变量存储对他们的数据引用,因此后者称为对象,因此对一个变量操作可能影响另一个变量所引用的对象。对于值类型都有他们自己的数据副本,因此对一个变量操作不可能影响另一个变量。
-
定义一个struct
struct SRectangle {
var width = 200
}
- 定义一个class
class CRectangle {
var width = 200
}
1.成员变量
class CRectangle {
var width = 200
var height: Int // 报错
}
解释:
struct
定义结构体类型时其成员可以没有初始值,如果使用这种格式定义一个类,编译器是会报错的,他会提醒你这个类没有被初始化。
2.构造器
var sRect = SRectangle(width: 300)
sRect.width // 结果是300
var cRect = CRectangle()
// 不能直接用CRectangle(width: 300),需要构造方法
cRect.width // 结果是200
解释:
所有的struct
都有一个自动生成的成员构造器,而class
需要自己生成
3.指定给另一个变量的行为不同
var sRect2 = sRect
sRect2.width = 500
sRect.width // 结果是300
var cRect2 = cRect
cRect2.width = 500
cRect.width // 结果是500
解释
valueType
每一个实例都有一份属于自己的数据,在复制时修改一个实例的数据并不影响副本的数据。而ReferenceType
被复制的时候其实复制的是一份引用,两份指向同一个对象,所以在修改一个实例的数据时副本的数据也被修改了。
4.不可变实例的不同
let sRect3 = SRectangle(width: 300)
sRect3.width = 500 // 报错
let cRect3 = CRectangle()
cRect3.width = 500
解释:
struct
对于let
声明的实例不能对变量进行赋值,class
预设值是可以赋值let实例的。注意Swift
中常用的String
、Array
、 Dictionary
都是struct
。
struct SRectangle {
var width = 200
mutating func changeWidth(width: Int) {
self.width = width
}
}
class CRectangle {
var width = 200
func changeWidth(width: Int) {
self.width = width
}
}
解释:
struct
的方法要去修改property
的值,要加上mutating
,class
则不需要。
5.继承
struct
不能继承,class
可以继承
2.Swift和Objective-C之间的联系
-
Swift
和Objective-C
共同运用同一套运行时环境 - 同一个工程可以同时使用
Swift
和Objective-C
-
Objective-C
中出现的绝大多数概念,如引用计数,ARC,属性,协议,接口,初始化,扩展类,命名参数,匿名参数等在Swift中依然有效
3.Swift和Objective-C有什么优势?
-
Swift
更容易阅读 -
Swift
更易维护 -
Swift
更加安全 -
Swift
速度更快
4.Swift的内存管理是怎样的?
-
Swift
采用自动引用计数(ARC)来简化内存管理
5. Swift支持面向过程编程吗
- 它采用了
Objective-C
的命名参数以及动态对象模型,可以无缝对接到现有的Cocoa
框架,并且兼容Objective-C
代码,支持面向过程编程和面向对象编程
6.Swift 是一门安全的语言吗
-
Swift
是一门类型安全的语言,Optianals就是代表,Swift
帮助你在类型安全的环境下工作,如果你的代码需要使用String
类型,Swift
会帮助你组织Int
类型的值传过来,这是你在开发阶段发现问题并及时修正
7.为什么在变量类型后面加一个?
- 用来标记这个值是可选的,一般用
?
和!
定义可选变量的区别:用!
定义的可选变量必须保证能够转换成功,否则就会报错,而使用?
定义的可选变量即使转换失败也不会报错
Swift泛型是什么,解决了什么问题
- 泛型是用代码进行安全工作,提供灵活并重用的函数和类型,如果一个函数中实现的功能是将传入的两个值进行互换,不适用泛型的情况下你需要指明传入参数的类型,但是如果你想传入其他的参数类型则需要重新再写一遍这个方法,知识传入参数类型不同,如果使用了泛型就可以解决这个问题
// 定义一个交换两个变量的函数
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
var numb1 = 100
var numb2 = 200
print("交换前数据: \(numb1) 和 \(numb2)")
swapTwoValues(&numb1, &numb2)
print("交换后数据: \(numb1) 和 \(numb2)")
var str1 = "A"
var str2 = "B"
print("交换前数据: \(str1) 和 \(str2)")
swapTwoValues(&str1, &str2)
print("交换后数据: \(str1) 和 \(str2)")
Objective-C
方法和选择器有何不同(method
和selector
)
-
slector
是一个方法的名字,method
是一个组合体,包含了名字和提现
malloc
和new
的区别
-
new
是C++
中的操作符,malloc
是C
中的一个函数 -
new
不止分配内存,还会调用类的构造函数,同理delete
会调用类的析构函数,而malloc
则只分配内存,不会进行初始化类成员的工作,同样free
也不会调用析构函数 - 内存泄露对于
malloc
和new
都可以检查出来,区别在于new
可以指明哪个的哪一行,而malloc
不行 -
new
可以看做是malloc
加上析构函数的执行 -
new
出来的指针是带有类型信息的
你是否接触过OC中的反射机制?简单聊一下概率和使用
- Class反射
- 通过类名的字符串形式实例化对象
Class class NSClassFromString@(@"student");
Student *stu = [[class alloc]init];
- 将类名变为字符串
Class class = [Class class];
NSString *string = NSStringFromClass(class);
- SEL反射,通过方法的字符串形式实例化方法
SEL selector = NSSelectorFromClass(@"setName");
[stu performSelector:selector withObject:@"Mike"];
- 将方法转换了字符串
NSStingFromSelector(@selector*(setName));
什么是SEL
?如何声明一个SEL
?通过哪些方法能够调用SEL
包装起来的方法
-
SEL
就是对方法的一种包装,包装的SEL
类型数据它对于的方法地址,找到方法的地址就可以调用方法。在内存中每一个类的方法都存储在类对象中,每一个方法都有一个与之对应的SEL类型的数据,根据一个SEL数据就可以找到对应的方法地址,进而调用方法
SEL s1 = @selector(test1);//将test1方法包装成SEL对象
调用方法有两种方式:
- 直接通过方法名调用
[person test1]
- 间接通过SEL数据来调用
SEL mod = @selector(test1);
[person performSelector:mod]
协议中<NSObject>
是什么意思?子类继承了父类,那么子类会继承父类中遵守的协议吗?协议中能过定义成员变量?如何约束一个对象类型的变量要存储的地址是一个遵守的协议对象
- 遵守NSObject协议
- 会
- 能,但是只在头文件中声明,编译器是不会自动生成实例变量的。需要自己处理getter和setter方法
- id<xxx>
NS/CF/CG/CA/UI这些前缀分别是什么含义
-
NS
函数归属于cocoa Fundation
框架 -
CF
函数归属于core Fundation
框架 -
CG
函数归属于Core Graphics.frameworks
框架 -
CA
函数归属于Core Animation.frameworks
框架 -
UI
函数归属于UIKit
框架
面向对象都有哪些特征以及对你这些特征的理解
- 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类,基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
- 封装:封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。我们在类中的编写方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一些可隐藏的东西,向外界提供最简单的编程接口
- 多态性:多态性是指允许不同子类型的对象对同一消息做出不同的响应。简单来说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性是编译时的多态性和运行时的多态性。方法重载(
overload
)实现的是编译时的多态性,而方法重写(override
)实现的是运行时的多态性。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事方法重写和对象造型。 - 抽象:抽象是将异类对象的共同特征总结出来的构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
我们说的Objective-C是动态运行时语言是什么意思?
- 主要是将数据类型的确定由编译时,推迟到了运行时,主要涉及到运行时和多态两个概念
- 简单来说,运行时机制使我们知道运行时才去决定一个对象的类别,以及调用该类别对象指定的方法
- 多态:不同对象以自己的方式响应相同消息的能力叫多态
- 运行时机制是多态的基础
readwrite,readonly,assign,retain,copy,nonatomic属性的作用?
-
getter=getterName,setter=setterName,设置setter与getter的方法名
-
readwrite,readonly,设置可供访问级别,assign,setter方法直接赋值,不进行任何retain操作,为了解决原类型与环循引用问题
-
retain,setter方法对参数进行release旧值再retain新值,所有实现都是这个顺序
-
copy,setter方法进行Copy操作,与retain处理流程一样,先旧值release,再Copy出新的对象,retainCount为1。这是为了减少对上下文的依赖而引入的机制。
-
nonatomic,非原子性访问,不加同步,多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。锁被加到所属对象实例级。
简述NotificationCenter、KVO、KVC、Delegate?并说明它们之间的区别?
-
KVO(Key-Value-Observing)
:一对多,观察者模式,键值观察机制,提供观察某一属性变化的方法,极大的简化了代码。 -
KVC(Key-Value-Coding)
:是键值编码,可以通过key
直接访问对象的属性,或者给对象的属性赋值,这样就可以在运行时动态的访问或修改对象的属性;检查是否存在对应的set
方法,存在就调用set
方法,set方法不存在就查找_key
的成员变量是否存在,存在就直接赋值,如果没有找到就找相同名字的key
,存在就赋值,如果没有就调用valueForUndefinedkey
和setValue:forUndefinedKey
。 - Delegate:通常发送者和接受者的关系是直接的一对一的关系;代理的目的是改变或者传递控制链,允许一个类在特定时刻通知到其他类,而不需要获取那些类的指针,可以减少框架复杂度,消息的发送者告知接收者某个时间将要发生,
delegate
同意然后发送者响应时间,delegate
机制使得接收者可以改变发送者的行为。 -
Notification
:观察者模式,同城发送者和接受者关系是间接的多对多关系。消息的发送者告知接收者事件已经发送或者将要发生,仅此而已,接收者并不能反过来影响发送者的行为。 - 区别:
delegate
比NSNotification
效率高,delegate
方法比Notification
更加直接,两个模块联系不是很紧密就用notification
传值
iOS内省的几个方法?
1.什么是内省?
- 在计算机科学中,内省是指计算机程序在运行时(Run time)检查对象(Object)类型的一种能力,通常也被称作运行时类型检查,不应该将内省和反射混淆。相对于内省,反射更进一步,是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力
- iOS内省的的几种方法
isMemberOfClass:Class
:检查对象是否是那个类但不包括继承类而实例化的对象
isKindOfClass:Class
:检查对象是否是那个类或者其继承类实例化的对象
isSubClassOfClass
:检查某个类对象是否是另一个类型的子类
respondToSelector:selector
:检查对象是否包含这个方法
instancesRespondToSelector:
:判断类是否有这个方法
conformsToProtocol
:是用来检查对象是否实现了指定协议类的方法
分类和扩展有什么区别?可以分别用来做什么?分类有哪些局限性?分类的结构体里面有哪些成员?
- 1.分类主要用来为某个类添加方法,属性,协议
Category
Category 是表示一个指向分类的结构体的指针,其定义如下:
typedef struct objc_category *Category;
struct objc_category {
char *category_name OBJC2_UNAVAILABLE; // 分类名
char *class_name OBJC2_UNAVAILABLE; // 分类所属的类名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 实例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 类方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}
通过上面我们可以发现,这个结构体主要包含了分类定义的实例方法与类方法,其中instance_methods 列表是 objc_class 中方法列表的一个子集,而class_methods列表是元类方法列表的一个子集。
但这个结构体里面
根本没有属性列表!
1.分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,只有方法列表。所以< 原则上讲它只能添加方法, 不能添加属性(成员变量),实际上可以通过其它方式添加属性> ;
2.分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量(编译时会报警告);
3.可以在分类中访问原有类中.h中的属性;
4.如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法。所以同名方法调用的优先级为 分类 > 本类 > 父类。因此在开发中尽量不要覆盖原有类;
5.如果多个分类中都有和原有类中同名的方法, 那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法。
- 2.扩展主要用来为某个类原来没有的成员变量,属性和方法(方法只能声明不能实现)
为一个类添加额外的原来没有变量,方法和属性
一般的类扩展写到.m文件中
一般的私有属性写到.m文件中的类扩展中
- 3.区别
①类别中原则上只能增加方法(能添加属性的的原因只是通过runtime解决无setter/getter的问题而已);
②类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的(
用范围只能在自身类,而不是子类或其他地方);
③类扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。
④类扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。
⑤定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。
OC有多继承吗?没有的话用什么方法可以替代?
- 多继承即一个子类可以有多个父类,它继承了多个父类的特性
-
Objective-C
的类没有多继承,只支持单继承,如果要实现多继承可以通过类别和协议的方式来实现 -
Protocol
可以实现多个接口,通过实现多个接口就可以完成多继承 -
Category
一般使用分类,用Category
去重写类的方法,仅对本Category
有效,不会影响到其他类与原有类的关系。
include和#import的区别?#import和@class的区别?
-
#include
是C
中用来引用文件的关键字,而#import
是obj-c
中用来代替include
的关键字。#import
可以确保同一个文件只能被导入一次,从而避免了使用#include
容易引起的重复引用问题,即classA
引用了classC
,classB
也引用了classC
,而当classD
同时引用classA
,classB
的时候就会报重复引用的错误。 -
#import""
与#import<>
:#import""
实现从当前工作目录中找要导入的文件,如果没有再到系统类库中找,而#import<>
是直接从系统类库中找要导入的文件。 - @class只是告诉编译器,后面遇到的这个名称是一个类名称,至于这个类是如何实现的暂不用考虑。引入@class主要是用来解决引用死锁--如果两个类存在循环依赖关系,即A->B,B->A,如果用#import来相互包含,就会出现编译错误:
Expected specifier-qualifier-list before ‘A’或者Expected specifier-qualifier-list before ‘B’。
一般情况下,在 .h
文件中,只需要知道类的名字就可以了,所以用@class
,而在 .m
文件中通常需要知道类的成员变量即方法,所以要用#import
来将类文件导进来。
那为什么不在 .h
文件中直接用#import
来将文件导入呢,因为如果导入大量的头文件,编译器就会花大量的时间来编译。
需要在.h
文件中用#import
的情况:
- 如果有继承关系的要用#import,如,A继承B,需要在A中将B import进来。
- 使用有category的类,需要在 .h文件中用#import将该类的category导进来。
浅复制和深复制的区别?
- 浅复制(
copy
):只复制指向对象的指针,而不复制引用对象本身 - 深复制(
mutableCopy
):复制引用对象本身。深复制就好理解了,内存中存在了两份独立对象本身,当修改A时,A_copy不变
类变量的@protected,@private,@public,@package声明各有什么含义?
变量的作用域不同
-
@protected
该类和子类中访问,是默认的 -
private
只能在本类中访问 -
public
任何地方都能访问 -
package
本包内使用,跨包不可以
Objective-C与C、C++之间的联系和区别
-
Objective-C
和C++
都是C
的面向对象的超集 -
Objective-C
与C++的区别主要点:Objective-C
是完全动态的,支持在运行时动态类型决议(dynamic typing)
,动态绑定(dynamic binding
)以及动态装载(dynamic loading
);而C++
是部分动态的,编译时静态绑定,通过嵌入类(多重继承)和虚函数(虚表)来模拟实现。 -
Objective-C
在语言层次上支持动态消息转发,其消息发送语法为[Object function]
;而且C++
为object>funcation()
。两者的语义也不同,在Objective-C
里说发送消息到一个对象上,至于这个对象能不能响应还是转发消息都不会crash
,而C++
里面说对象进行某个操作,如果对象没有这个操作的话,要么编译会报错(静态绑定),要么程序会crash
掉(动态绑定)。
讲一下atomic的实现机制;为什么不能保证绝对的线程安全?
A:atomic的实现机制
-
atmoic
是property
的修饰词之一,表示是原子性的,使用方式为@property(atomic)int age
;此时编译器会自动生成getter/setter
方法,最终会调用objc_getProperty
方法进行存取属性 - 若此时的属性用
atomic
修饰的话,在这两个方法内部使用os_unfair_lock
进行加锁,来保证读写的原子性。锁都在PropertyLocks
中保存着(iOS平台会初始化8个,mac平台64个),在用之前,会把锁都初始化好,在需要用到的时候,用对象的地址加上成员变量的偏移量key,去PropertyLocks
中去取,因此存取用的是同一个锁,所以atomic能保证属性的存取是线程安全的。 - 注:由于锁是有限的,不用对象,不同数学的读取用的也可能是同一个锁
B:atomic为什么不能保证绝对的线程安全?
-
atomic
在getter/setter
方法中加锁,仅保证了存取时的线程安全,假设我们的属性是@property(atomic)NSMutableArray *array
;可变容器时,无法保证对容器的修改是线程安全的。 - 在编译器自动生产的
getter/sette
r方法,最终会调用objc_getProperty
和objc_setProperty
方法存取属性,在此方法内部保证了线程安全,但是当我们重写getter/setter
方法的时候,只能依靠我们自己在getter/setter
中保证线程安全
atomic和nonatomic的区别
-
atomic
提供多线程安全,防止读写未完成的时候被另一个线程读写,造成数据错误。 -
nonatomic
在自己管理的内存的环境中,解析的访问器保留并自动释放返回值,若指定了nonatomic
,那么访问器只是简单的返回这个值。 -
atomic
和nonatomic
的区别在于,系统自动生成的getter/setter
方法不一样。如果你自己写getter/setter
,那atomic/nonatomic/retain/assign/copy
这些关键字只起提示作用,写不写都一样。对于atomic
的属性,系统生成的getter/setter
会保证get
、set
操作的完整性,不受其他线程影响
Atomic
是默认的
会保证 CPU 能在别的线程来访问这个属性之前,先执行完当前流程
速度不快,因为要保证操作整体完成
Non-Atomic
不是默认的
更快
线程不安全
如有两个线程访问同一个属性,会出现无法预料的结果
简单来说,就是 atomic 会加一个锁来保障线程安全,并且引用计数会 +1,来向调用者保证这个对象会一直存在。假如不这样做,如有另一个线程调 setter,可能会出现线程竞态,导致引用计数降到0,原来那个对象就释放掉了。
id和nil代表什么
-
id
类型的指针可以指向任何OC
对象 -
nil
代表控制(空指针的值,0)
nil、Nil,NULL和NSNull区别
- 我们给对象赋值时一般会使用
object = nil
,表示我想把这个对象释放掉;或者对象由于某种原因,经过多次release
,于是对象引用计数器为0了,系统将这块内存释放掉,这个时候这个对象为nil
,我称它为“空对象”。(注意:我这里强调的是“空对象”,下面我会拿它和“值为空的对象”作对比!!!)所以对于这种空对象,所有关于retain
的操作都会引起程序崩溃,例如字典添加键值或数组添加新原素等. -
NSNull
和nil
的区别在于,nil
是一个空对象,已经完全从内存中消失了,而如果我们想表达“我们需要有这样一个容器,但这个容器里什么也没有”的观念时,我们就用到NSNull
,我称它为“值为空的对象”。如果你查阅开发文档你会发现NSNull
这个类是继承NSObject
,并且只有一个+ (NSNull *) null
类方法。这就说明NSNull
对象拥有一个有效的内存地址,所以在程序中对它的任何引用都是不会导致程序崩溃的。 -
nil
和Nil
在使用上是没有严格限定的,也就是说凡是使用nil
的地方都可以用Nil来代替,反之亦然。只不过从编程人员的规约中我们约定俗成地将nil
表示一个空对象,Nil
表示一个空类。 - 我们知道Object-C来源于C、支持于C,当然也有别于C。而NULL就是典型C语言的语法,它表示一个空指针,参考代码如下:
int *ponit = NULL
;
block 和 weak 的区别
-
__
block 本身并不能避免循环引用,避免循环引用需要在block
内部把__block
的obj
置位nil
。 -
__weak
则可以避免循环引用的出现,但是其会导致外部对象释放了之后,block
内部也访问不到这个对象的问题。如果要避免循环引用问题,则可以在block
内部声明一个__strong
的变量来指向weakObj
,使外部对象能在block
内部保持住 -
__block
会持有该对象,即使超出了该对象的作用域,该对象还是会存在的,直到block
对象从堆上销毁;而__weak
仅仅是将该对象赋值给weak
对象,当该对象销毁时,weak
对象将指向nil
; -
__block
可以让bloc
k修改局部变量,而__weak
不能。另外,在MRC
中__block
是不会引起retain
,而在ARC中则会引起retain
,所以在ARC中应该使用__weak
。 -
__block
不管是ARC
还是MRC
模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。 -
__weak
只能在ARC模式下使用,也只能修饰对象(NSString)
,不能修饰基本数据类型int
; -
_block
对象可以在block
中被重新赋值,__weak
则不可以; -
__bloc
k对象在ARC下可能会导致循环引用,非ARC下会避免循环引用,__weak只在ARC下使用,可以避免循环引用
深入理解block
我理解不深,直接查看资料来源:
iOS中Block实现原理的全面分析