1. load 与 initialize
load:
是当类或分类被添加到 Objective-C runtime (加入内存)时被调用的,
从先父类后子类,先本类后类别的顺序调用;
initialize:
是在类或它的子类收到第一条消息之前被调用的。
分类中的实现会覆盖类中的方法,只执行分类的实现
调用方式:load是根据函数地址直接调用,父类与分类之间+load方法的调用是互不影响的; initialize是通过objc_msgSend调用; 都不需要显式调用父类实现[super load] [super initialize];
使用:load一般是用来交换方法Method Swizzle,由于它是线程安全的,而且一定会调用且只会调用一次;initialize方法主要用来对一些不方便在编译期初始化的对象进行赋值,或者说对一些静态常量进行初始化操作
image
2. #import、#include、#import< >和#import" "的区别
#include 是c/c++导入头文件的关键字, #import 是oc导入头文件的关键字;
#import 不会引起交叉编译的问题, 会自动导入一次,不会重复导入。因为在Objective-C中会存在C/C++和Object-C混编的问题,如果用#include引入头文件,会导致交叉编译。
#import< >引用系统文件,编译器会在系统文件目录下去查找该文件.
#import " ":用户自定义的文件,编译器首先会在用户目录下查找,然后到安装目录中查
@class
仅仅是声明一个类名,并不会包含类的完整声明, 当执行时,才去查看类的实现文件
能解决循环包含的问题:当两个类文件有循环依赖关系 ( A 引用 B , B 引用 A ) 时,需要用 @class
3. Object-C支持多继承吗?可以实现多个接口吗?Category是什么?重写一个类的方式是继承好还是分类好?为什么?
Objective-C不支持多继承(由于消息机制名字查找发生在运行时而非编译时,很难解决多个基类可能导致的二义性问题),可以实现多个接口,但可以间接实现多继承目的的方法:
- 通过类的组合实现“多继承”:
ClassA,ClassB作为ClassC的成员变量。 - 通过协议实现“多继承”:
类在协议的遵守上却允许使用多继承,协议只能提供接口,而没有提供实现方式,如果只是想多继承基类的接口,那么遵守多协议无疑是最好的方法。缺点:需要修改两个父类,同时并不能调用两个父类的原生方法,需要在子类中实现方法 - 通过category实现:
分类可以拓展这个类.添加额外的方法。 Category使得在不修改该类原先代码的情况下,拓展或者修改现有类的定义,并且是向下有效的,既会影响到该类所有子类。用Category重写类的方法,它仅仅只对本Category有效
category的描述
✓ 使用类别就是为了能够为现有类添加新的方法,不用继承该现有类,就可使用现有类的对象调用添加的方法了。
✓ 类别可以使类的实现分散在多个文件中.
✓ 类别中不能有变量,类别中没有放变量的位置.
✓ 如果类中的方法和类别中的方法名称相同,这将造成冲突,类别的方法将完全取代类的方法。
✓ 同一个类的不同类别声明了相同的方法,这将导致不稳定,哪个方法会被调用是不确定的.
重写一个类的方式用继承还是分类, 取决于具体情况。重写时还需要调用父类的方法,分类时完全重写,无法调用原来同名的方法;扩展类的属性(虽然category可通过关联间接添加属性),继承后比较好; 如果仅仅是拓展方法.分类更好.(不需要涉及到原先的代码)
3. Objective-C如何对内存管理的,说说你的看法和解决方法
主要有三方方式:
- ARC自动引用计数:程序编译时Xcode可以自动给你的代码添加内存释放代码
- 手动内存管理
- 内存池:自动内存释放使用@autoreleasepool关键字声明一个代码块,如果一个对象在初始化时调用了autorelase方法,那么当代码块执行完之后,在块中调用过autorelease方法的对象都会自动调用一次release方法。这样一来就起到了自动释放的作用,同时对象的销毁过程也得到了延迟(统一调用release方法)。autorelease方法不会改变对象的引用计数器,只是将这个对象放到自动释放池中,自动释放池实质是当自动释放池销毁后调用对象的release方法,不一定就能销毁对象(例如如果一个对象的引用计数器>1则此时就无法销毁)
原理
ObjC中内存的管理是依赖对象引用计数器来进行的:在ObjC中每个对象内部都有一个与之对应的整数(retainCount),叫“引用计数器”,当一个对象在创建之后它的引用计数器为1,当调用这个对象的alloc、retain、new、copy方法之后引用计数器自动在原来的基础上加1,当调用这个对象的release方法之后它的引用计数器减1,如果一个对象的引用计数器为0,则系统会自动调用这个对象的dealloc方法来销毁这个对象
4. 描述一下iOS SDK中如何实现MVC的开发模式
MVC是模型、视图、控制开发模式。
对于iOS SDK,所有的View都是视图层的,它应该独立于模型层,由视图控制层来控制。
所有的用户数据都是模型层,它应该独立于视图。
所有的ViewController都是控制层,由它负责控制视图,访问模型数据。
5. NSString* testObject = [[NSData alloc] init];testObject 在编译时和运行时分别是什么类型的对象?
编译时是NSString的类型;运行时是NSData类型的对象。
编译时NSString *testObject 等同于 id,testObject 编译器都会在栈空间分配一个 id 类型的数据,id 数据实际上就是一个 struct objc_object 结构体指针。
声明 NSString *testObject 是告诉编译器,testObject是一个指向某个Objective-C对象的指针。因为不管指向的是什么类型的对象,一个指针所占的内存空间都是固定的,所以这里声明成任何类型的对象,最终生成的可执行代码都是没有区别的。这里限定了NSString只不过是告诉编译器,请把testObject当做一个NSString来检查,如果后面调用了非NSString的方法,会产生警告。
接着,你创建了一个NSData对象,然后把这个对象所在的内存地址保存在testObject里。那么运行时,testObject指向的内存空间就是一个NSData对象。你可以把testObject当做一个NSData对象来用
/// objc_object 结构体
Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
///
A pointer to an instance of a class.
typedef struct objc_object *id;
6.属性的特性assign、retain、copy、strong、weak、nonatomic、atomic、readonly、readwrite的区别与介绍
assign:直接进行赋值,用于基础数据类型(例如NSInteger)和C的数据类型(int, float, double, char)另外还有id类型的属性。
retian :表示持有特性,一般用于指针对象,例如数组、字典、视图、控制器等,这些属性需要保存引用计数,传入参数的retaincount会+1。当用retain时,会释放旧的对象,将输入对象的索引计数+1,然后将输入对象的值赋予新对象,实际执行代码如下:
- (void)setName:(NSString *)newName
{
if (name != newName)
{
[name release];
name = [newName retain];
}
}
copy :表示拷贝特性,setter方法将传入对象复制一份;copy主要用于NSString;retain是指针拷贝,copy是内容拷贝然后新的对象开辟新内存,引用计数为1,原来对象计数不变。copy修饰代码块Block属性时有特殊意义,代码块初次生成是在栈上,我们无法确认其释放时机等,而通过copy修饰后就把他拷贝到了堆下,这样我们就可以自由控制其生存。处理流程基先旧对象release,再Copy出新的对象,retainCount为:1。
- (void)setName:(NSString *)newName
{
if (name != newName)
{
[name release];
name = [newName copy];
}
}
strong:强引用,是ARC新引入的对象变量属性,简单讲strong等同retain,只有在你打开ARC时才会被要求使用;但是对于strong来说,它会自己判断是选择retain还是copy,比较方便。
weak:弱引用,是由ARC引入的对象变量的属性,相当于assign,只有在你打开ARC时才会被要求使用,但是weak只能修饰对象即指针类型的属性和id类型的属性,所以在ARC中修饰基本数据类型的属性还是要用assign;weak比assign多了一个功能,就是对象消失后把指针置为nil,避免野指针。
readwrite:属性可读可写,默认属性。如果使用@synthesize关键字,会同时生成getter和setter方法;
readonly:属性只读,如果你指定了只读,即只会生成getter方法,不会生成setter方法;在不希望属性在类外改变时候使用。
nonatomic:非原子性访问,对属性赋值的时候不加锁,不加同步,多线程并发访问会提高性能。默认是atomic;非原子操作,会简单的操作属性的值,这会加快属性存取的速度,但没办法保证在多线程环境下不出错。
atomic:原子操作,默认的,在多线程环境下,为原子操作提供了可靠的属性存取方法,而不用担心并发时会产生问题;多线程写入属性的时候,保证同一时间只有一个线程能够写入操作,但是不能保证读取操作,即还是可以多线程读取属性
关于nonatomic是线程不安全的,当有多个线程同时修改属性name的值的时候,拿到的结果可能不是自己想要的,因为当属性设置nonatomic的话是允许多个线程是可以同时修改name的值。
简单来说atomic是串行,nonatomic是并行,但如果要真正实现防止多线程修改某属性的值的时候,单单设atomic是不够的,还需要更完善的防止手法
7. 深拷贝(MutableCopy)与浅拷贝(Copy)的区别
浅拷贝:浅拷贝并不拷贝对象本身,只是对指向对象的指针进行(内存地址)拷贝,让目标对象指针和源对象指向同一片内存空间。
深拷贝:拷贝对象的具体内容到内存中另一块区域,拷贝结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉。
iOS中并不是所有对象都支持Copy和MutableCopy,遵循NSCopying协议的类可以发送Copy协议,遵循NSMutableCopying协议的类可以发送MutableCopy消息。如果要遵循NSCopying协议,那么必须实现copyWithZone方法。如果要遵循NSMutableCopying协议那么必须实现mutableCopyWithZone方法。
源对象 | 拷贝方法 | 副本对象类型 | 是否产生了新对象(重新分配一块内存) | 拷贝类型 |
---|---|---|---|---|
NSString | Copy | NSString | NO | 浅拷贝(指针拷贝) |
MutableCopy | NSMutableString | YES | 深拷贝(内容拷贝) | |
NSMutableString | Copy | NSString | YES | 深拷贝(内容拷贝) |
MutableCopy | NSMutableString | YES | 深拷贝(内容拷贝) | |
NSArray | Copy | NSArray | NO | 浅拷贝(指针拷贝) |
MutableCopy | NSMutableArray | YES | 深拷贝(内容拷贝) | |
NSMutableArray | Copy | NSArray | YES | 深拷贝(内容拷贝) |
MutableCopy | NSMutableArray | YES | 深拷贝(内容拷贝) | |
总结 | ||||
NS* | Copy | NS* | NO | 浅拷贝(指针拷贝) |
MutableCopy | NSMutable* | YES | 深拷贝(内容拷贝) | |
NSMutable* | Copy | NS* | YES | 深拷贝(内容拷贝) |
MutableCopy | NSMutable* | YES | 深拷贝(内容拷贝) |
系统容器类对象(Array Dictionary),对于其可变类型不管调用Copy还是MutableCopy都是新分配一块内存。虽然重新分配了一块内存,但是对象里面的数据依然是指针复制的,即其元素对象始终是指针复制。
8. static 、extern、const
作用于变量:
-
Static修饰局部变量:使变量成为静态的局部变量,即编译时就为变量分配内存,直到程序退出才释放存储单元(延长生命周期),只会初始化一次。这样,使得该局部变量有记忆功能,可以记忆上次的数据,不过由于仍是局部变量,因而只能在代码块内部使用(作用域不变)。
用static声明外部变量(全局变量?):外部变量指在所有代码块{}之外定义的变量,它缺省为静态变量,编译时分配内存,程序结束时释放内存单元。同时其作用域很广,整个文件都有效甚至别的文件也能引用它。为了限制某些外部变量的作用域,使其只在本文件中有效,而不能被其他文件引用,可以用static 关键字对其作出声明。 -
extern:引用关键字,当某一个全局变量,没有用static修饰时,其作用域为整个项目文件,若是在其他类想引用该变量,则用extern关键字,例如,想引用其他类的全局变量,int age = 10;则在当前类中实现,extern int age;也可以在外部修改该变量,extern int age = 40;,若某个文件中的全局变量不想被外界修改,则用static修饰该变量,则其作用域只限于该文件
-
const:被const修饰的"变量"是只读的(不可修改),常用来定义常量。
static与const的联合使用:定义一个只能在当前文件访问的全局常量
static 类型 const 常量名 = 初始化值
static NSString *const cell =@"ABC";
extern与const的联合使用: 定义一个整个项目都能访问的全局常量
// LMConst.h 文件
#import <UIKIT/UIKit.h>
UIKIT_EXTERN NSString *const LM_Name
// LMConst.m 文件
#import "LMConst.h"
#import <UIKIT/UIKit.h>
NSString *const LM_Name = @"我是一个全局不能改变的字符串常量"
作用于函数:
-
static用于函数定义: 对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。
-
extern作用于函数:如果想要其他文件可以引用本地函数,则要在函数定义时使用关键字extern,表示该函数是外部函数,可供其他文件调用。另外在要引用别的文件中定义的外部函数的文件中,使用extern声明要用的外部函数即可
9. 静态存储区、堆区、栈区...
可编程内存在基本上分为这样的几大部分:静态存储区、堆区和栈区。他们的功能不同,对他们使用方式也就不同。
静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。程序结束后由系统释放。
栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,存储的为非静态的局部变量,例如:函数参数,在函数中声明的对象的指针等,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。
代码区:存放函数体的二进制代码
文字常量区:常量字符串就是放在这里的。程序结束后由系统释放
10. GET和POST对比
- GET在请求URL后面以?的形式跟上发给服务器的参数,多个参数之间用&隔开;POST发给服务器的参数放在请求体body中。
- GET的URL会有长度上的限制,则POST的数据则可以非常大。
- POST比GET安全,GET以明文的方式向服务器发送请求,post是包装到请求体body中后。
使用:
- 如果要传递大量数据,比如文件上传,只能用POST请求;
- GET的安全性比POST要差些,如果包含机密\敏感信息,建议用POST;
- 在做数据查询时,建议用Get方式;而在做数据添加、修改、下载或删除时,建议用Post方式;
http的另外一个请求方式:
HEAD方法通常用在下载文件之前,获取远程服务器的文件信息!相比于GET请求,不会下载文件数据,只获得响应头信息!
一般,使用HEAD方法的目的是提前告诉用户下载文件的信息,由用户确定是否下载文件!所以, HEAD方法,最好发送同步请求!
11.TCP连接、HTTP连接与Socket连接的区别
HTTP协议
超文本传输协议(HTTP,HyperText Transfer Protocol)是客户端与服务端之间的传输数据的协议。通信速度很快;允许传输任意类型的数据。
HTTP请求与响应的内容
http请求:
一个完整的http请求包含'请求行'、'请求头'、'请求体'。
1. 请求行:包含了请求方法,请求资源标识符(URI);http协议版本.,如: "GET /resources/images/ HTTP/1.1"。
. 请求方法就是我们所熟悉的POST、GET、HEAD、PUT等
. URI就是URL中排除掉Host剩下的部分,也就是资源在服务器本地上的路径
. HTTP版本号,目前主流的版本是1.1(1999年开始采用),最新的版本是2.0(2015年5月发布)。
2. 请求头:包含了对客户端的环境描述,客户端请求的主机地址等信息
. Host: 目标服务器的网络地址
. Accept: 客户端所能接收的数据类型,如text/html /
. Content-Type: body中的数据类型,如application/json; charset=UTF-8
. Accept-Language: 客户端的语言环境,如zh-cn
. Accept-Encoding: 客户端支持的数据压缩格式,如gzip
. User-Agent: 客户端的软件环境,我们可以更改该字段为自己客户端的名字,比如QQ music v1.11
. Connection: keep-alive,该字段是从HTTP 1.1才开始有的,用来告诉服务端这是一个持久连接,“请服务端不要在发出响应后立即断开TCP连接”
. Content-Length: body的长度,如果body为空则该字段值为0。该字段一般在POST请求中才会有。
3. 请求体body:客户端发给服务器的具体数据,比如文件/图片等。在GET请求中请求体为空;在普通的POST请求中请求体就是一些表单数据。
http响应:
一个完整的http响应包含'状态行','响应头','实体内容'。
1. 状态行:包含了http协议版本,状态码,状态英文名称,如"HTTP/1.1 200 OK"。
部分错误码
1XX:信息提示。不代表成功或者失败,表示临时响应,比如100表示继续,101表示切换协议
2XX: 成功
3XX: 重定向
4XX:客户端错误,很有可能是客户端发生问题,如亲切可爱的404表示未找到文件,说明你的URI是有问题的,服务器机子上该目录是没有该文件的;414URI太长
5XX: 服务器错误,比如504网关超时
2.响应头:包含了对服务器的描述,对返回数据的描述。
. Content-Encoding: 服务器支持的数据压缩格式,如gzip
. Content-Length: 返回数据的长度
. Content-Type:返回数据的类型,如application/xhtml+xml;charset=utf-8
. Date: 响应的时间,如Mon,15Jun201509:06:46GMT
.Server: 服务器类型
3.响应实体:服务器返回给客户端的具体数据(图片/html/文件...)
TCP连接、HTTP连接与Socket连接的区别:
这三个概念经常被谈到,也是比较容易被混掉的概念。在回顾之前我们先看一下这三者在TCP/IP协议族中的位置关系:
层次关系
HTTP是应用层的协议,更靠近用户端;TCP是传输层的协议;而socket是从传输层上抽象出来的一个抽象层,本质是接口。所以本质上三种还是很好区分的。尽管如此,有时候你可能会懵逼,HTTP连接、TCP连接、socket连接有什么区别?
1. TCP连接与HTTP连接的区别
HTTP是基于TCP的,客户端往服务端发送一个HTTP请求时第一步就是要建立与服务端的TCP连接,也就是先三次握手。从HTTP 1.1开始支持持久连接,也就是一次TCP连接可以发送多次的HTTP请求。
小总结:HTTP基于TCP
2. TCP连接与Socket连接的区别
socket层只是在TCP/UDP传输层上做的一个抽象接口层,因此一个socket连接可以基于连接,也有可能基于UDP。基于TCP协议的socket连接同样需要通过三次握手建立连接,是可靠的;基于UDP协议的socket连接不需要建立连接的过程,不过对方能不能收到都会发送过去,是不可靠的,大多数的即时通讯IM都是后者。
小总结:Socket也基于TCP
3. HTTP连接与Socket连接的区别
-
HTTP是短连接,Socket(基于TCP协议的)是长连接。尽管HTTP1.1开始支持持久连接,但仍无法保证始终连接。而Socket连接一旦建立TCP三次握手,除非一方主动断开,否则连接状态一直保持。
-
HTTP连接服务端无法主动发消息,Socket连接双方请求的发送先后没有限制。这点就比较重要了,因为它将决定二者分别适合应用在什么场景下。HTTP采用“请求-响应”机制,在客户端还没发送消息给服务端前,服务端无法推送消息给客户端。必须满足客户端发送消息在前,服务端回复在后。Socket连接双方类似peer2peer的关系,一方随时可以向另一方喊话。
4. 什么时候该用HTTP,什么时候该用socket?
用HTTP的情况:双方不需要时刻保持连接在线,比如客户端资源的获取、文件上传等。
用Socket的情况:大部分即时通讯应用(QQ、微信、聊天室、苹果APNs等。
12. instancetype和id的区别
- instancetype在类型表示上,跟id一样,可以表示任何对象类型
- instancetype只能用在返回值类型上,不能像id一样用在参数类型上
- 在编译时会检测instancetype的真实类型。
第3点的解释:
// Person.m文件里
+ (id)person{
return [[self alloc] init];
}
// 下面这行代码,用字符串类型的指针指向Person类的对象,编译通过,因为person返回的id类型,任何指针都可以指向它
NSString *str = [Person person];
// 如果用instancetype,编译时,会有警告.也就是说instancetype比id多了检测真实类型的功能,可以提前暴露程序存在的风险
+ (instancetype)person{
return [[self alloc] init];
}
// 会有警告,[Person person]返回的Person类型, 警告信息:把Person类型的数据赋值给字符串类型
NSString *str = [Person person];
13. @synthesize和@dynamic分别有什么作用?
@property有两个对应的词,一个是 @synthesize,一个是 @dynamic。Xcode6以后省略这两个, 默认在 @implementation .m中添加@syntheszie var = _var;
- @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
- @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成,避免编译期间产生警告。
14. 为什么说 Objective-C 没有私有方法和私有变量
在 Objective-C 中,对象调用方法是以发送消息的形式实现的。所有方法的调用最终都会转化为发送消息的形式:id objc_msgSend(id self, SEL op, ...)
详细解释
15. 图层CALayer 和 UIView的区别和联系
每个视图都有一个相关联的root图层(自动创建),UIView对它们的一个封装和管理,图层才是真正在屏幕上显示和做动画的。
- View可以接受并处理事件,而 Layer 不可以。UIView继承自UIResponder,而 CALayer直接继承 NSObject;
- 对于每一个 UIView 都有一个 layer,把这个 layer 且称作RootLayer,而不是 View 的根 Layer的叫做 非 RootLayer。我们对UIView的属性修改时时不会产生默认动画,而对单独 layer属性直接修改会,这个默认动画的时间缺省值是0.25s.
- 每个 UIView 内部都有一个 CALayer 在背后提供内容的绘制和显示,并且 UIView 的尺寸样式都由内部的 Layer 所提供。两者都有树状层级结构,layer 内部有 SubLayers,View 内部有 SubViews.但是 Layer 比 View 多了个AnchorPoint
16. iOS中的静态库和framework区别
静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。
动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。
静态库形式:.a和.framework
动态库形式:.dylib和.framework;系统的.framework是动态库,我们自己建立的.framework是静态库。
.a是一个纯二进制文件,.framework中除了有二进制文件之外还有资源文件。
.a文件不能直接使用,至少要有.h文件配合,.framework文件可以直接使用。
.a + .h + sourceFile = .framework。
为什么要使用静态库?
方便共享代码,便于合理使用。
实现iOS程序的模块化。可以把固定的业务模块化成静态库。
和别人分享你的代码库,但不想让别人看到你代码的实现。
开发第三方sdk的需要。
制作和使用
17. runtime 如何实现 weak 变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
18. 两个app之间互相调用、传值
URL Schemes是苹果给出的用来跳转到系统应用或者跳转到别人的应用的一种机制,同时还可以在应用之间传数据
19. __weak与__block修饰符到底有什么区别
- __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
- __weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
- __block对象可以在block中被重新赋值,__weak不可以。
20. 分别写一个setter方法用于完成@property (nonatomic,retain)NSString *name 和 @property (nonatomic,copy) NSString *name
retain属性的setter方法是保留新值并释放旧值,然后更新实例变量,令其指向新值。顺序很重要。假如还未保留新值就先把旧值释放了,而且两个值又指向同一个对象,先执行的release操作就可能导致系统将此对象永久回收。
-(void)setName:(NSString *)name
{
[name retain];
[_name release];
_name = name;
}
-(void)setName:(NSString *)name
{
id t = [str copy];
[_name release];
_name = t;
}
21. id声明的对象有什么特性
没有 * 号;id类型的对象可以是任意类型的OC对象,与C中的void*万能指针相似, 具有运行时的特点,在程序运行时才确定对象的类型
22. 原子(atomic)跟非原子(non-atomic)属性有什么区别?
1). atomic提供多线程安全。是防止在写未完成的时候被另外一个线程读取,造成数据错误,为setter方法加锁,线程安全的,效率相对低。
2). non-atomic:在自己管理内存的环境中,解析的访问器保留并自动释放返回的值,如果指定了nonatomic,那么访问器只是简单地返回这个值。不会为setter方法加锁,线程不安全的, 效率高
atomic的作用只是给getter和setter加了个锁,atomic只能保证代码进入getter或者setter函数内部时是安全的,一旦出了getter和setter,多线程安全只能靠程序员自己保障了。所以atomic属性和使用property的多线程安全并没什么直接的联系
23. 内存管理的几条原则是什么?按照默认法则.哪些关键字生成的对象需要手动释放?在和property结合的时候怎样有效的避免内存泄露?
原则:谁申请,谁释放
内存管理主要要避免“过早释放”和“内存泄漏”,对于“过早释放”需要注意@property设置特性时,一定要用对特性关键字,对于“内存泄漏”,一定要申请了要负责释放,要细心。
关键字alloc 或new 生成的对象需要手动释放;
24. 类别(Category)和扩展(Extension)的区别
分类(Category)
通过Category即使在没有源代码(如某个系统类,iOS不开源的)的情况下,也可以为这个类添加新的方法声明。而新方法的实现可以在另外的文件中。
类名+分类名.h
类名+分类名.m
1. Category只能用于添加方法,不能用于添加成员属性。可运用runtime.h中objc_getAssociatedObject / objc_setAssociatedObject来访问和生成关联对象。通过这种方法来模拟生成属性
2. 分类中方法的优先级比原来类中的方法高,也就是说,在分类中重写了原来类中的方法,那么分类中的方法会覆盖原来类中的方法
3. 通过category 增加到类中的方法会被这个类的所有子类继承,就和此类的其它方法一样。
4. 不提供实现时,编译器不会报错,运行调用时出错
扩展(Extension)
只有.h文件,方法要在源文件的.m文件中实现。
1. Extension可添加方法,也可添加成员属性。
2. Extension增加的方法如果与类的方法同名,则会冲突报错。
4. Extension在@implementation中不提供实现,编译会报错;
25. KVC和KVO的使用及原理
KVC
Key-Value Coding,即键值编码。它是一种不通过存取访问器方法,而通过属性名称字符串间接访问属性的机制。可以访问修改私有成员的值
- (id)valueForKey:(NSString *)key;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
KVC的原理:
KVC再某种程度上提供了访问器的替代方案。不过访问器方法是一个很好的东西,以至于只要是有可能,KVC也尽量在访问器方法的帮助下工作。为了设置或者返回对象属性,KVC按顺序使用如下技术:
1.检查是否存在-< property >、-is< property >(只针对布尔值有效)或者-get< property >的访问器方法,如果有可能,就是用这些方法返回值;
检查是否存在名为-set< property >:的方法,并使用它做设置值。对于-get< property >和-set< property >:方法,将大写Key字符串的第一个字母,并与Cocoa的方法命名保持一致;
2.如果上述方法不可用,则检查名为-_< property >、-_is< property >(只针对布尔值有效)、-_get< property >和-_set< property >:方法;
3.如果没有找到访问器方法,可以尝试直接访问实例变量。实例变量可以是名为:< property >或 _< property >;
4.如果仍为找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。
KVO
Key-Value Obersver,即键值观察。对目标对象的某属性添加观察,当该属性发生变化时,会自动的通知观察者.
// 1 添加观察:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
// 2 实现下面方法来接收通知
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context
{
// 最好判断目标对象object和属性路径keyPath
if(object == _student && [keyPath isEqualToString:@"stuName"])
{
// 处理...
}
else
{
// 父类也有可能使用了KVO哦,所以在else里,对现有条件外的情况交给父类去处理
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
// 3 最后要移除观察者:
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
KVO的原理:
当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中被观察属性的 setter 方法,在setter方法里使其具有通知机制。因此,要想KVO生效,必须直接或间接的通过setter方法访问属性(KVC的setValue就是间接方式)。直接访问成员变量KVO是不生效的。
同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。
apple用什么方式实现对一个对象的KVO
重写的setter方法里到底干了什么,而使其就有了通知机制呢?其实只是在setter方法里,给属性赋值的前后分别调用了两个方法
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
而- (void)didChangeValueForKey:(NSString *)key;会调用
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
更多kvc、kvo使用请看KVC/KVO原理详解及编程指南
若一个类有实例变量 NSString *_foo ,调用setValue:forKey:时,可以以foo还是 _foo 作为key?
26. 代理
代理是一种通用的设计模式,主要由三部分组成:
协议:用来指定代理双方可以做什么,必须做什么。
代理:根据协议,完成委托方需要实现的功能(方法)。
委托:根据协议,指定代理去完成什么功能。
协议只能定义公用的一套接口,类似于一个约束代理双方的作用。但不能提供具体的实现方法,实现方法需要代理对象(可以理解为接受协议遵守协议的代理人)去实现。协议可以继承其他协议,也可以继承多个协议,协议有两个修饰符@optional和@required
代理原理:
委托方:有@property (nonatomic, weak) id<Delegate> delegate 代理属性的对象。
代理对象:给代理属性赋值,实现遵守并实现协议的对象。
委托方的代理属性本质上就是代理对象自身,设置委托代理就是代理属性指针指向代理对象,相当于代理对象只是在委托方中调用自己的方法,如果方法没有实现就会导致崩溃。从崩溃的信息上来看,就可以看出来是代理方没有实现协议中的方法导致的崩溃。
而协议只是一种语法,是声明委托方中的代理属性可以调用协议中声明的方法,而协议中方法的实现还是有代理方完成,而协议方和委托方都不知道代理方有没有完成,也不需要知道怎么完成。
注意:代理属性要使用weak,防止循环引用,一个对象被释放后,weak会自动将指针指向nil,而assign则不会。在iOS中,向nil发送消息时不会导致崩溃的,所以assign就会导致野指针的错误unrecognized selector sent to instance
代理作用:
1.传值:
2.传递事件:
所谓传事件就是a类发生了什么事,把这件事告诉关注自己的人,也就是委托的对象,由委托的对象去考虑发生这个事件后应该做出什么反映。(这个经常见,例如在异步请求中,界面事件触发数据层改变等等)
3.利用委托赋值:
这种方法感觉是为了不暴露自己的属性就可以给自己复值,而且这样更方便了类的管理,只有在你想要让别人给你赋值的时候才调用,这样的赋值更可控一些。(例如tableView中的委托(dateSource)中常见)
代理和block的选择
性能上来说,block的性能消耗要大于delegate,因为block会涉及到栈区向堆区拷贝等操作。而代理只是定义了一个方法列表,在遵守协议对象的objc_protocol_list中添加一个节点,在运行时向遵守协议的对象发送消息即可。如何选择要看情景,和你自己的习惯了.
两个情况可以考虑 delegate:
1.有多个相关方法。假如每个方法都设置一个 block, 这样会更麻烦。而 delegate 让多个方法分成一组,只需要设置一次,就可以多次回调。当多于 3 个方法时就应该优先采用 delegate。比如一个网络类,假如只有成功和失败两种情况,每个方法可以设计成单独 block。但假如存在多个方法,比如有成功、失败、缓存、https 验证,网络进度等等,这种情况下,delegate 就要比 block 要好
2. 为了避免循环引用,也可以使用 delegate。使用 block 时稍微不注意就形成循环引用,导致对象释放不了。这种循环引用,一旦出现就比较难检查出来。而 delegate 的方法是分离开的,并不会引用上下文,因此会更安全些。
27. oc是动态运行时语言是什么意思?
动态语言,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。所谓的动态类型语言,主要是将数据类型的确定由编译时,推迟到了运行时。oc的运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。
OC的动态特性表现为了三个方面:
1. 动态类型:
动态类型能使程序直到执行时才确定对象的所属类, 其具体引用的对象在运行时才能确定。
2. 动态绑定:
动态绑定能使程序直到运行时才确定调用对象的实际方法,传什么参数。
3. 动态加载:
动态加载让程序在运行时添加代码模块以及其他资源。用户可以根据需要加载一些可执行代码和资源,而不是在启动时就加载所有组件。(例如根据不同机型加载@2x @3x图片资源)。
更多详细
多态
多态一般都要跟继承结合起来说,其本质是子类通过覆盖或重载,父类的方法,来使得对同一类对象同一方法的调用产生不同的结果。在父类指针指向不同的对象的时候,通过父类指针调用被重写的方法,会执行该指针所指向的那个对象的方法。
例如:假设生物类(Life)都用有一个相同的方法-eat;那人类属于生物,猪也属于生物,都继承了life后,实现各自的eat,用户父类life分别指向人类对象、猪类对象,但是life调用eat时执行了各自的eat方法。
原理:
动态绑定可以做到在程序直到执行时才确定对象的真实类型,进而确定需要调用那个对象方法。
详细原理请看isa指针和类对象
28. 单例模式
一个单例类,在整个程序中只有一个实例,并且提供一个类方法供全局调用,在编译时初始化这个类,然后一直保存在内存中,到程序(APP)退出时由系统自动释放这部分内存。苹果封装成单例常用的有UIApplication, NSUserDefaults, NSNotificationCenter, NSFIleManager
等等.
优点缺点:
优点: 因为单例模式会使类只有一个实例,所以方便使用,并且节省内存资源的分配.
缺点: 1.单例创建的内存只有在程序结束时才会被释放,单例闲置时消耗了系统内存资源. 2.由于单例不能被继承(因为返回的是同一个实例),所以扩展性很不好
写法:
static Singleton * _instance = nil;
+ (instancetype) sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance= [[self alloc] init];
});
return _instance;
}
上面是经常用的写法,但是通过[[Class alloc] init]
仍会创建新对象。alloc会执行allocWithZone
,所以如果想只分配一次内存就要重写此方法,同时为了严谨,防止copy出现以上问题,还要重写copyWithZone、mutableCopyWithZone
#import "Singleton.h"
static Singleton * _instance = nil;
@implementation Singleton
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone ];
});
return _instance;
}
+ (instancetype)sharedInstance{
if (_instance == nil) {
_instance = [[super alloc]init];
}
return _instance;
}
- (id)copyWithZone:(NSZone *)zone{
return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone{
return _instance;
}
@end
29. iOS中是否支持垃圾回收机制
iOS开发只支持手动内存管理和ARC,Mac开发支持GC垃圾回收机制, 18.8之后弃用了GC
30. 什么是谓词?
NSPredicate类是一种过滤器,经常用来定义的逻辑条件约束来过滤数组。
NSPredicate 使用详解, NSPredicate和正则表达式
31. Block
Block是C语言的扩充,是一个自动包含局部变量的匿名函数.
Block的使用与实现原理
block的用法:
- 回调传值。
- block作为返回值,链式编程思想
32. 用预处理指令#define声明一个常数,用以表明1年中有多少秒
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
#define 语法的基本知识(例如:不能以分号结束,括号的使用,等等).
如果你在你的表达式中用到UL(表示无符号长整型), 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
33. iOS沙盒机制
iOS App都有自己的存储空间,这个存储空间就叫沙盒,每个沙盒之间是相互独立的,不能相互访问,app可以在自己的沙河文件中读取文件。
沙盒中的结构:
沙盒的目录结构- Documents
此目录一般保存应用程序本身产生的需要长久保存的数据(eg:游戏进度归档,应用程序个人设置等),通过iTunes,ICloud备份时,会备份这个目录下的数据,此目录下保存相对重要的数据。注意:此目录下不要保存网络上下载的文件,否则app无法上架 - Library/Caches
此目录一般存储的是缓存文件,这些数据一般存储体积比较大,又不是十分重要,比如网络请求数据,从网络上下载的文件或者数据(eg:音乐缓存、图片缓存等)。这些数据需要用户负责删除。iTunes同步设备时不会备份该目录。系统发出内存警告是会清理 - Library/Preferences
此目录保存应用程序的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes,iCloud同步设备时会备份该目录。注意:该文件夹下不能直接创建偏好设置文件,而是应该使用NSUserDefaults类来取得和设置应用程序的偏好。
- Library/Caches
- tmp
用于存放临时文件,应用程序退出,系统磁盘空间不够,手机重启,都会自动清除该目录的数据。无需程序员手动清楚该目录中的数据。iTunes、iCloud不会备份此目录下的数据。
获取目录路径:
// 获取沙盒目录
NSString *homeDir=NSHomeDirectory();
// 获取Documents目录路径
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
// 获取Library的目录路径
NSString *libDir = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
// 获取Preferences目录路径
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *preferences = [[paths firstObject] stringByAppendingString:@"/Preferences"];
// 获取Caches目录路径
NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
// 获取tmp目录路径
NSString *tmpDir = NSTemporaryDirectory();
34. viewDidLoad和viewDidUnload何时调用
loadView、viewDidLoad及viewDidUnload的关系
1.第一次访问UIViewController的view时,view为nil,然后就会调用loadView方法创建view
2.view创建完毕后会调用viewDidLoad方法进行界面元素的初始化
3.当内存警告时,系统可能会释放UIViewController的view,将view赋值为nil,并且调用viewDidUnload方法
4.当再次访问UIViewController的view时,view已经在3中被赋值为nil,所以又会调用loadView方法重新创建view
5.view被重新创建完毕后,还是会调用viewDidLoad方法进行界面元素的初始化
35. 有哪几种手势通知方法、方法名是什么?
手指按下事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
手指移动事件
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
手指抬起事件
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
意外中断事件(如电话打扰
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
3D触摸事件
- (void)touchesEstimatedPropertiesUpdated:(NSSet * _Nonnull)touches
36. tableView的重用机制
Table中Cell的重用reuse机制分析
UITableView的复用机制及优化
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = [NSString stringWithFormat:@"Cell"];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
//...
return cell;
}
UITableView头文件,有visibleCells和reusableTableCells,visibleCells内保存当前显示的cells,reusableTableCells保存可重用的cells。TableView显示之初,reusableTableCells为空,那么tableView dequeueReusableCellWithIdentifier: 返回nil。
开始的cell都是通过[[UITableViewCell alloc] initWithStyle: reuseIdentifier: ]来创建
37. BAD_ACCESS内存错误
BAD_ACCESS 报错属于内存访问错误,会导致程序崩溃,错误的原因是访问了野指针。包括向野指针发送消息,读写野指针本来指向的对象的成员变量等等。
调试方法:开启僵尸对象诊断模式
BAD_ACCESS内存错误调试
38. 以下代码运行结果
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"Hello");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"World");
});
NSLog(@" ! ");
}
只会输出Hello
, 发生主线程锁死。
Submits a block to a dispatch queue like dispatch_async(), however dispatch_sync() will not return until the block has finished.
Calls to dispatch_sync() targeting the current queue will result in dead-lock.
dispatch_sync()是同步,当block完成后才会返回。
如果dispatch_sync()的目标queue为当前queue,会发生死锁(并行queue并不会)。
具体原因请看dispatch_sync死锁问题研究
39. Runloop
iOS RunLoop详解
RunLoop的应用场景
RunLoop入门
Runloop和线程的关系:
- runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里。
- runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。
- 对于主线程来说,runloop在程序一启动就默认创建好了. iOS的应用程序的main()函数里有UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象,这就解释了:为什么我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。
- 对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建(第一次获取时被创建,在线程结束时被销毁),所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。
40. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
不能向编译后得到的类中增加实例变量;能向运行时创建的类中添加实例变量;
原因:
因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表 和 instance_size 实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量;
运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。
41. @property @synthesize @dynamic 相关
1. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的?
@property 的本质:
@property = ivar + getter + setter;
解释如下:
“属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)
“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为 Objective-C 2.0 的一部分。 而在正规的 Objective-C 编码风格中,存取方法有着严格的命名规范。 正因为有了这种严格的命名规范,所以 Objective-C 这门语言才能根据名称自动创建出存取方法。其实也可以把属性当做一种关键字,其表示: 编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量, 所以你也可以这么说@property = getter + setter;
例如下面这个类:
@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
上述代码写出来的类与下面这种写法等效:
@interface Person : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end
ivar、getter、setter 是如何生成并添加到这个类中的?
“自动合成”( autosynthesis)
完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为 _firstName 与 _lastName。也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字.
@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
2. @protocol 和 category 中如何使用 @property?
- protocol 中使用 property 只会生成 setter 和 getter 方法声明, 我们使用属性的目的,是希望遵守我协议的对象能实现该属性
- category 使用 @property 也是只会生成 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数(关联):
objc_setAssociatedObject
、objc_getAssociatedObject
3. @property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?
属性可以拥有的特质分为四类:
- 原子性: nonatomic 特质
在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备 nonatomic 特质,则不使用自旋锁。请注意,尽管没有名为“atomic”的特质(如果某属性不具备 nonatomic 特质,那它就是“原子的” ( atomic) ),但是仍然可以在属性特质中写明这一点,编译器不会报错。若是自己定义存取方法,那么就应该遵从与属性特质相符的原子性。 - 读/写权限:readwrite(读写)、readonly (只读)
- 内存管理语义:assign、strong、 weak、unsafe_unretained、copy
- 方法名:getter=<name> 、setter=<name>
getter=<name>的样式:
@property (nonatomic, getter=isOn) BOOL on;
不常用的:nonnull,null_resettable,nullable
ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?
1.对应基本数据类型默认关键字是: atomic,readwrite,assign
- 对于普通的 Objective-C 对象: atomic,readwrite,strong
4. @synthesize和@dynamic分别有什么作用?
- @property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;
- @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
- @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
5. @synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?
在回答之前先说明下一个概念:实例变量 = 成员变量 = ivar
如果使用了属性的话,那么编译器就会自动编写访问属性所需的方法,此过程叫做“自动合成”( auto synthesis)。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法” (synthesized method)的源代码。除了生成方法代码之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。
@interface CYLPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
在上例中,会生成两个实例变量,其名称分别为 _firstName 与 _lastName。也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字:
@implementation CYLPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
上述语法会将生成的实例变量命名为 _myFirstName 与 _myLastName ,而不再使用默认的名字。一般情况下无须修改默认的实例变量名,但是如果你不喜欢以下划线来命名实例变量,那么可以用这个办法将其改为自己想要的名字。笔者还是推荐使用默认的命名方案,因为如果所有人都坚持这套方案,那么写出来的代码大家都能看得懂。
总结下 @synthesize 合成实例变量的规则,有以下几点:
- 如果指定了成员变量的名称,会生成一个指定的名称的成员变量
- 如果这个成员已经存在了就不再生成了.
- 如果是 @synthesize foo; 还会生成一个名称为foo的成员变量,也就是说:如果没有指定成员变量的名称会自动生成一个属性同名的成员变量
- 如果是 @synthesize foo = _foo; 就不会生成成员变量了.
假如 property 名为 foo,存在一个名为 _foo 的实例变量,那么还会自动合成新变量么? 不会。如下图:
image
6. 在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?
回答这个问题前,我们要搞清楚一个问题,什么情况下不会autosynthesis(自动合成):
- 同时重写了 setter 和 getter 时
- 重写了只读属性的 getter 时
- 使用了 @dynamic 时
- 在 @protocol 中定义的所有属性
- 在 category 中定义的所有属性
- 重载的属性
当你在子类中重载了父类中的属性,你必须 使用 @synthesize 来手动合成ivar。
除了后三条,对其他几个我们可以总结出一个规律:当你想手动管理 @property 的所有内容时,你就会尝试通过实现 @property 的所有“存取方法”(the accessor methods)或者使用 @dynamic 来达到这个目的,这时编译器就会认为你打算手动管理 @property,于是编译器就禁用了 autosynthesis(自动合成)。
因为有了 autosynthesis(自动合成),大部分开发者已经习惯不去手动定义ivar,而是依赖于 autosynthesis(自动合成),但是一旦你需要使用ivar,而 autosynthesis(自动合成)又失效了,如果不去手动定义ivar,那么你就得借助 @synthesize 来手动合成 ivar。
举例说明:同时重写了 setter 和 getter 时应用场景:
//
// .m文件
// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong
// 打开第14行和第17行中任意一行,就可编译成功
@import Foundation;
@interface CYLObject : NSObject
@property (nonatomic, copy) NSString *title;
@end
@implementation CYLObject {
// NSString *_title;
}
//@synthesize title = _title;
- (instancetype)init
{
self = [super init];
if (self) {
_title = @"微博@iOS程序犭袁";
}
return self;
}
- (NSString *)title {
return _title;
}
- (void)setTitle:(NSString *)title {
_title = [title copy];
}
@end
结果编译器报错:
image
当你同时重写了 setter 和 getter 时,系统就不会生成 ivar(实例变量/成员变量)。这时候有两种选择:
要么如第14行:手动创建 ivar
要么如第17行:使用@synthesize foo = _foo; ,关联 @property 与 ivar。
42. 用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。copy 修饰的所属的设置方法并不保留新值,而是将其“拷贝” (copy)。当属性类型为 NSString 时,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。
如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象(NSMutableString、NSMutableArray、NSMutableDictionary),如果这个可变对象在外部被修改了,那么会影响该属性
例如:
@interface Person : NSObject
@property (nonatomic,strong) NSString *name;
@end
然后如下操作:
NSMutableString *str = [[NSMutableStringalloc] initWithString:@"aa"];
Person *p1 = [[Personalloc] init];
p1.name = str;
[str appendString:@"bb"];
NSLog(@"%@ %@", str, p1.name);
NSLog(@"%p %p", str, p1.name);
打印显示如下:
2015-09-15 17:19:08.930 DeepCopy[3837:1317274] aabb aabb
2015-09-15 17:19:08.931 DeepCopy[3837:1317274] 0x10020a690 0x10020a690
@property (copy) NSMutableArray *array; 这个写法会出什么问题?
1、添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为 copy 就是复制一个不可变 NSArray 的对象;
2、使用了 atomic 属性会严重影响性能 ;
43. 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
如何重写带 copy 关键字的 setter NSString?
- (void)setName:(NSString *)name {
if (_name != name) {
//[_name release]; //MRC
_name = [name copy];
}
}
// 可以不要if判断, 因为if不成立时是,把一个“ @property他当前的值 ”赋给了他自己WTF。
如何让自己的类用 copy 修饰符?
若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。
具体步骤:
- 需声明该类遵从 NSCopying 协议
- 实现 NSCopying 协议。该协议只有一个方法:
- (id)copyWithZone:(NSZone *)zone;
注意:一提到让自己的类用 copy 修饰符,我们总是想覆写copy方法,其实真正需要实现的却是 “copyWithZone” 方法。
例如:
// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 修改完的代码
typedef NS_ENUM(NSInteger, CYLSex) {
CYLSexMan,
CYLSexWoman
};
@interface CYLUser : NSObject<NSCopying>
@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readonly, assign) CYLSex sex;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
@end
然后实现协议中规定的方法:
- (id)copyWithZone:(NSZone *)zone {
CYLUser *copy = [[[self class] allocWithZone:zone] initWithName:_name
age:_age
sex:_sex];
return copy;
}
44. 怎么用 copy 关键字?
- NSString、NSArray、NSDictionary 等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;
- block 也经常使用 copy 关键字
45. 目标-动作机制(target - Action)
目标是动作消息的接受者,动作是目标为了响应动作而实现的方法。该模式减少模块之间代码的耦合性,以及增强模块内代码之间的内聚性
UIBarButtonItem *saveBtn = [[UIBarButtonItem alloc] initWithTitle:@"Save" style:UIBarButtonItemStyleDone target:self action:@selector(saveRecipe:)];
一个按钮控件的click事件的实现. 在这里, 按钮被按下以后会调用 target(也就是self)上的saveRecipe方法. 按照objC的习惯来说是当click事件发生以后,会给self对象发送一个message导致self对象上的saveRecipe方法的调用.
- Target - Action 主要用来在MVC设计模式中,V和C之间的进行通信。
46. ANPs推送
47. 多线程
GCD
队列——任务管理方式
要管理多个任务时,线程开发给我们带来了一定的技术难度,或者说不方便性,GCD给出了我们统一管理任务的方式,那就是队列。不管是串行还是并行,队列都是按照FIFO的原则依次触发任务.
队列:
- 串行队列:所有任务会在一条线程中执行(有可能是当前线程也有可能是新开辟的线程),并且一个任务执行完毕后,才开始执行下一个任务。(等待完成)
- 并行队列:可以开启多条线程并行执行任务(但不一定会开启新的线程),并且当一个任务放到指定线程开始执行时,下一个任务就可以开始执行了。(等待发生)
GCD两个特殊队列:
- 主队列:系统为我们创建好的一个串行队列,牛逼之处在于它管理必须在主线程中执行的任务,属于有劳保的。
- 全局队列:系统为我们创建好的一个并行队列,使用起来与我们自己创建的并行队列无本质差别。
任务执行方式
说完队列,相应的,任务除了管理,还得执行,并且在GCD中并不能直接开辟线程执行任务,所以在任务加入队列之后,GCD给出了两种执行方式——同步执行(sync)和异步执行(async)。
- 同步执行:在当前线程执行任务,不会开辟新的线程。必须等到Block函数执行完毕后,dispatch函数才会返回。
- 异步执行:可以在新的线程中执行任务,但不一定会开辟新的线程。dispatch函数会立即返回, 然后Block在后台异步执行。
队列与执行方式的组合
同步执行会在当前线程执行任务,不具备开辟线程的能力。并且,同步执行必须等到Block函数执行完毕,dispatch函数才会返回,从而阻塞同一串行队列中外部方法的执行。
异步执行dispatch函数会直接返回,从而不会阻塞当前外部任务的执行。同时,只有异步执行才有开辟新线程的必要,但是异步执行不一定会开辟新线程。
组合方式:
同步+串行:未开辟新线程,串行执行任务;
同步+并行:未开辟新线程,串行执行任务;
异步+串行:新开辟一条线程(主队除外),串行执行任务;
异步+并行:开辟多条新线程,并行执行任务;
在主线程中同步使用主队列执行任务,会造成死锁。
GCD其他函数用法:
- dispatch_after:能让我们添加进队列的任务延时执行,该函数并不是在指定时间后执行处理,而只是在指定时间追加处理到dispatch_queue
//该方法的第一个参数是time,第二个参数是dispatch_queue,第三个参数是要执行的block。//在主线程中延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
- dispatch_once:保证函数在整个生命周期内只会执行一次
+(Manager *)sharedInstance
{
static Manager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[Manager alloc] init];
});
return manager;
}
-
dispatch_group_async 、dispatch_group_notify、dispatch_group_wait 和dispatch_group_enter与dispatch_group_leave
用队列组dispatch_group来进行线程同步,当加入到队列组中的所有任务执行完成之后,会调用dispatch_group_notify函数通知任务全部完成。使用dispatch_group来进行线程同步 -
dispatch_barrier_async: 在一个并发队列中创建一个同步点。使用此方法创建的任务,会查找当前队列中有没有其他任务要执行,如果有,则等待已有任务执行完毕后再执行,同时,在此任务之后进入队列的任务,需要等待此任务执行完成后,才能执行。
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任务1");
});
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任务2");
});
dispatch_barrier_async(self.concurrentQueue, ^{
NSLog(@"任务barrier");
});
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任务3");
});
dispatch_async(self.concurrentQueue, ^{
NSLog(@"任务4");
});
// 打印结果
ThreadDemo[1833:678739] 任务2
ThreadDemo[1833:678740] 任务1
ThreadDemo[1833:678740] 任务barrier
ThreadDemo[1833:678740] 任务3
ThreadDemo[1833:678739] 任务4
- dispatch_apply:该函数用于重复执行某个任务,如果任务队列是并行队列,重复执行的任务会并发执行,如果任务队列为串行队列,则任务会顺序执行,需要注意的是,该函数为同步函数,要防止线程阻塞和死锁。dispatch apply能够避免一些线程爆炸的情况发生(创建很多线程)。GCD学习之dispatch_apply
// 并行队列 重复执行5次
dispatch_apply(5, self.concurrentQueue, ^(size_t i) {
NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
});
//死锁
dispatch_apply(5, dispatch_get_main_queue(), ^(size_t i) {
NSLog(@"第%@次_%@",@(i),[NSThread currentThread]);
});
- dispatch_semaphore_create 、 dispatch_semaphore_signal 、 dispatch_semaphore_wait
信号量控制最大并发数,提高并行效率的同时,也防止太多线程的开辟对CPU造成负面的效率负担。
dispatch_semaphore_create创建信号量,初始值不能小于0;
dispatch_semaphore_wait等待降低信号量,也就是信号量-1;
dispatch_semaphore_signal提高信号量,也就是信号量+1;
dispatch_semaphore_wait和dispatch_semaphore_signal通常配对使用。
dispatch_semaphore
NSOperation、NSOperationQueue
NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也更高。
NSOperation:
其实就是任务,在线程中执行的那段代码,但是这个类不能直接使用,我们要用他的两个子类,NSBlockOperation和NSInvocationOperation。NSOperation 单独使用时系统同步执行操作,配合 NSOperationQueue 我们能更好的实现异步执行。
NSOperationQueue:
这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序由操作之间相对的优先级决定(优先级是操作对象自身的属性)。操作队列通过设置 最大并发操作数(maxConcurrentOperationCount) 来控制并发、串行。
GCD和NSOperation的区别:
-
GCD面向C,NSOperation是GCD的封装,面向对象:
GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。
在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构; 而Operation作为一个对象,为我们提供了更多的选择; -
NSOperation可以取消任务,GCD不行:
在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,
已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的, 但需要许多复杂的代码); -
NSOperation可设置依赖关系,而且能设置不同队列之间的任务依赖:
NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation, 这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行; -
NSOperation用KVO监听完成,取消,开始,挂起等状态,比GCD更能掌控后台操作:
我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消, 这样子能比GCD更加有效地掌控我们执行的后台任务; -
NSOperation可以设置任务之间的优先级,GCD只能设置不同队列之间的优先级,如果要做,需要大量代码:
在NSOperation中,我们能够设置NSOperation的priority优先级, 能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级, 如果要区分block任务的优先级,也需要大量的复杂代码;
48. Socket相关
从这张图片中,我们可以很直观的看到Socket的位置。我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,Socket是应用层与TCP/IP协议族通信的中间软件抽象层,是它的一组接口。
基于TCP的Socket:
基于UDP的Socket:
49. iOS中的内存分配
内存分配.jpg-
代码区:
用来存放函数的二进制代码,在运行时要防止被非法修改,只允许读取不允许操作 -
常量区:
存储常量 -
静态区:
1、数据区:存放程序静态(static)分配的变量和全局变量
2、BSS:包含了程序中未初始化全局变量 -
堆区(heap):
堆是由程序员分配和释放,用于存放进程运行中被动态分配的内存段,它大小并不固定,可动态扩张或缩减。当进程调用alloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用realse释放内存时,被释放的内存从堆中被剔除(堆被缩减),因为我们现在iOS基本都使用ARC来管理对象,所以不用我们程序员来管理,但是我们要知道这个对象存储的位置. -
栈区(stack):
栈是由编译器自动分配并释放,用户存放程序临时创建的局部变量,存放函数的参数值,局部变量等。也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味这在数据段中存放变量)。除此以外在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也回被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上将我们可以把栈看成一个临时数据寄存、交换的内存区。
以上中堆和静态区以及常量区都是连续的,栈和代码区都是独立存放的,栈是向低地址扩展的数据结构,是一块连续的内存的区域。堆是向高地址扩展的数据结构,是不连续的内存区域。堆和栈不会碰到一起,之间间隔很大,绝少有机会能碰到一起,况且堆是链表方式存储!
#import "ViewController.h"
int age = 24; // 数据区 (全局变量初始化)
NSString *name; // BSS区 (全局变量未初始化)
static NSString *sName = @"Dely"; // 数据区(静态变量)
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
int tmpAge; // 栈 (局部变量)
NSString *tmpName = @"XiaoMin"; // temName在栈上 (局部变量), @"XiaoMin" 常量区
NSString *number = @"123456"; // number在栈上,123456在常量区。
NSMutableArray *array = [NSMutableArray arrayWithCapacity:1]; // 分配而来的8字节的区域就在堆中(相当于alloc分配内存),array在栈中,指向堆区的地址
NSInteger total = [self getTotalNumber:1 number2:1];
}
- (NSInteger)getTotalNumber:(NSInteger)number1 number2:(NSInteger)number2{
int num = number1 + number2; //number1和number2 栈区
return num; // 返回值num也暂存在栈中
}
@end
50. 解析XML
XML结构.png进行XML解析有两种方式:SAX 和 DOM
-
SAX:Simple API for XML .基于事件驱动的解析方式,逐行解析数据。(采用协议回调机制)
SAX 解析XML,是基于事件通知的模式,一边读取XML文档一边处理,不必等整个文档加载完之后才采取操作,SAX解析器会检测整个XML树形结构,你的代码 会控制它在哪里停止,使用哪些数据之类的事情。就是说,SAX可控制性强,占用内存小,适用于提取部分数据,适合解析大型XML,解析速度快。当在读取解析过程中遇到需要处理的对象,会发 出通知对其进行处理,如果XML格式在某一处出现错误,前面的数据会被提取出来,错误后面数据的就显示不出来。NSXMLParse类是iOS自带的XML解析类。采用SAX方式解析数据,解析过程由NSXMLParserDelegate协议方法回调。 - DOM:Document Object Model (文档对象模型)。解析时需要将XML文件整体读入,并且将XML结构化成树状,使用时再通过树状结构读取相关数据,查找特定节点,然后对节点进行读或 写。他的主要优势是实现简单,读写平衡;缺点是比较占内存,因为他要把整个xml文档都读入内存,解析速度慢,适合解析小型文档。当文件内容出现错误时,在 输入框内会标记出错误的位置,GDataXMLNode是Google提供的开元XML解析类。
51. Core Data相关
CoreData是苹果提供的一套数据保存框架,不可以跨平台使用,Sqlite可以跨平台使用.
- CoreData:
- 可视化,不用再写 SQL 语句,大量简化代码.
- 与iOS紧密结合, 只能用于开发iOS.
- 存储内容, 以对象的形式存储, 符合面向对象的思想.
- SQLite:
- 是一个轻量级数据库而且功能强大的关系型数据库, 很容易被嵌入到应用当中, 可移植性高, 可以在多个平台使用
- SQLite 基于C接口, 使用SQL语句, 代码繁琐
- OC中不是可视化的.
52. 程序调试
LLDB调试: LLDB调试器和断点调试、LLDB调试技巧
Instruments工具:Instruments 学习系列
53. SDWebImage源码分析
sdwebimage源码分析
SDWebImage源码解析(一)
54. app的性能优化
1 instruments
在iOS上进行性能分析的时候,首先考虑借助instruments这个利器分析出问题出在哪,比如要查看程序哪些部分最耗时,可以使用Time Profiler,要查看内存是否泄漏了,可以使用Leaks等。
2 不要阻塞主线程
在iOS里关于UIKit的操作都是放在主线程,因此如果主线程被阻塞住了,你的UI可能无法及时响应事件,给人一种卡顿的感觉。大多数阻塞主线程的情况是在主线程做IO操作,比如文件的读写,包含数据库、图片、json文本或者log日志等,尽量将这些操作放放到子线程(如果数据库有一次有较多的操作,记得采用事务来处理,性能相差还是挺大的),或者在后台建立对应的dispatch queue来做这些操作,比如一个低级别的serial queue来负责log文件的记录等等。程序中如果你的代码逻辑是按照同步的逻辑来写的,尽量修改逻辑代码吧。
3 使用缓存
一般为了提升用户体验,都会在应用中使用缓存,比如对于图片资源可以使用SDWebImage这个开源库,里面就实现了一个图片缓存的功能。
4. 重用和延迟加载Views,不要把view设置为透明,减少视图层级,减少subviews个数,
更多的view意味着更多的渲染,也就是更多的CPU和内存消耗。系统的view默认都是懒加载过程,只有用到view的时候,才会新建加载,节省CPU的消耗。把View设置为不透明颜色,能减少渲染时的性能消耗。
5. 重用大开销的对象
例如cells之类。还比如NSDateFormatter和NSCalendar类的初始化非常慢,我们就通过使用属性来延迟加载NSDateFormatter对象
6. 使用UITableView和UICollectionView的重用,并缓存动态Cell的高度。
给UITableViewCells, UICollectionViewCells,UITableViewHeaderFooterViews设置正确的reuseIdentifier,来复用相应视图。缓存cell的高度,避免每次都是计算。
7. 正确使用加载图片的方式
[UIImage imageNamed:@"myImage"];
首先会到缓存中查找如果存在返回图片对象,缓存中没有就会从资源文件中加载并缓存到内存中去。 [UIImage imageWithContentsOfFile:@"myImage"];
是从磁盘中读取加载图片。如果你要加载一个大图片而且是一次性使用,那么就没必要缓存这个图片,用imageWithContentsOfFile足矣,这样不会浪费内存来缓存它。
app新能优化
55. 横竖屏布局适配
关于iOS横竖屏适配
iOS 屏幕适配,autoResizing autoLayout和sizeClass图文详解
iPad横竖屏下的代码适配
网友评论