×
广告

iOS面试记录

96
BURIBURI_ZAEMON
2018.04.13 22:58 字数 34822

Ping++SDK:一次申请开通多个支付渠道,一次接入所有平台和支付方式

需要传递三个参数:api key 应用id notify url

流程:

设置 API-Key (相当于token)

SDK 验证签名设置 为了进一步增强交易请求的安全性,Ping++ 交易接口针对所有的 POST 和 PUT 请求已经新增 RSA 加密验签功能。如果使用该签名验证功能,你需要生成密钥,然后将私钥配置到你的代码中,公钥上传至 Ping++ 管理平台并启用验签开关。

从服务端发起支付请求,获取支付凭据  Ping++ 收到支付请求后返回给你的服务器一个 Charge 对象,我们称这个 Charge 对象为支付凭据。

将获得的支付凭据传给 Client 你的服务器需要按照 JSON 字符串格式将支付凭据返回给你的客户端,Ping++ SDK 对此做了相应的处理,你只需要将获得的支付凭据直接传给客户端。客户端接收后使用该支付凭据用于调起支付控件,而支付凭据的传送方式需要你自行实现。

接收 Webhooks 通知 当用户完成交易后 Ping++ 会给你配置在 Ping++ 管理平台的 Webhooks 通知地址主动发送支付结果,我们称之为Webhooks 通知。 Webhooks 通知是以 POST 形式发送的 JSON,放在请求的 body 里,内容是 Event 对象,支付成功的事件类型为charge.succeeded ,你需要监听并接收 Webhooks 通知,接收到 Webhooks 后需要返回服务器状态码 2xx 表示接收成功,否则请返回状态码500。

验证 Webhooks 签名 Ping++ 的 Webhooks 通知包含了签名字段,可以使用该签名验证 Webhooks 通知的合法性。签名放置在 header 的自定义字段 x-pingplusplus-signature 中,签名用 RSA 私钥对 Webhooks 通知使用 RSA-SHA256 算法进行签名,以 base64 格式输出。

C数组初始化:

一般来说 全局变量、静态变量处于数据区,默认初始化为0 (如果指定初始值,则为指定的值)

而局部变量处于堆栈区,其数值是随机的,即当时内存中的值。

在表达式中,所有的float类型都转换为double型以提高运算精度。

在赋值语句中,如果赋值号左右两端的类型不同,则将赋值号右边的值转换为赋值号左边的类型,其结果类型还是左边类型。

Java:

'a'+1的结果是 b (C应该也是)

" " + 'a'+ 1

的结果是a1

NSURLConnectionDelegate协议主要有如下4个常用方法:

connection: didReceiveResponse: // 开始接收到服务器的响应时调用

connection: didReceiveData: // 接收到服务器返回的数据时调用,服务器返回的数据比较大时会分多次调用

connectionDidFinishLoading: // 服务器返回的数据完全接收完毕后调用

connection: didFailWithError: // 请求出错时调用,比如请求超时

UIApplication的keyWindow 是APP中唯一的Uiwindow对象

UIWindow的keyWindow可以接手键盘输入等时间的Uiwindow

判断是否为iPad(注意,还有iPhone、iPod等)

if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {...}

iOS数据持久化方式

plist文件(属性列表)

preference(偏好设置)

NSKeyedArchiver(归档)

SQLite 3

CoreData

plist 能被序列化的都是ns开头的对象,使用过程:1.获取文件路径2.writeToFile 可选atomically 安全写入

 读取NSArray *result = [NSArray arrayWithContentsOfFile:fileName];

preference NSUserDefaults 1.获取[NSUserDefaults standardUserDefaults] 2.写入[userDefaults setBool:YES forKey:@"sex"]; 3. [userDefaults synchronize];

读取方法  NSString *name = [userDefaults objectForKey:@"a"];

NSKeyedArchiver NSCoding协议声明了两个方法,这两个方法都是必须实现的。一个用来说明如何将对象编码到归档中,另一个说明如何进行解档来获取一个新对象。

解档:- (id)initWithCoder:(NSCoder *)aDecoder {

 if ([super init]) {

 self.avatar = [aDecoder decodeObjectForKey:@"avatar"];}

 return self;

 }

归档:- (void)encodeWithCoder:(NSCoder *)aCoder {

 [aCoder encodeObject:self.avatar forKey:@"avatar"];}

SQLite

sqlite3_exec()创表、更新、插入和删除操作。但是一般不用它执行查询语句,因为它不会返回查询到的数据。

sqlite3_exec(_sqlite3(数据库对象), sql.UTF8String(编码方式), NULL(返回), NULL, &errmsg(报错信息));

查询:

sqlite3_prepare_v2() : 检查sql的合法性

sqlite3_step() : 逐行获取查询结果,不断重复,直到最后一条记录

sqlite3_coloum_xxx(数据库对象,列数) : 获取对应类型的内容,iCol对应的就是SQL语句中字段的顺序,从0开始。根据实际查询字段的属性,使用sqlite3_column_xxx取得对应的内容即可。

sqlite3_finalize() : 释放数据库对象

coredata

CoreData主要提供的对象-关系映射(ORM)功能,把OC对象转化为数据保存到文件,也可以数据转化为OC对象

Category分类

可以在不修改原来类的基础上,为一个类扩展方法,

分类中只能添加“方法”,不能增加成员变量。

分类中可以访问原来类中的成员变量,但是只能访问@protect和@public形式的变量。

如果想要访问本类中的私有变量,分类和子类一样,只能通过方法来访问。

如果一定要在分类中添加成员变量,可以通过getter,setter手段进行添加,详细以后再写,TODO

extension类扩展是category的一个特例,有时候也被称为匿名分类。他的作用是为一个类添加一些私有的成员变量和方法。

和分类不同,类扩展即可以声明成员变量又可以声明方法。

拓展主要在.m文件中

$ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,请使用\$。

( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 \( 和 \)。

. 匹配除换行符 \n之外的任何单字符。要匹配 .,请使用 \。

[ 标记一个中括号表达式的开始。要匹配 [,请使用 \[。

* 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。

+ 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。

? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。

\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\n’ 匹配换行符。序列 ‘\\’ 匹配 “\”,而 ‘\(’ 则匹配 “(”。

^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。

{ 标记限定符表达式的开始。要匹配 {,请使用 \{。

| 指明两项之间的一个选择。要匹配 |,请使用 \|。

Genstrings  [-a] [-q] [-o] sourcefile 多语言

程序的五种状态

Not Running:未运行。

Inactive:前台非活动状态。处于前台,但是不能接受事件处理。

Active:前台活动状态。处于前台,能接受事件处理。

Background:后台状态。进入后台,如果又可执行代码,会执行代码,代码执行完毕,程序进行挂起。

Suspended:挂起状态。进入后台,不能执行代码,如果内存不足,程序会被杀死。

刚创建的对象没有被引用的话,引用计数是0。只有某个变量指向了这个对象时才会+1。

开发者使用 block 的时候苹果官方文档中说明推荐使用copy,使用copy的原因就在于大家所熟知的把block从栈管理过渡到堆管理

在ARC下面苹果果帮助我们完成了copy的工作,在ARC下面即使使用的修饰符是Strong,实际上效果和使用copy是一样的这一点在苹果的官方文档也有说明

imageNamed是会把读取到的image存在某个缓存里面,第二次读取相同图片的话系统就会直接从那个缓存中获取,从某种意义上好像一种优化,但是imageNamed读取到的那个图片似乎不会因为Memory Warning而释放,所以用这个会导致在 内存不足 的时候闪退。简单的说imageNamed采用了缓存机制,如果缓存中已加载了图片,直接从缓存读就行了,每次就不用再去读文件了,效率会更高

NSXMLParser构造方法 initWithContentsOfURL,initWithData,initWithStream,

也就是本地xml文件的解析必须转化为NSData才可以

json的初始化方法有JSONObjectWithData, JSONObjectWithStream

解析json用dataWithJSONObject,writeJSONObject;toStream:

不管在ARC还是MRC环境下,block内部如果没有访问外部变量,这个block是全局block__NSGlobalBlock__,形式类似函数,存储在内存中的代码区。

在MRC下,block内部如果访问外部变量,这个block是栈block__NSStackBlock__,存储在内存中的栈上。

在MRC下,block内部访问外部变量,同时对该block做一次copy操作,这个block是堆block__NSMallocBlock__,存储在内存中的堆上。

在ARC下,block内部如果访问外部变量,这个block是堆block__NSMallocBlock__,存储在内存中的堆上,因为在ARC下,默认对block做了一次copy操作

主线程默认启动run loop,子线程等需要手动启动,且在特定的场景下需要启动:需要使用NSTimer等。

pthread是一套通用的多线程API,适用于Unix\Linux\Windows,跨平台,是c语言,生命周期要手动管理 语言C

NSThread是面向对象的,基于pthread的封装,也是手动管理生命周期 OC 封装pthread

GCD是OC管理多线程最好用的方案,没有之一,自动管理生命周期 底层C

NSOperation是基于GCD的封装,所以问题不在于是NSOperation或者是NSOperationQueue,因为GCD是纯C的,所以,NSOperation以及GCD与NSThread半毛线关系都没有。

MRC:手动内存管理。ARC自动内存管理 GC:垃圾回收。MRR:MRC的官方名字

iPhone中预置了3种可以直接使用的导航模式,平铺列表、标签页、树状结构

平铺导航( UITabbarController ) 标签导航( UINavigationController ) 树形导航(UIPageViewController)

NSString不能手动分配内存,只能由系

多态是指不同对象以自己的方式响应相同的消息的能力

OCUnit和XCTest都是官方的测试框架,OCUnit已经过时被XCTest所取代。

GHUnit和OCMock都是第三方的测试框架

通过组合实现多继承

假设C类要同时继承A类和B类 .

1.C类在头文件导入A类和B类的头文件 .

2.C类头文件声明需要继承自A类和B类已有的方法和属性 . ( 实现继承的特性 )

3.在C类内部创建A类和B类的实例 , 变成自己的成员变量 . ( C类调用方法时 , 方法内部是用A类和B类调用自己对应的方法 )

4.把A类和B类的属性与C类的属性存储方法关联起来 . ( C类继承自A类和B类 , 就拥有了A类和B类的能力 , 可是我们是在通过组合来实现多继承 , 所以一定要保证属性关联正确 )

5.C类调用继承过来的方法时 , 实际内部是用A类和B类调用自己对应的方法的 .

通过协议实现多继承

假设C类要同时继承A类和B类 .

1.C类在头文件导入A类和B类的头文件 .

2.在A类和B类把需要被C类继承的方法和属性声明成一份协议 .

3.C类遵守A类和B类的协议 .

4.在实现文件中实现协议方法 , 协议属性也要实现 . ( 问题是你无法调用到A类和B类原生的方法 )

组合和协议实现多继承的问题

1.组合实现下 , 想要继承来的不管是属性还是方法都没有提示 .

2.如果继承来的两个类中属性名和方法名相同就很麻烦了 , 要仔细区分开 .

3.通过协议实现的多继承 , 不能调用父类的方法 .

Swift 值永远不会被隐式转换为其他类型

@implementation Somelass

@autorelease Somelass

@protocol Somelass

@interface Somelass

A是声明一个类的实现部分;

B是声明自动释放池的指令;

C是声明协议;

D是声明一个类;

OC中也是有四种访问权限修饰符:@public、@protected、@private、@package

OC中的方法是没有修饰符的概念的,这个和Java有很大的区别,一般都是公开访问的,即public的, 如果想让一个方法不被外界访问的话,只需要在.m文件中实现这个方法,不要在头文件中进行定义

一个object c中的接口就是C语言中的一个HEADER文件,这个文件的结构如下:

我们来逐行看一下上面的内容:

(1.) 这里与C 语言不同的是导入头文件使用的是import,而不是include。另外与C 语言一样的地方是如果你想从的地方是如果你想从当前目录查找Header 文件,找不到就到系统的头文件库中查找,请使用#import “Header 文件”,如果你只想从系统的头文件库中查找,请使用#import。Foundation/Foundation.h 包含了Foundation Kit 中的所有的头文件定义,GNUStep 的Object-C 的Foundation 头文件在\GNUStep 安装目录\GNUstep\System\Library\Headers\Foundation 文件夹。GNUStep 的Object-C 的AppKit 头文件在\GNUStep 安装目录\GNUstep\System\Library\Headers\ AppKit 文件夹。

(2.) static 标识的类变量定义在接口的外面,类变量只能本类访问,除非提供类方法给外部访问这个类变量。

(3.) Object-C 中的@+指令表示C 语言之外的Object-C 的衍生语法,因此@interface 表示定义了一个接口,接口名称之后紧跟了一个冒号,冒号后是父类的名字,Object-C 中的顶级父类是NSObject。

(4.) 接口定义之后紧接着一对{ },其中定义了成员变量,所谓的成员变量就相当于JAVA 中的实例变量,从属于类的对象。Object-C 中的成员变量使用@public、@protected、@private作为访问修饰符,默认是@protected。这里你要知道的是Object-C 中只有成员变量有访问修饰符,类变量、类方法、成员方法是没有访问修饰符的,所有的方法都是public 的,所有的类变量都是私有的。

(5.) 以-开头的方法为成员方法,以+开头的方法为类方法,方法中的类型描述(返回值类型、参数类型)都必须使用( )包围。如果方法有多个参数,每个参数都有一个标签名(可以省略,但不建议这样做),每个标签名之后使用冒号与参数描述分隔。在有多个参数的方法中,实际的方法名称为 方法名:标签名1:标签名2:… …,上面的拥有两个参数的方法的方法名为setNumerator:andDenominator:。与JAVA不同的是Object-C 中的类方法只能类调用,如果你使用对象调用会报错,而JAVA 仅仅是在编译期给出警告。

(6.) 以@end 表示接口定义结束。这是因为与JAVA 不同的是JAVA 的类型定义使用{ }包围,而Object-C 中的{ }只包围成员变量,因此必须有个结束标志,一般JAVA 程序员经常会忘记写这个结束标记。这里你要知道Object-C 的@interface 与JAVA 的interface 并不是一回事儿,后面你会看到Object-C 中的@protocol 与JAVA 中的interface 才是等同的。这里你只需要记住的是Object-C中的@interface 只是类的一个描述,因为@interface 通常在独立的h 文件中,你可以把它类比成C 语言中的函数原型,也就是在Object-C 里应该叫做类的原型。通过这个原型,编译器可以知道具体实现类有哪些功能。上面的接口中的方法很简单,主要就是成员变量的setter、getter 方法,这与JAVA 没有什么不同的。但是你会发现getter 方法没有以get 作为方法名称前缀,这是因为get 开头的方法在Object-C 中有着特殊的含义,这在后面将会看到。

OC中的多态:不同对象对同一消息的不同响应.子类可以重写父类的方法

多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作

继承

1) public继承方式

基类中所有public成员在派生类中为public属性;

基类中所有protected成员在派生类中为protected属性;

基类中所有private成员在派生类中不可访问。

2) protected继承方式

基类中的所有public成员在派生类中为protected属性;

基类中的所有protected成员在派生类中为protected属性;

基类中的所有private成员在派生类中仍然不可访问。

3) private继承方式

基类中的所有public成员在派生类中均为private属性;

基类中的所有protected成员在派生类中均为private属性;

基类中的所有private成员在派生类中均不可访问。

在公用派生类中的访问属性在私有派生类中的访问属性在保护派生类中的访问属性

私有成员不可访问不可访问不可访问

公用成员公用私有保护

保护成员保护私有保护

category  extension 的区别

分类有名字,类扩展没有分类名字,是一种特殊的分类

分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法

define  const常量有什么区别?

define在预处理阶段进行替换,const常量在编译阶段使用

宏不做类型检查,仅仅进行替换,const常量有数据类型,会执行类型检查

define不能调试,const常量可以调试

define定义的常量在替换后运行过程中会不断地占用内存,而const定义的常量存储在数据段只有一份copy,效率更高

define可以定义一些简单的函数,const不可以

blockweak修饰符的区别?

__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,也可以修饰基本数据类型

__weak只能在ARC模式下使用,只能修饰对象(NSString),不能修饰基本数据类型

block修饰的对象可以在block中被重新赋值,weak修饰的对象不可以

static关键字的作用

函数(方法)体内 static 变量的作用范围为该函数体,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;

在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;

在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明 它的模块内;

在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;

在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量

堆和栈的区别

从管理方式来讲

对于栈来讲,是由编译器自动管理,无需我们手工控制;

对于堆来说,释放工作由程序员控制,容易产生内存泄露(memory leak)

从申请大小大小方面讲

栈空间比较小

堆控件比较大

从数据存储方面来讲

栈空间中一般存储基本类型,对象的地址

堆空间一般存放对象本身,block的copy等

风格纠错题

修改后的代码

typedef NS_ENUM(NSInteger, CYLSex)

{

 CYLSexMan,

 CYLSexWoman

};

@interface CYLUser : NSObject

@property (nonatomic, copy, readonly) NSString *name;

@property (nonatomic, assign, readonly) NSUInteger age;

@property (nonatomic, assign, readwrite) CYLSex sex;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;

+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;

@end

详细解说,这里就不列举了

Objective-C使用什么机制管理对象内存?

MRC 手动引用计数

ARC 自动引用计数,现在通常ARC

通过 retainCount 的机制来决定对象是否需要释放。 每次 runloop 的时候,都会检查对象的 retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,可以释放掉了

Alloc之后引用计数为0,init之后才为1

nsstring的retaincount有多种情况,

NSString *s = @"test" 为极大值

NSString *s = [NSString stringWithFormat:@"%s", "test"]; 为1

NSString *s2 = [NSString stringWithString:[NSString stringWithFormat:@"test,%d",1]]; retainCount取决于变量值,因此为2

NSMutableString* myStr3 = [NSMutableString stringWithString:@"string 3"]; 为1

ARC通过什么方式帮助开发者管理内存?

通过编译器在编译的时候,插入类似内存管理的代码

ARC 并不是舍弃了 @autoreleasepool,而是在编译阶段帮你插入必要的 retain/release/autorelease 的代码调用。

用@autoreleasepool是有用的。正常情况下,你创建的变量会在超出其作用域的时候被释放掉。而如果你的函数写的很长,在你函数运行过程中出现很多中间变量,占据了大量的内存,怎么办?用@autoreleasepool。在@autoreleasepool中创建的变量,会在@autoreleasepool结束的时候执行一次release,进行释放。其实@autoreleasepool就相当于一层作用域。

ARC是为了解决什么问题诞生的?

首先解释ARC: automatic reference counting自动引用计数

了解MRC的缺点

在MRC时代当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被release了

释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次(MRC下即谁创建,谁释放,避免重复释放)

模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放

多线程操作时,不确定哪个线程最后使用完毕

综上所述,MRC有诸多缺点,很容易造成内存泄露和坏内存的问题,这时苹果为尽量解决这个问题,从而诞生了ARC

ARC下还会存在内存泄露吗?

循环引用会导致内存泄露

Objective-C对象与CoreFoundation对象进行桥接的时候如果管理不当也会造成内存泄露

CoreFoundation中的对象不受ARC管理,需要开发者手动释放

什么情况使用weak关键字,相比assign有什么不同?

首先明白什么情况使用weak关键字?

在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用weak来解决,比如:delegate代理属性,代理属性也可使用assign

自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用weak,自定义IBOutlet控件属性一般也使用weak;当然,也可以使用strong,但是建议使用weak

weak 和 assign的不同点

weak策略在属性所指的对象遭到摧毁时,系统会将weak修饰的属性对象的指针指向nil,在OC给nil发消息是不会有什么问题的;如果使用assign策略在属性所指的对象遭到摧毁时,属性对象指针还指向原来的对象,由于对象已经被销毁,这时候就产生了野指针,如果这时候在给此对象发送消息,很容造成程序奔溃

assigin 可以用于修饰非OC对象,而weak必须用于OC对象

@property 的本质是什么?

@property其实就是在编译阶段由编译器自动帮我们生成ivar成员变量,getter方法,setter方法

ivargettersetter是如何生成并添加到这个类中的?

使用“自动合成”( autosynthesis)

这个过程由编译器在编译阶段执行自动合成,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码

除了生成getter、setter方法之外,编译器还要自动向类中添加成员变量(在属性名前面加下划线,以此作为实例变量的名字)

为了搞清属性是怎么实现的,反编译相关的代码,他大致生成了五个东西

·   // 该属性的“偏移量” (offset),这个偏移量是“硬编码” (hardcode),表示该变量距离存放对象的内存区域的起始地址有多远

·   OBJC_IVAR_$类名$属性名称

·       

·   // 方法对应的实现函数

·   setter与getter

·       

·   // 成员变量列表

·   ivar_list

·       

·   // 方法列表

·   method_list

·       

·   // 属性列表

·   prop_list

每次增加一个属性,系统都会在ivar_list中添加一个成员变量的描述

在method_list中增加setter与getter方法的描述

在prop_list中增加一个属性的描述

计算该属性在对象中的偏移量

然后给出setter与getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转

@protocol  category 中如何使用 @property

在protocol中使用property只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性

category 使用 @property也是只会生成setter和getter方法声明,如果我们真的需要给category增加属性的实现,需要借助于运行时的两个函数

objc_setAssociatedObject

objc_getAssociatedObject

@property后面可以有哪些修饰符?

原子性---nonatomic特质

如果不写默认情况为atomic(系统会自动加上同步锁,影响性能)

在iOS开发中尽量指定为nonatomic,这样有助于提高程序的性能

读/写权限---readwrite(读写)、readooly (只读)

内存管理语义---assign、strong、 weak、unsafe_unretained、copy

方法名---getter=、setter=

@property (nonatomic, getter=isOn) BOOL on;

// setter=这种不常用,也**不推荐**使用。故不在这里给出写法

不常用的:nonnull,null_resettable,nullable

使用atomic一定是线程安全的吗?

不是,atomic的本意是指属性的存取方法是线程安全的,并不保证整个对象是线程安全的。

举例:声明一个NSMutableArray的原子属性stuff,此时self.stuff 和self.stuff = othersulf都是线程安全的。但是,使用[self.stuff objectAtIndex:index]就不是线程安全的,需要用互斥锁来保证线程安全性

@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 = instance.var时,由于缺getter方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定

ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?

基本数据:atomic,readwrite,assign

普通的OC对象:atomic,readwrite,strong

@synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?

先回答第二个问题:不会

@synthesize合成成员变量的规则,有以下几点:

如果指定了成员变量的名称,会生成一个指定的名称的成员变量

如果这个成员已经存在了就不再生成了

如果指定@synthesize foo;就会生成一个名称为foo的成员变量,也就是说:会自动生成一个属性同名的成员变量

o @interface XMGPerson : NSObject

o  

o @property (nonatomic, assign) int age;

o  

o @end

o  

o @implementation XMGPerson

o  

o // 不加这语句默认生成的成员变量名为_age

o // 如果加上这一句就会生成一个跟属性名同名的成员变量

o @synthesize age;

o  

o @end

如果是 @synthesize foo = _foo; 就不会生成成员变量了

在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?

首先的搞清楚什么情况下不会autosynthesis(自动合成)

同时重写了setter和getter时

重写了只读属性的getter时

使用了@dynamic时

在 @protocol 中定义的所有属性

在 category 中定义的所有属性

重载的属性,当你在子类中重载了父类中的属性,必须 使用@synthesize来手动合成ivar

应用场景

当你同时重写了setter和getter时,系统就不会生成ivar)。这时候有两种选择

手动创建ivar

使用@synthesize foo = _foo;,关联@property与ivar

可以用来修改成员变量名,一般不建议这么做,建议使用系统自动生成的成员变量

怎么用 copy 关键字?

NSString、NSArray、NSDictionary等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,为确保对象中的属性值不会无意间变动,应该在设置新属性值时拷贝一份,保护其封装性

block也经常使用copy关键字

block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.

在ARC中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但是建议写上copy,因为这样显示告知调用者“编译器会自动对 block 进行了 copy 操作”

@property声明的NSString(或NSArrayNSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

因为父类指针可以指向子类对象,使用copy的目的是为了让本对象的属性不受外界影响,使用copy无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.

如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.

复制详解

浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指针复制。

深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有一层是深复制。

完全复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都是对象复制。

非集合类对象的copy与mutableCopy

· [不可变对象 copy] // 浅复制

· [不可变对象 mutableCopy] //深复制

· [可变对象 copy] //深复制

· [可变对象 mutableCopy] //深复制

集合类对象的copy与mutableCopy

· [不可变对象 copy] // 浅复制

· [不可变对象 mutableCopy] //单层深复制

· [可变对象 copy] //单层深复制

· [可变对象 mutableCopy] //单层深复制

这里需要注意的是集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制

这个写法会出什么问题: @property (copy) NSMutableArray *array;

因为copy策略拷贝出来的是一个不可变对象,然而却把它当成可变对象使用,很容易造成程序奔溃

这里还有一个问题,该属性使用了同步锁,会在创建时生成一些额外的代码用于帮助编写多线程程序,这会带来性能问题,通过声明nonatomic可以节省这些虽然很小但是不必要额外开销,在iOS开发中应该使用nonatomic替代atomic

如何让自定义类可以用 copy 修饰符?如何重写带 copy 关键字的 setter

若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopyiog与NSMutableCopying协议,不过一般没什么必要,实现NSCopying协议就够了

// 实现不可变版本拷贝

- (id)copyWithZone:(NSZone *)zone;

// 实现可变版本拷贝

- (id)mutableCopyWithZone:(NSZone *)zone;

// 重写带 copy 关键字的 setter

- (void)setName:(NSString *)name

{

 _name = [name copy];

}

+(void)load; +(void)initialize;有什么用处?

+(void)load;

当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息

load 方法会在每一个类甚至分类被引入时仅调用一次,调用的顺序:父类优先于子类, 子类优先于分类

由于 load 方法会在类被import 时调用一次,而这时往往是改变类的行为的最佳时机,在这里可以使用例如method swizlling 来修改原有的方法

load 方法不会被类自动继承

+(void)initialize;

也是在第一次使用这个类的时候会调用这个方法,也就是说 initialize也是懒加载

总结:

在Objective-C中,runtime会自动调用每个类的这两个方法

+load会在类初始加载时调用

+initialize会在第一次调用类的类方法或实例方法之前被调用

这两个方法是可选的,且只有在实现了它们时才会被调用

两者的共同点:两个方法都只会被调用一次

Foundation对象与Core Foundation对象有什么区别

Foundation框架是使用OC实现的,Core Foundation是使用C实现的

Foundation对象 和 Core Foundation对象间的转换:俗称桥接

ARC环境桥接关键字:

o // 可用于Foundation对象 和 Core Foundation对象间的转换

o __bridge

o  

o // 用于Foundation对象 转成 Core Foundation对象

o __bridge_retained

o  

o // Core Foundation对象 转成 Foundation对象

o __bridge_transfer

Foundation对象 转成 Core Foundation对象

使用__bridge桥接

如果使用__bridge桥接,它仅仅是将strOC的地址给了strC, 并没有转移对象的所有权,也就是说, 如果使用__bridge桥接, 那么如果strOC释放了,strC也不能用了

注意:在ARC条件下,如果是使用__bridge桥接,那么strC可以不用主动释放, 因为ARC会自动管理strOC和strC

§ NSString *strOC1 = [NSString stringWithFormat:@"abcdefg"];

§ CFStringRef strC1 = (__bridge CFStringRef)strOC1;

§ NSLog(@"%@ %@", strOC1, strC1);

使用__bridge_retained桥接

如果使用__bridge_retained桥接,它会将对象的所有权转移给strC, 也就是说, 即便strOC被释放了, strC也可以使用

注意:在ARC条件下,如果是使用__bridge_retained桥接,那么strC必须自己手动释放,因为桥接的时候已经将对象的所有权转移给了strC,而C语言的东西不是不归ARC管理的

§ NSString *strOC2 = [NSString stringWithFormat:@"abcdefg"];

§ // CFStringRef strC2 = (__bridge_retained CFStringRef)strOC2;

§ CFStringRef strC2 = CFBridgingRetain(strOC2);// 这一句, 就等同于上一句

§ CFRelease(strC2);

Core Foundation对象 转成 Foundation对象

使用__bridge桥接

如果使用__bridge桥接,它仅仅是将strC的地址给了strOC, 并没有转移对象的所有权

也就是说如果使用__bridge桥接,那么如果strC释放了,strOC也不能用了

§ CFStringRef strC3 = CFStringCreateWithCString(CFAllocatorGetDefault(), "12345678", kCFStringEncodingASCII);

§ NSString *strOC3 = (__bridge NSString *)strC3;

§ CFRelease(strC3);

使用__bridge_transfer桥接

如果使用__bridge_transfer桥接,它会将对象的所有权转移给strOC, 也就是说, 即便strC被释放了, strOC也可以使用

如果使用__bridge_transfer桥接, 他会自动释放strC, 也就是以后我们不用手动释放strC

§ CFStringRef strC4 = CFStringCreateWithCString(CFAllocatorGetDefault(), "12345678", kCFStringEncodingASCII);

§ // NSString *strOC = (__bridge_transfer NSString *)strC;

§ NSString *strOC4 = CFBridgingRelease(strC4); // 这一句, 就等同于上一句

MRC环境:直接强转

o -(void)bridgeInMRC

o {

o   // 将Foundation对象转换为Core Foundation对象,直接强制类型转换即可

o   NSString *strOC1 = [NSString stringWithFormat:@"xxxxxx"];

o   CFStringRef strC1 = (CFStringRef)strOC1;

o   NSLog(@"%@ %@", strOC1, strC1);

o   [strOC1 release];

o   CFRelease(strC1);

o  

o   // 将Core Foundation对象转换为Foundation对象,直接强制类型转换即可

o   CFStringRef strC2 = CFStringCreateWithCString(CFAllocatorGetDefault(), "12345678", kCFStringEncodingASCII);

o   NSString *strOC2 = (NSString *)strC2;

o   NSLog(@"%@ %@", strOC2, strC2);

o   [strOC2 release];

o   CFRelease(strC2);

o }

addObserver:forKeyPath:options:context:各个参数的作用分别是什么,observer中需要实现哪个方法才能获得KVO回调?

/**

 1. self.person:要监听的对象

 2. 参数说明

 1> 观察者,负责处理监听事件的对象

 2> 要监听的属性

 3> 观察的选项(观察新、旧值,也可以都观察)

 4> 上下文,用于传递数据,可以利用上下文区分不同的监听

 */

[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"Person Name"];

/**

 * 当监控的某个属性的值改变了就会调用

 *

 * @param keyPath 监听的属性名

 * @param object 属性所属的对象

 * @param change 属性的修改情况(属性原来的值、属性最新的值)

 * @param context 传递的上下文数据,与监听的时候传递的一致,可以利用上下文区分不同的监听

 */

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context

{

 NSLog(@"%@对象的%@属性改变了:%@", object, keyPath, change);

}

KVO内部实现原理

KVO是基于runtime机制实现的

当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制

如果原类为Person,那么生成的派生类名为NSKVONotifying_Person

每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法

键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前,willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey: 会被调用,继而observeValueForKey:ofObject:change:context: 也会被调用。

补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类

如何手动触发一个valueKVO

自动触发的场景:在注册KVO之前设置一个初始值,注册之后,设置一个不一样的值,就可以触发了

想知道如何手动触发,必须知道自动触发 KVO 的原理,见上面的描述

手动触发演示

@property (nonatomic, strong) NSDate *now;

- (void)viewDidLoad

{

 [super viewDidLoad];

 // “手动触发self.now的KVO”,必写。

 [self willChangeValueForKey:@"now"];

 // “手动触发self.now的KVO”,必写。

 [self didChangeValueForKey:@"now"];

}

1.KVC与KVO的不同?

KVC(键值编码),即Key-Value Coding,一个非正式的Protocol,使用字符串(键)访问一个对象实例变量的机制。而不是通过调用Setter、Getter方法等显式的存取方式去访问。

KVO(

键值监听),即Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,对象就会接受到通知,前提是执行了setter方法、或者使用了KVC赋值。

2.和notification(通知)的区别?

notification比KVO多了发送通知的一步。两者都是一对多,但是对象之间直接的交互,notification明显得多,需要notificationCenter来做为中间交互。而KVO如我们介绍的,设置观察者->处理属性变化,至于中间通知这一环,则隐秘多了,只留一句“交由系统通知”,具体的可参照以上实现过程的剖析。

notification的优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,例如键盘、前后台等系统通知的使用也更显灵活方便。(参照通知机制第五节系统通知名称内容)

3.与delegate的不同?

和delegate一样,KVO和NSNotification的作用都是类与类之间的通信。但是与delegate不同的是:这两个都是负责发送接收通知,剩下的事情由系统处理,所以不用返回值;而delegate 则需要通信的对象通过变量(代理)联系;

delegate

一般是一对一,而这两个可以一对多。

4.涉及技术:

KVC/KVO实现的根本是Objective-C的动态性和runtime,以及访问器方法的实现;

Kvo对比其他

对比其他的回调方式,KVO机制的运用的实现,更多的由系统支持,相比notification、delegate等更简洁些,并且能够提供观察属性的最新值以及原始值;但是相应的在创建子类、重写方法等等方面的内存消耗是很巨大的。所以对于两个类之间的通信,我们可以根据实际开发的环境采用不同的方法,使得开发的项目更加简洁实用。

另外需要注意的是,由于这种继承方式的注入是在运行时而不是编译时实现的,如果给定的实例没有观察者,那么KVO不会有任何开销,因为此时根本就没有KVO代码存在。但是即使没有观察者,委托和NSNotification还是得工作,这也是KVO此处零开销观察的优势。

若一个类有实例变量NSString *_foo,调用setValue:forKey:时,是以foo还是_foo作为key

都可以

KVCkeyPath中的集合运算符如何使用?

必须用在集合对象上或普通对象的集合属性上

简单集合运算符有@avg, @count , @max , @min ,@sum

格式 @"@sum.age" 或 @"集合属性.@max.age"???

KVCKVOkeyPath一定是属性么?

可以是成员变量

如何关闭默认的KVO的默认实现,并进入自定义的KVO实现?

如何自己动手实现 KVO

apple用什么方式实现对一个对象的KVO

此题就是问KVO的实现原理

Size Classes 具体使用

对屏幕进行分类

UIViewCALayer是什么关系?

UIView显示在屏幕上归功于CALayer,通过调用drawRect方法来渲染自身的内容,调节CALayer属性可以调整UIView的外观,UIView继承自UIResponder,比起CALayer可以响应用户事件,Xcode6之后可以方便的通过视图调试功能查看图层之间的关系

UIView是iOS系统中界面元素的基础,所有的界面元素都继承自它。它内部是由Core Animation来实现的,它真正的绘图部分,是由一个叫CALayer(Core Animation Layer)的类来管理。UIView本身,更像是一个CALayer的管理器,访问它的跟绘图和坐标有关的属性,如frame,bounds等,实际上内部都是访问它所在CALayer的相关属性

UIView有个layer属性,可以返回它的主CALayer实例,UIView有一个layerClass方法,返回主layer所使用的类,UIView的子类,可以通过重载这个方法,来让UIView使用不同的CALayer来显示,如:

- (class) layerClass {

 // 使某个UIView的子类使用GL来进行绘制

 return ([CAEAGLLayer class]);

}

UIView的CALayer类似UIView的子View树形结构,也可以向它的layer上添加子layer,来完成某些特殊的显示。例如下面的代码会在目标View上敷上一层黑色的透明薄膜。

grayCover = [[CALayer alloc]init];

grayCover.backgroudColor = [[UIColor blackColor]colorWithAlphaComponent:0.2].CGColor;

[self.layer addSubLayer:grayCover];

补充部分,这部分有深度了,大致了解一下吧,UIView的layer树形在系统内部被系统维护着三份copy

逻辑树,就是代码里可以操纵的,例如更改layer的属性等等就在这一份

动画树,这是一个中间层,系统正是在这一层上更改属性,进行各种渲染操作

显示树,这棵树的内容是当前正被显示在屏幕上的内容

这三棵树的逻辑结构都是一样的,区别只有各自的属性

loadView的作用?

loadView用来自定义view,只要实现了这个方法,其他通过xib或storyboard创建的view都不会被加载

看懂控制器view创建的这个图就行

IBOutlet连出来的视图属性为什么可以被设置成weak?

因为父控件的subViews数组已经对它有一个强引用

IBUser Defined Runtime Attributes如何使用?

User Defined Runtime Attributes是一个不被看重但功能非常强大的的特性,它能够通过KVC的方式配置一些你在interface builder中不能配置的属性

当你希望在IB中作尽可能多得事情,这个特性能够帮助你编写更加轻量级的viewcontroller

沙盒目录结构是怎样的?各自用于那些场景?

Application:存放程序源文件,上架前经过数字签名,上架后不可修改

Documents:常用目录,iCloud备份目录,存放数据

Library

Caches:存放体积大又不需要备份的数据

Preference:设置目录,iCloud会备份设置信息

tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能

pushViewControllerpresentViewController有什么区别

两者都是在多个试图控制器间跳转的函数

presentViewController提供的是一个模态视图控制器(modal)

pushViewController提供一个栈控制器数组,push/pop

请简述UITableView的复用机制

每次创建cell的时候通过dequeueReusableCellWithIdentifier:方法创建cell,它先到缓存池中找指定标识的cell,如果没有就直接返回nil

如果没有找到指定标识的cell,那么会通过initWithStyle:reuseIdentifier:创建一个cell

当cell离开界面就会被放到缓存池中,以供下次复用

如何高性能的给 UIImageView 加个圆角?

不好的解决方案

使用下面的方式会强制Core Animation提前渲染屏幕的离屏绘制, 而离屏绘制就会给性能带来负面影响,会有卡顿的现象出现

o self.view.layer.cornerRadius = 5;

o self.view.layer.masksToBounds = YES;

正确的解决方案:使用绘图技术

- (UIImage *)circleImage

{

 // NO代表透明

 UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);

 // 获得上下文

 CGContextRef ctx = UIGraphicsGetCurrentContext();

 // 添加一个圆

 CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);

 CGContextAddEllipseInRect(ctx, rect);

 // 裁剪

 CGContextClip(ctx);

 // 将图片画上去

 [self drawInRect:rect];

 UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

 // 关闭上下文

 UIGraphicsEndImageContext();

 return image;

}

还有一种方案:使用了贝塞尔曲线"切割"个这个图片, 给UIImageView 添加了的圆角,其实也是通过绘图技术来实现的

UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];

imageView.center = CGPointMake(200, 300);

UIImage *anotherImage = [UIImage imageNamed:@"image"];

UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);

[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds

 cornerRadius:50] addClip];

[anotherImage drawInRect:imageView.bounds];

imageView.image = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

[self.view addSubview:imageView];

使用drawRect有什么影响?

drawRect方法依赖Core Graphics框架来进行自定义的绘制

缺点:它处理touch事件时每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton实例,那就会很糟糕了

这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会把当前图层标记为dirty,但还是会显示原来的内容,直到下一次的视图渲染周期,才会将标记为 dirty 的图层重新建立Core Graphics上下文,然后将内存中的数据恢复出来, 再使用 CGContextRef 进行绘制

描述下SDWebImage里面给UIImageView加载图片的逻辑

SDWebImage 中为 UIImageView 提供了一个分类UIImageView+WebCache.h, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后在替换占位图片

加载图片的过程大致如下:

首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存

如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来

如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片

下载后的图片会加入缓存中,并写入磁盘中

整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来

设计个简单的图片内存缓存器

类似上面SDWebImage实现原理即可

一定要有移除策略:释放数据模型对象

控制器的生命周期

就是问的view的生命周期,下面已经按方法执行顺序进行了排序

// 自定义控制器view,这个方法只有实现了才会执行

- (void)loadView

{

 self.view = [[UIView alloc] init];

 self.view.backgroundColor = [UIColor orangeColor];

}

// view是懒加载,只要view加载完毕就调用这个方法

- (void)viewDidLoad

{

 [super viewDidLoad];

 NSLog(@"%s",__func__);

}

// view即将显示

- (void)viewWillAppear:(BOOL)animated

{

 [super viewWillAppear:animated];

 NSLog(@"%s",__func__);

}

// view即将开始布局子控件

- (void)viewWillLayoutSubviews

{

 [super viewWillLayoutSubviews];

 NSLog(@"%s",__func__);

}

// view已经完成子控件的布局

- (void)viewDidLayoutSubviews

{

 [super viewDidLayoutSubviews];

 NSLog(@"%s",__func__);

}

// view已经出现

- (void)viewDidAppear:(BOOL)animated

{

 [super viewDidAppear:animated];

 NSLog(@"%s",__func__);

}

// view即将消失

- (void)viewWillDisappear:(BOOL)animated

{

 [super viewWillDisappear:animated];

 NSLog(@"%s",__func__);

}

// view已经消失

- (void)viewDidDisappear:(BOOL)animated

{

 [super viewDidDisappear:animated];

 NSLog(@"%s",__func__);

}

// 收到内存警告

- (void)didReceiveMemoryWarning

{

 [super didReceiveMemoryWarning];

 NSLog(@"%s",__func__);

}

// 方法已过期,即将销毁view

- (void)viewWillUnload

{

}

// 方法已过期,已经销毁view

- (void)viewDidUnload

{

}

你是怎么封装一个view

可以通过纯代码或者xib的方式来封装子控件

建立一个跟view相关的模型,然后将模型数据传给view,通过模型上的数据给view的子控件赋值

/**

 * 纯代码初始化控件时一定会走这个方法

 */

- (instancetype)initWithFrame:(CGRect)frame

{

 if(self = [super initWithFrame:frame])

 {

 [self setup];

 }

 return self;

}

/**

 * 通过xib初始化控件时一定会走这个方法

 */

- (id)initWithCoder:(NSCoder *)aDecoder

{

 if(self = [super initWithCoder:aDecoder])

 {

 [self setup];

 }

 return self;

}

- (void)setup

{

 // 初始化代码

}

如何进行iOS67的适配

通过判断版本来控制,来执行响应的代码

功能适配:保证同一个功能在6、7上都能用

UI适配:保证各自的显示风格

// iOS版本为7.0以上(包含7.0)

#define iOS7 ([[UIDevice currentDevice].systemVersion doubleValue]>=7.0)

如何渲染UILabel的文字?

通过NSAttributedString/NSMutableAttributedString(富文本)

UIScrollViewcontentSize能否在viewDidLoad中设置?

因为UIScrollView的内容尺寸是根据其内部的内容来决定的,所以是可以在viewDidLoad中设置的

补充:(这仅仅是一种特殊情况)

前提,控制器B是控制器A的一个子控制器,且控制器B的内容只在控制器A的view的部分区域中显示

假设控制器B的view中有一个UIScrollView这样一个子控件

如果此时在控制器B的viewDidLoad中设置UIScrollView的contentSize的话会导致不准确的问题

因为任何控制器的view在viewDidLoad的时候的尺寸都是不准确的,如果有子控件的尺寸依赖父控件的尺寸,在这个方法中设置会导致子控件的frame不准确,所以这时应该在下面的方法中设置子控件的尺寸

-(void)viewDidLayoutSubviews;

触摸事件的传递

触摸事件的传递是从父控件传递到子控件

如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件

不能接受触摸事件的四种情况

不接收用户交互,即:userInteractionEnabled = NO

隐藏,即:hidden = YES

透明,即:alpha <= 0.01

未启用,即:enabled = NO

提示:UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的

如何找到最合适处理事件的控件:

首先,判断自己能否接收触摸事件

可以通过重写hitTest:withEvent:方法验证

其次,判断触摸点是否在自己身上

对应方法pointInside:withEvent:

从后往前(先遍历最后添加的子控件)遍历子控件,重复前面的两个步骤

如果没有符合条件的子控件,那么就自己处理

事件响应者链

如果当前view是控制器的view,那么就传递给控制器

如果控制器不存在,则将其传递给它的父控件

在视图层次结构的最顶层视图也不能处理接收到的事件或消息,则将事件或消息传递给UIWindow对象进行处理

如果UIWindow对象也不处理,则将事件或消息传递给UIApplication对象

如果UIApplication也不能处理该事件或消息,则将其丢弃

补充:如何判断上一个响应者

如果当前这个view是控制器的view,那么控制器就是上一个响应者

如果当前这个view不是控制器的view,那么父控件就是上一个响应者

如何实现类似QQ的三角形头像

Quartz2D

使用coreGraphics裁剪出一个三角形

核心动画里包含什么?

基本动画

回头自己总结吧

如何使用核心动画?

创建

设置相关属性

添加到CALayer上,会自动执行动画

runtime怎么添加属性、方法等

ivar表示成员变量

class_addIvar

class_addMethod

class_addProperty

class_addProtocol

class_replaceProperty

是否可以把比较耗时的操作放在NSNotificationCenter

首先必须明确通知在哪个线程中发出,那么处理接受到通知的方法也在这个线程中调用

如果在异步线程发的通知,那么可以执行比较耗时的操作;

如果在主线程发的通知,那么就不可以执行比较耗时的操作

runtime 如何实现 weak 属性

首先要搞清楚weak属性的特点

weak策略表明该属性定义了一种“非拥有关系” (nonowning relationship)。

为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似;

然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)

那么runtime如何实现weak变量的自动置nil?

runtime对注册的类,会进行布局,会将 weak 对象放入一个 hash 表中。

用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会调用对象的 dealloc 方法,

假设 weak 指向的对象内存地址是a,那么就会以a为key,在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为nil。

weak属性需要在dealloc中置nil

在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理

即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil

在属性所指的对象遭到摧毁时,属性值也会清空

// 模拟下weak的setter方法,大致如下

- (void)setObject:(NSObject *)object

{

 objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);

 [object cyl_runAtDealloc:^{

 _object = nil;

 }];

}

一个Objective-C对象如何进行内存布局?(考虑有父类的情况)

所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中

父类的方法和自己的方法都会缓存在类对象的方法缓存中,类方法是缓存在元类对象中

每一个对象内部都有一个isa指针,指向他的类对象,类对象中存放着本对象的如下信息

对象方法列表

成员变量的列表

属性列表

每个 Objective-C 对象都有相同的结构,如下图所示

Objective-C 对象的结构图

ISA指针

根类(NSObject)的实例变量

倒数第二层父类的实例变量

...

父类的实例变量

类的实例变量

根类对象就是NSObject,它的super class指针指向nil

类对象既然称为对象,那它也是一个实例。类对象中也有一个isa指针指向它的元类(meta class),即类对象是元类的实例。元类内部存放的是类方法列表,根元类的isa指针指向自己,superclass指针指向NSObject类

一个objc对象的isa的指针指向什么?有什么作用?

每一个对象内部都有一个isa指针,这个指针是指向它的真实类型

根据这个指针就能知道将来调用哪个类的方法

下面的代码输出什么?

@implementation Son : Father

- (id)init

{

 self = [super init];

 if (self) {

 NSLog(@"%@", NSStringFromClass([self class]));

 NSLog(@"%@", NSStringFromClass([super class]));

 }

 return self;

}

@end

答案:都输出 Son

这个题目主要是考察关于objc中对 self 和 super 的理解:

self 是类的隐藏参数,指向当前调用方法的这个类的实例。而 super 本质是一个编译器标示符,和 self 是指向的同一个消息接受者

当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;

而当使用 super时,则从父类的方法列表中开始找。然后调用父类的这个方法

调用[self class] 时,会转化成 objc_msgSend函数

o id objc_msgSend(id self, SEL op, ...)

调用 [super class]时,会转化成 objc_msgSendSuper函数

o id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

第一个参数是 objc_super 这样一个结构体,其定义如下

o struct objc_super {

o   __unsafe_unretained id receiver;

o   __unsafe_unretained Class super_class;

o };

第一个成员是 receiver, 类似于上面的 objc_msgSend函数第一个参数self

第二个成员是记录当前类的父类是什么,告诉程序从父类中开始找方法,找到方法后,最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class))去调用, 此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son

objc Runtime开源代码对- (Class)class方法的实现

o -(Class)class {

o   return object_getClass(self);

o }

runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)

每一个类对象中都一个对象方法列表(对象方法缓存)

类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)

方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.

当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找

当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找

objc中的类方法和实例方法有什么本质区别和联系

类方法:

类方法是属于类对象的

类方法只能通过类对象调用

类方法中的self是类对象

类方法可以调用其他的类方法

类方法中不能访问成员变量

类方法中不能直接调用对象方法

类方法是存储在元类对象的方法缓存中

实例方法:

实例方法是属于实例对象的

实例方法只能通过实例对象调用

实例方法中的self是实例对象

实例方法中可以访问成员变量

实例方法中直接调用实例方法

实例方法中可以调用类方法(通过类名)

实例方法是存放在类对象的方法缓存中

使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

无论在MRC下还是ARC下均不需要

被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放

补充:对象的内存销毁时间表,分四个步骤

1.调用 -release :引用计数变为零

 * 对象正在被销毁,生命周期即将结束.

 * 不能再有新的 __weak 弱引用,否则将指向 nil.

 * 调用 [self dealloc]

2. 父类调用 -dealloc

 * 继承关系中最直接继承的父类再调用 -dealloc

 * 如果是 MRC 代码 则会手动释放实例变量们(iVars)

 * 继承关系中每一层的父类 都再调用 -dealloc

3. NSObject 调 -dealloc

 * 只做一件事:调用 Objective-C runtime 中的 object_dispose() 方法

4. 调用 object_dispose()

 * 为 C++ 的实例变量们(iVars)调用 destructors

 * 为 ARC 状态下的 实例变量们(iVars) 调用 -release

 * 解除所有使用 runtime Associate方法关联的对象

 * 解除所有 __weak 引用

 * 调用 free()

_objc_msgForward函数是做什么的?直接调用它将会发生什么?

_objc_msgForward是IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发

直接调用_objc_msgForward是非常危险的事,这是把双刃刀,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事

JSPatch就是直接调用_objc_msgForward来实现其核心功能的

详细解说参见这里的第一个问题解答

能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

不能向编译后得到的类中增加实例变量;

能向运行时创建的类中添加实例变量;

分析如下:

因为编译后的类已经注册在runtime中,类结构体中的objc_ivar_list 实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime 会调用class_setIvarLayout 或 class_setWeakIvarLayout来处理strong weak引用,所以不能向存在的类中添加实例变量

运行时创建的类是可以添加实例变量,调用 class_addIvar函数,但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。

runloop和线程有什么关系?

每条线程都有唯一的一个RunLoop对象与之对应的

主线程的RunLoop是自动创建并启动

子线程的RunLoop需要手动创建

子线程的RunLoop创建步骤如下:

在子线程中调用[NSRunLoop currentRunLoop]创建RunLoop对象(懒加载,只创建一次)

获得RunLoop对象后要调用run方法来启动一个运行循环

o // 启动RunLoop

o [[NSRunLoop currentRunLoop] run];

RunLoop的其他启动方法

o // 第一个参数:指定运行模式

o // 第二个参数:指定RunLoop的过期时间,即:到了这个时间后RunLoop就失效了

o [[NSRunLoop currentRunLoop] runMode:kCFRunLoopDefaultMode beforeDate:[NSDate distantFuture]];

runloopmode作用是什么?

用来控制一些特殊操作只能在指定模式下运行,一般可以通过指定操作的运行mode来控制执行时机,以提高用户体验

系统默认注册了5个Mode

kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行,对应OC中的:NSDefaultRunLoopMode

UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响

kCFRunLoopCommonModes:这是一个标记Mode,不是一种真正的Mode,事件可以运行在所有标有common modes标记的模式中,对应OC中的NSRunLoopCommonModes,带有common modes标记的模式有:UITrackingRunLoopMode和kCFRunLoopDefaultMode

UIInitializationRunLoopMode:在启动 App时进入的第一个 Mode,启动完成后就不再使用

GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到

+scheduledTimerWithTimeInterval...的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?

这里强调一点:在主线程中以+scheduledTimerWithTimeInterval...的方式触发的timer默认是运行在NSDefaultRunLoopMode模式下的,当滑动页面上的列表时,进入了UITrackingRunLoopMode模式,这时候timer就会停止

可以修改timer的运行模式为NSRunLoopCommonModes,这样定时器就可以一直运行了

以下是我的笔记补充:

在子线程中通过scheduledTimerWithTimeInterval:...方法来构建NSTimer

方法内部已经创建NSTimer对象,并加入到RunLoop中,运行模式为NSDefaultRunLoopMode

由于Mode有timer对象,所以RunLoop就开始监听定时器事件了,从而开始进入运行循环

这个方法仅仅是创建RunLoop对象,并不会主动启动RunLoop,需要再调用run方法来启动

如果在主线程中通过scheduledTimerWithTimeInterval:...方法来构建NSTimer,就不需要主动启动RunLoop对象,因为主线程的RunLoop对象在程序运行起来就已经被启动了

o // userInfo参数:用来给NSTimer的userInfo属性赋值,userInfo是只读的,只能在构建NSTimer对象时赋值

o [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(run:) userInfo:@"ya了个hoo" repeats:YES];

o  

o // scheduledTimer...方法创建出来NSTimer虽然已经指定了默认模式,但是【允许你修改模式】

o [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

o  

o // 【仅在子线程】需要手动启动RunLoop对象,进入运行循环

o [[NSRunLoop currentRunLoop] run];

猜想runloop内部是如何实现的?

从字面意思看:运行循环、跑圈;

本质:内部就是do-while循环,在这个循环内部不断地处理各种事件(任务),比如:Source、Timer、Observer;

每条线程都有唯一一个RunLoop对象与之对应,主线程的RunLoop默认已经启动,子线程的RunLoop需要手动启动;

每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode,如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入,这样做主要是为了隔离不同Mode中的Source、Timer、Observer,让其互不影响;

附上RunLoop的运行图

不手动指定autoreleasepool的前提下,一个autorealese对象在什么时刻释放?(比如在一个vcviewDidLoad中创建)

分两种情况:手动干预释放时机、系统自动去释放

手动干预释放时机:指定autoreleasepool就是所谓的:当前作用域大括号结束时就立即释放

系统自动去释放:不手动指定autoreleasepool,Autorelease对象会在当前的 runloop 迭代结束时释放,下面详细说明释放时机

RunLoop中的三个状态会处理自动释放池,通过打印代码发现有两个Observer监听到状态值为:1和160(32+128)

kCFRunLoopEntry(1)// 第一次进入会创建一个自动释放池

kCFRunLoopBeforeWaiting(32)// 进入休眠状态前先销毁自动释放池,再创建一个新的自动释放池

kCFRunLoopExit(128)// 退出RunLoop时销毁最后一次创建的自动释放池

如果在一个vc的viewDidLoad中创建一个Autorelease对象,那么该对象会在 viewDidAppear 方法执行前就被销毁了(是这样的吗???)

苹果是如何实现autoreleasepool的?

autoreleasepool以一个队列数组的形式实现,主要通过下列三个函数完成.

objc_autoreleasepoolPush

objc_autoreleasepoolPop

objc_aurorelease

看函数名就可以知道,对autorelease分别执行push,和pop操作。销毁对象时执行release操作

GCD的队列(dispatch_queue_t)分哪两种类型?背后的线程模型是什么样的?

串行队列

并行队列

dispatch_global_queue();是全局并发队列

dispatch_main_queue();是一种特殊串行队列

背后的线程模型:自定义队列 dispatch_queue_t queue; 可以自定义是并行:DISPATCH_QUEUE_CONCURRENT 或者 串行DISPATCH_QUEUE_SERIAL

苹果为什么要废弃dispatch_get_current_queue

容易误用造成死锁

如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)

必须是并发队列才起作用

需求分析

首先,分别异步执行2个耗时的操作

其次,等2个异步操作都执行完毕后,再回到主线程执行一些操作

使用队列组实现上面的需求

// 创建队列组

dispatch_group_t group = dispatch_group_create();

// 获取全局并发队列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// 往队列组中添加耗时操作

dispatch_group_async(group, queue, ^{

 // 执行耗时的异步操作1

});

// 往队列组中添加耗时操作

dispatch_group_async(group, queue, ^{

 // 执行耗时的异步操作2

});

// 当并发队列组中的任务执行完毕后才会执行这里的代码

dispatch_group_notify(group, queue, ^{

 // 如果这里还有基于上面两个任务的结果继续执行一些代码,建议还是放到子线程中,等代码执行完毕后在回到主线程

 // 回到主线程

 dispatch_async(group, dispatch_get_main_queue(), ^{

 // 执行相关代码...

 });

});

dispatch_barrier_async的作用是什么?

函数定义

dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

必须是并发队列,要是串行队列,这个函数就没啥意义了

注意:这个函数的第一个参数queue不能是全局的并发队列

作用:在它前面的任务执行结束后它才执行,在它后面的任务等它执行完成后才会执

示例代码

-(void)barrier

{

 dispatch_queue_t queue = dispatch_queue_create("12342234", DISPATCH_QUEUE_CONCURRENT);

 dispatch_async(queue, ^{

 NSLog(@"----1-----%@", [NSThread currentThread]);

 });

 dispatch_async(queue, ^{

 NSLog(@"----2-----%@", [NSThread currentThread]);

 });

 // 在它前面的任务执行结束后它才执行,在它后面的任务等它执行完成后才会执行

 dispatch_barrier_async(queue, ^{

 NSLog(@"----barrier-----%@", [NSThread currentThread]);

 });

 dispatch_async(queue, ^{

 NSLog(@"----3-----%@", [NSThread currentThread]);

 });

 dispatch_async(queue, ^{

 NSLog(@"----4-----%@", [NSThread currentThread]);

 });

}

以下代码运行结果如何?

- (void)viewDidLoad

{

 [super viewDidLoad];

 NSLog(@"1");

 dispatch_sync(dispatch_get_main_queue(), ^{

 NSLog(@"2");

 });

 NSLog(@"3");

}

答案:主线程死锁

lldbgdb)常用的调试命令?

po:打印对象,会调用对象description方法。是print-object的简写

expr:可以在调试时动态执行指定表达式,并将结果打印出来,很有用的命令

print:也是打印命令,需要指定类型

bt:打印调用堆栈,是thread backtrace的简写,加all可打印所有thread的堆栈

br l:是breakpoint list的简写

BAD_ACCESS在什么情况下出现?

访问一个僵尸对象,访问僵尸对象的成员变量或者向其发消息

死循环

如何调试BAD_ACCESS错误

设置全局断点快速定位问题代码所在行

开启僵尸对象调试功能

简述下Objective-C中调用方法的过程(runtime

Objective-C是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector),整个过程介绍如下:

objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类

然后在该类中的方法列表以及其父类方法列表中寻找方法运行

如果,在最顶层的父类(一般也就NSObject)中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX

但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会,这三次拯救程序奔溃的说明见问题《什么时候会报unrecognized selector的异常》中的说明

补充说明:Runtime 铸就了Objective-C 是动态语言的特性,使得C语言具备了面向对象的特性,在程序运行期创建,检查,修改类、对象及其对应的方法,这些操作都可以使用runtime中的对应方法实现。

什么是method swizzling(俗称黑魔法)

简单说就是进行方法交换

在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的

每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP

交换方法的几种实现方式

利用 method_exchangeImplementations 交换两个方法的实现

利用 class_replaceMethod 替换方法的实现

利用 method_setImplementation 来直接设置某个方法的IMP

objc中向一个nil对象发送消息将会发生什么?

在Objective-C中向nil发送消息是完全有效的——只是在运行时不会有任何作用

如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)

如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*)

float,double,long double 或者long long的整型标量,发送给nil的消息将返回0

如果方法返回值为结构体,发送给nil的消息将返回0。结构体中各个字段的值将都是0

如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的

具体原因分析

objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)

为了方便理解这个内容,还是贴一个objc的源代码

o struct objc_class

o {

o // isa指针指向Meta Class,因为Objc的类的本身也是一个Object,

o // 为了处理这个关系,runtime就创造了Meta Class,

o // 当给类发送[NSObject alloc]这样消息时,实际上是把这个消息发给了Class Object

o Class isa OBJC_ISA_AVAILABILITY;

o #if !__OBJC2__

o Class super_class OBJC2_UNAVAILABLE; // 父类

o const char *name OBJC2_UNAVAILABLE; // 类名

o long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0

o long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识

o long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小

o struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表

o struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表

o // 方法缓存,对象接到一个消息会根据isa指针查找消息对象,

o // 这时会在method Lists中遍历,

o // 如果cache了,常用的方法调用时就能够提高调用的效率。

o // 这个方法缓存只存在一份,不是每个类的实例对象都有一个方法缓存

o // 子类会在自己的方法缓存中缓存父类的方法,父类在自己的方法缓存中也会缓存自己的方法,而不是说子类就不缓存父类方法了

o struct objc_cache *cache OBJC2_UNAVAILABLE;

o struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表

o #endif

o } OBJC2_UNAVAILABLE;

objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后再发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。

如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误

objc中向一个对象发送消息[obj foo]objc_msgSend()函数之间有什么关系?

[obj foo];在objc动态编译时,会被转意为:objc_msgSend(obj, @selector(foo));

什么时候会报unrecognized selector的异常?

当调用该对象上某个方法,而该对象上没有实现这个方法的时候, 可以通过“消息转发”进行解决,如果还是不行就会报unrecognized selector异常

objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector),整个过程介绍如下:

objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类

然后在该类中的方法列表以及其父类方法列表中寻找方法运行

如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会

三次拯救程序崩溃的机会

Method resolution

objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。

如果你添加了函数并返回 YES,那运行时系统就会重新启动一次消息发送的过程

如果 resolve 方法返回 NO ,运行时就会移到下一步,消息转发

Fast forwarding

如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会

只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。

否则,就会继续Normal Fowarding。

这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但Normal forwarding转发会创建一个NSInvocation对象,相对Normal forwarding转发更快点,所以这里叫Fast forwarding

Normal forwarding

这一步是Runtime最后一次给你挽救的机会。

首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。

如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。

如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象

HTTP协议中POST方法和GET方法有那些区别?

GET用于向服务器请求数据,POST用于提交数据

GET请求,请求参数拼接形式暴露在地址栏,而POST请求参数则放在请求体里面,因此GET请求不适合用于验证密码等操作

GET请求的URL有长度限制,POST请求不会有长度限制

使用block时什么情况会发生引用循环,如何解决?

block内如何修改block外部变量?

使用系统的某些block api(如UIViewblock版本写动画时),是否也考虑循环引用问题?

系统的某些block api中,UIView的block版本写动画时不需要考虑,但也有一些api 需要考虑

以下这些使用方式不会引起循环引用的问题

[UIView animateWithDuration:duration animations:^

{ [self.superview layoutIfNeeded]; }];

[[NSOperationQueue mainQueue] addOperationWithBlock:^

{ self.someProperty = xyz; }];

[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"

 object:nil

 queue:[NSOperationQueue mainQueue]

 usingBlock:^(NSNotification * notification)

 { self.someProperty = xyz; }];

但如果方法中的一些参数是 成员变量,那么可以造成循环引用,如 GCD 、NSNotificationCenter调用就要小心一点,比如 GCD 内部如果引用了 self,而且 GCD 的参数是 成员变量,则要考虑到循环引用,举例如下:

GCD

分析:self-->_operationsQueue-->block-->self形成闭环,就造成了循环引用

§ __weak __typeof__(self) weakSelf = self;

§ dispatch_group_async(_operationsGroup, _operationsQueue, ^

§ {

§ [weakSelf doSomething];

§ [weakSelf doSomethingElse];

§ } );

NSNotificationCenter

分析:self-->_observer-->block-->self形成闭环,就造成了循环引用

o __weak __typeof__(self) weakSelf = self;

o _observer = [[NSNotificationCenter defaultCenter]

o addObserverForName:@"testKey"

o object:nil

o queue:nil

o usingBlock:^(NSNotification *note){

o     [weakSelf dismissModalViewControllerAnimated:YES];

o }];

OC中常见的循环引用总结

IOS 常见的循环引用总结

介绍:

循环引用,指的是多个对象相互引用时,使得引用形成一个环形,导致外部无法真正是否掉这块环形内存。其实有点类似死锁。

举个例子:A->B->C->....->X->B   ->表示强引用,这样的B的引用计数就是2,假如A被系统释放了,理论上A会自动减小A所引用的资源,就是B,那么这时候B的引用计数就变成了1,所有B无法被释放,然而A已经被释放了,所有B的内存部分就肯定无法再释放再重新利用这部分内存空间了,导致内存泄漏。

情况一:delegate

Delegate是ios中开发中最常遇到的循环引用,一般在声明delegate的时候都要使用弱引用weak或者assign

?

1@property (nonatomic, weak, nullable) id delegate;

当然怎么选择使用assign还是weak,MRC的话只能用assign,在ARC的情况下最好使用weak,因为weak修饰的变量在是否后自动为指向nil,防止不安全的野指针存在

情况二:Block

Block也是比较常见的循环引用问题,在Block中使用了self容易出现循环引用,因此很多人在使用block的时候,加入里面有用到self的操作都会声明一个__weak来修饰self。其实便不是这样的,不是所有使用了Block都会出现Self循环引用问题,只有self拥有Block的强引用才会出现这种情况。

所以一般在函数中临时使用Block是不会出现循环应用的,因为这时候Block引用是属于栈的。当栈上的block释放后,block中对self的引用计数也会减掉

当然不一定要Self对Block有直接的引用才会出现,假如self的变量B,B中有个Block变量,就容易出现这种情况,好的是在block出现循环引用的,xcode7会出现警告提示(之前版本不确定)。

情况三:NSTimer

这是一个神奇的NSTimer,当你创建使用NSTimer的时候,NSTimer会默认对当前self有个强引用,所有在self使用完成打算是否的时候,一定要先使用NSTimer的invalidate来停止是否时间控制对self的引用

?

1[_timer invalidate];

反转链表

方法1:将单链表储存为数组,然后按照数组的索引逆序进行反转。

方法2:使用3个指针遍历单链表,逐个链接点进行反转。

方法3:从第2个节点到第N个节点,依次逐节点插入到第1个节点(head节点)之后,最后将第一个节点挪到新表的表尾。

方法4: 递归(相信我们都熟悉的一点是,对于树的大部分问题,基本可以考虑用递归来解决。但是我们不太熟悉的一点是,对于单链表的一些问题,也可以使用递归。可以认为单链表是一颗永远只有左(右)子树的树,因此可以考虑用递归来解决。或者说,因为单链表本身的结构也有自相似的特点,所以可以考虑用递归来解决)

二分查找法

· 存储在数组中

· 有序排列

冒泡排序

数据结构(链表、二叉树、算法时间复杂度、空间复杂度)

排序法最差时间分析平均时间复杂度稳定度空间复杂度

冒泡排序O(n2)O(n2)稳定O(1)

快速排序O(n2)O(n*log2n)不稳定O(log2n)~O(n)

选择排序O(n2)O(n2)稳定O(1)

二叉树排序O(n2)O(n*log2n)不一顶O(n)

插入排序O(n2)O(n2)稳定O(1)

堆排序O(n*log2n)O(n*log2n)不稳定O(1)

希尔排序OO不稳定O(1)

什么是二叉搜索树?时间复杂度是什么?

采用二叉树链表作为存储结构,每个左节点均小于父节点,每个右节点均大于父节点,用中序遍历可得到顺序

O(log2(n))

T9算法如何实现全拼算法

最短路径算法

强连通量算法

实现连连看算法

MVC里面, View怎么通知到Model

由controller 改变model的属性,model只要发现自已属性有改变,就会发出事件广播,通知所有监听自已此属性的view,view收到通知后,会update自

如何实现一个数组每个元素依次向右移动k位。(后头的往前面补比如: [1, 2, 3, 4, 5] 挪两位变成[4, 5, 1, 2, 3]

求两个整数的最大公约数

微信用户都是双向的好友,ab的好友,那么b一定是a的。给定一个用户列表,有些用户是好友,有些不是,请判断,这些用户是否可以划分为两组,每组内的用 户,互相都不是好友。如果能,请给出这个划分

算法题:说 预约会议室,会有n个团队预约当天会议室,时间各不相同,求最少需要几个会议室。比如:1预约的时间是[9-11], 2预约的时间是[10-12], 3预约的时间是[12-14], 此时会议最小个数是2

常用的设计模式

单例模式

组合模式

观察者模式

代理模式

享元模式

工厂方法模式

抽象工厂模式

MVC的理解

数据管理者(M)、数据展示者(V)、数据加工者(C)

M应该做的事:

给ViewController提供数据

给ViewController存储数据提供接口

提供经过抽象的业务基本组件,供Controller调度

C应该做的事:

管理View Container的生命周期

负责生成所有的View实例,并放入View Container

监听来自View与业务有关的事件,通过与Model的合作,来完成对应事件的业务。

V应该做的事:

响应与业务无关的事件,并因此引发动画效果,点击反馈(如果合适的话,尽量还是放在View去做)等。

界面元素表达

MVC 和 MVVM 的区别

MVVM是对胖模型进行的拆分,其本质是给控制器减负,将一些弱业务逻辑放到VM中处理

MVC是一切设计的基础,所有新的设计模式都是基于MVC进行的改进

补充:常见的设计模式有:MVC、MVCS、MVVM、viper

TCP和UDP有什么区别?

TCP是面向连接的,建立连接需要经历三次握手,保证数据正确性和数据顺序

UDP是非连接的协议,传送数据受生成速度,传输带宽等限制,可能造成丢包

UDP一台服务端可以同时向多个客户端传输信息

TCP报头体积更大,对系统资源要求更多

TCP的三次握手

第一次握手:客户端发送syn包到服务器,并进入syn_send状态,等待服务器进行确认;

第二次握手:服务器收到客户端的syn包,必须确认客户的SYN,同时自己也发送一个SYN包,即SYN + ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户收到服务器发送的SYN+ACK包之后,向服务器发送确认包,此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成第三次握手。

如何制作一个静态库/动态库?他们的区别是什么?

Xcode6支持制作静态库/动态库 framework

无论是动态库还是静态库都是区分真机和模拟器的

静态库编译静态库文件装入程序空间,动态库是文件动态装入内存

动态库执行到相关函数才会被调用,节省空间

苹果一般不允许第三方动态库,APP容易被拒

- 一个lib包含了很多的架构,会打到最后的包里么?

不会,如果lib中有armv7, armv7s, arm64, i386,x86_64架构,而target architecture选择了armv7s,arm64,那么只会从lib中link指定的这两个架构的二进制代码,其他架构下的代码不会link到最终可执行文件中;反过来,一个lib需要在模拟器环境中正常link,也得包含i386架构的指令

每一个设备都有属于自己的CPU架构

每一个静态支持的架构是固定

模拟器

4s-->5 : i386

5s-->6plus : x86_64

真机

3gs-->4s : armv7

5/5c : armv7s,静态库只要支持了armv7,就可以跑在armv7s的架构上

5s-->6plus : arm64

常用命令总结:

// 使用lipo -info命令,查看指定库支持的架构,比如UIKit框架

lipo -info UIKit.framework/UIKit

// 想看的更详细的信息可以使用lipo -detailed_info

lipo -detailed_info UIKit.framework/UIKit

// 还可以使用file命令

file UIKit.framework/UIKit

// 合并MyLib-32.a和MyLib-64.a,可以使用lipo -create命令合并

lipo -create MyLib-32.a MyLib-64.a -output MyLib.a

支持64-bit后程序包会变大么?

会,支持64-bit后,多了一个arm64架构,理论上每个架构一套指令,但相比原来会大多少还不好说

用过Core Data 或者 SQLite吗?读写是分线程的吗?遇到过死锁没?如何解决的?

用过SQLite,使用FMDB框架

丢给FMDatabaseQueue 或者 添加互斥锁(NSLock/@synchronized(锁对象))

请简单的介绍下APNS发送系统消息的机制

APNS优势:杜绝了类似安卓那种为了接受通知不停在后台唤醒程序保持长连接的行为,由iOS系统和APNS进行长连接替代

APNS的原理:

应用在通知中心注册,由iOS系统向APNS请求返回设备令牌(device Token)

应用程序接收到设备令牌并发送给自己的后台服务器

服务器把要推送的内容和设备发送给APNS

APNS根据设备令牌找到设备,再由iOS根据APPID把推送内容展示

不用中间变量,用两种方法交换A和B的值

方法1:

·        A = A + B

·        B = A - B

·        A = A - B

方法2:异或

·        A = A^B;

·        B = A^B;

·        A = A^B;

开发常用的工具有哪些?

你一般是怎么用 Instruments 的?

你一般是如何调试 Bug 的?

如何实现单例,单例会有什么弊端?

节省内存资源,一个应用就一个对象

APP上架后如何搜集错误信息?

简答描述下你所理解的敏捷开发

如何应对APP版本升级,数据结构随之变化?

常用的设计模式

单例会有什么弊端?

编程题:简述「Snakes and Ladders」的实现思路

什么时候会使用 Core Graphics,有什么注意事项么?

你会如何存储用户的一些敏感信息,如登录的 token

iOS Extension 是什么?能列举几个常用的 Extension 么?

Apple Pay 是什么?它的大概工作流程是怎样的?

iOS 的签名机制大概是怎样的?

iOS 7的多任务添加了哪两个新的 API? 各自的使用场景是什么?

UIScrollView 大概是如何实现的,它是如何捕捉、响应手势的?

NSOperation 相比于 GCD 有哪些优势?

如何为 Class 定义一个对外只读对内可读写的属性?

+[UIView animateWithDuration:animations:completion:] 内部大概是如何实现的?

什么时候会发生「隐式动画」?

Toll-Free Bridging 是什么?什么情况下会使用?

如何实现无图模式?(在Wifi下一定加载图片,在3G下如果开启无图模式,不显示不在缓存中的图片)

这个我估计也就是做个网络环境的判断,然后如果是3G(开启了无图模式)环境下且图片又不在缓存中就使用占位图片

iOS项目的持续集成怎么做?

frame 与 center bounds的关系,frame 和 bounds 的宽高一直相等吗?

直接调用_objc_msgForward函数将会发生什么?

通知中心的实现原理?

如何关闭默认的KVO的默认实现,并进入自定义的KVO实现?

断点续传如何实现的?

通知,代理,KVO的区别,以及通知的多线程问题

JSON 转对象的时候,一个NSString的属性,如果后台返回对应这个属性的类型不是NSString,而是其他的数据类型,怎么办?

说说iOS7之后, 蓝牙的围栏功能

无线滚动

如何实现类似 “Find My iPhone” 这样功能,这个是咱实现的呢?

UIWebView 有哪些性能问题?有没有可替代的方案?

为什么 NotificationCenter 要 removeObserver? 如何实现自动 remove?

深度遍历和广度遍历使用场景

如何保证软件质量, 怎么分析Crash日志

应用程序的启动过程

①.先加载Main函数

②.在Main函数里的 UIApplicationMain方法中,创建Application对象 创建Application的Delegate对象

③.创建主循环,代理对象开始监听事件

④.启动完毕会调用 didFinishLaunching方法,并在这个方法中创建UIWindow

⑤.设置UIWindow的根控制器是谁

⑥.如果有storyboard,会根据info.plist中找到应用程序的入口storyboard并加载箭头所指的控制器

⑦.显示窗口

AppDelegate中的:

1.application:didFinishLaunchingWithOptions:

2.applicationDidBecomeActive:

ViewController中的:

3.loadView

4.viewDidLoad

5.load

6.initialize

7.viewWillAppear

8.viewWillLayoutSubviews

9.viewDidLayoutSubviews

10.viewDidAppear

MainView(控制器的View)中的:

11.initWithCoder(如果没有storyboard就会调用initWithFrame,这里两种方法视为一种)

12.awakeFromNib

13.layoutSubviews

14.drawRect

ChildView(子控件View)中的:

15.initWithCoder(如果没有storyboard就会调用initWithFrame,这里两种方法视为一种)

16.awakeFromNib

17.layoutSubviews

18.drawRect


下面就是对各个方法的整理

+ (void)load;

1.这是应用程序启动就会调用的方法,在这个方法里写的代码最先调用

+ (void)initialize;

2.这个是需要用到本类时才调用,这个方法里一般写 设置导航控制器的主题啊之类的,如果在后面的方法设置导航栏主题就晚了!(当然在上面的方法里也能写)

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions;

3.这个方法里面会创建UIWindow,设置根控制器并展现,比如某些应用程序要加载授权页面也是在这加,也可以设置观察者,监听到通知切换根控制器

ChildView - (instancetype)initWithCoder:(NSCoder*)aDecoder;

4.这里反正我是万万没想到,childView的initwithcoder会在MainView的方法之前调用,父的都还没出来,就先整子控件?有了解比较透彻的博友恳请告诉我谢谢。

MainView - (instancetype)initWithCoder:(NSCoder*)aDecoder;

5.就是关于应用程序的数据存储后的解档操作。

MainView - (void)awakeFromNib;

6.在这个方法里设置view的背景等一系列普通操作,不要写关于frame的还不准,在使用IB的时候才会涉及到此方法的使用,当.nib文件被加载的时候,会发送一个awakeFromNib的消息到.nib文件中的每个对象,每个对象都可以定义自己的awakeFromNib函数来响应这个消息,执行一些必要的操作。

ChildView - (void)awakeFromNib

7.子控件也有本方法,重写父类的方法。基本用法同上

- (void)loadView;

8.创建视图的层次结构,这里需要注意,在没有创建控制器的view的情况下不能直接写 self.view 因为self.view的底层是:

if(_view == nil){

_view = [self loadView]

}

所以这么写会直接造成死循环。

如果重写这个loadView方法里面什么都不写,会显示黑屏。

如果写了[super view]还要看前面的控制器在创建时是写的initWithNibName(指定了xib名字),还是写的普通的init。 如果是后者还是黑屏。

如果不在这个方法中,init的底层是会调用initWithNibName的,如果名字是MainViewController,会先在项目中找MainView.xib 找不到会再找MainViewController.xib。 - (void)viewDidLoad;

9.卧槽,这个方法是当年用的最多的方法,但是在之后的开发中就会发现越来越不靠谱,很多东西都还没加载完毕,各种取值都不准确,很少在这里面写东西了。 这里只是把视图元件加载完成,还没有开始布局不要设置关于 frame 之类的属性!有时可能会出现差20个像素点等状况。

- (void)viewWillAppear:(BOOL)animated;

10.视图将要出现,这个方法用的非常多,比如如果要设置导航栏的setNavigationBarHiden:animate:就必须要在这里写,才能完美契合,不卡跳。 还有很多比如监听屏幕旋转啦,

viewWillTransitionToSize:可能要在本方法里再调一次,或者就是新到这个界面要reloadData或是自动下拉刷新等 都是写在本方法里。

- (void)viewWillLayoutSubviews;

11.视图将要布局子视图,苹果建议的设置界面布局属性的方法,这个方法和viewWillAppear里,系统的底层都是没有写任何代码的,也就是说这里面不写super 也是可以的

MainView  - (void)layoutSubviews;

12.在这个方法里一般设置子控件的frame,因为这里相当于是布局基本完成了,设置时取到的frame或者是self.bounds才最准,如果在awakeFromeNib里写会不准确 。还有这里要切记千万不能把super layoutSubviews忘了,可能最后都很难找到这个bug

- (void)viewDidLayoutSubviews;

13.这个方法我也是玩玩没想到,控制器的view的子控件还没有布局好呢,怎么这个控制器就已经说布局全部完成了?那后边的布局就不等了?有独到见解的也恳请你告诉我,这其中苹果的意思到底是什么。

ChildView - (void)layoutSubviews;

14.控制器的子控件里的子控件的布局就在这里写了。

MainView - (void)drawRect:(CGRect)rect;

15. 因为默认所有额UI控件都是画上去的,在这一步就是把所有的东西画上去,有时候需要用到Quartz2D的知识的时候都是在这个方法里话,但也是要注意别忘了写super,不然系统原本的东西就都画不上来了,这里要建议尽可能使用贝塞尔路径画图形,因为系统默认的那个上下文画法有时可能会内存泄露。drawRect方法只能在加载时调用一次,如果后面还需要调用,比如下载进度的圆弧,需要一直刷帧,就要使用setNeedsDisplay来定时多次调用本方法

ChildView - (void)drawRect:(CGRect)rect;

16.view的子控件内部的画图方法,有时可以自己自定义label 中间带个删除线的(用来写打折前的原价) 就是在这里画根线 。

- (void)viewDidAppear:(BOOL)animated;

17.把上面的画图都画完了,这里就会显示,视图完全加载完成。在这里的操作可能就是设置页面的一些动画,或者是设置tableView,collectionView,QQ聊天页面啥的滚动到底部scrollToIndexPath之类的代码操作。

- (void)applicationDidBecomeActive:(UIApplication *)application;

18.最后这是AppDelegate的应用程序获取焦点方法,真正到了这里,才是所有东西全部加载完毕,应用程序整装待发保持最佳状态等待用户操作。这个方法中一般会写关于弹出键盘的方法,比如有的用户登录界面为了更好的用户体验,就让你在刚打开程序来到登录界面的时候,光标的焦点就自动在账号的文本框里闪烁,也就是设置账号文本框为第一响应者。键盘在页面加载完毕后从下方弹出,这种代码一般就在本方法写。

TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接,如图1所示。

(1)第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。

(2)第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。

(3)第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。

完成三次握手,客户端与服务器开始传送数据。

确认号:其数值等于发送方的发送序号 +1(即接收方期望接收的下一个序列号)

参考答案:

声明私有变量可以通过@private关键字来声明。例如,这样就是私有的成员变量了:

 @interface HYBTestModel : NSObject { @private NSString *_userName; } @end

没有关键字声明为私有方法,因为ObjC中也没有真正意义上的私有方法。我们要让方法成员私有,只能通过放在.m文件中定义而不暴露在外部。但是,如果有人知道内部此这么一个方法,那么也是可以访问的。

先说明:ObjC中没有绝对的私有变量和私有方法。

如何修改私有成员变量的值?

 HYBTestModel *model = [[HYBTestModel alloc] init]; // 通过KVC可以轻松修改私有成员变量 // 自己加一个打印就可以看到有值了! [model setValue:@"修改私有变量的值" forKey:@"_userName"];

那又如何访问私有成员变量?

1 2 3 4Ivar userNameIvar = class_getInstanceVariable([model class], "_userName"); NSString *userName = object_getIvar(model, userNameIvar);

我们可以通过runtime来获取对象的成员变量Ivar,然后再通过object_getIvar来获取某个对象的成员变量的值。

看到这里,还相信ObjC中所谓私有变量吗?

1 2 3@property (nonatomic, retain) NSNumber *num;

参考答案:

从题目可知这问的是MRC下的问题。在MRC下:

assign用于非对象类型,对于对象类型的只用于弱引用。retain用于对象类型,强引用对象copy用于对象类型,强引用对象。

重写setter/getter(如何重写getter和setter,是不会自动登录_num成员变量的,需要自己手动声明):

 - (NSNumber *)num { return _num; } - (void)setNum:(NSNumber *)aNum { if (_num != aNum) { [_num release]; _num = nil; _num = [aNum retain]; } }

参考答案:

声明属性时要,在ARC下使用weak,在MRC下使用assign。比如:

 @property (nonatomic, weak) id delegate;

在MRC下,使用assign是因为没有weak关键字,只能使用assign来防止循环引用。在ARC下,使用weak来防止循环引用。

参考答案:

如果了解一点点Run Loop的知道,应该了解到:Run Loop在每个事件循环结束后会去自动释放池将所有自动释放对象的引用计数减一,若引用计数变成了0,则会将对象真正销毁掉,回收内存。

所以,autorelease的对象是在每个事件循环结束后,自动释放池才会对所有自动释放的对象的引用计数减一,若引用计数变成了0,则释放对象,回收内存。因此,若想要早一点释放掉auto release对象,那么我们可以在对象外加一个自动释放池。比如,在循环处理数据时,临时变量要快速释放,就应该采用这种方式:

 for (int i = 0; i < 10000000; ++i) { @autoreleasepool { HYBTestModel *tempModel = [[HYBTestModel alloc] init]; // 临时处理 // ... } // 出了这里,就会去遍历该自动释放池了 }

 for (int i = 0; i < 10000; ++i) { NSString *str = @"Abc"; str = [str lowercaseString]; str = [str stringByAppendingString:@"xyz"]; NSLog(@"%@", str); }

参考答案:

这道题从语法上看没有任何问题的,当然,既然面试官出了这一道题,那肯定是有问题的。

问题出在哪里呢?语法没有错啊?内存最后也可以得到释放啊!为什么会有问题呢?是的,问题是挺大的。这对于不了解iOS的自动释放池的原理的人或者说内存管理的人来说,这根本看不出来这有什么问题。

问题就出在内存得不到及时地释放。为什么得不到及时地释放?因为Run Loop是在每个事件循环结束后才会自动释放池去使对象的引用计数减一,对于引用计数为0的对象才会真正被销毁、回收内存。

因此,对于这里的问题,一个for循环执行10000次,会产生10000个临时自动番话对象,一直放到自动释放池中管理,内存得不到回收。

然后,现象是内存暴涨。正确的写法:

 for (int i = 0; i < 10000; ++i) { @autoreleasepool { NSString *str = @"Abc"; str = [str lowercaseString]; str = [str stringByAppendingString:@"xyz"]; NSLog(@"%@", str); } }

参考答案:

第一个问题:

在控制器被销毁前会调用viewDidUnload(MRC下才会调用)在控制器没有任何view时,会调用loadView在view加载完成时,会调用viewDidLoad

第二个问题:

在调用setNeedsDisplay后,会调用drawRect方法,我们通过在此方法中可以获取到context(设置上下文),就可以实现绘图在调用setNeedsLayout后,会调用layoutSubviews方法,我们可以通过在此方法去调整UI。当然能引起layoutSubviews调用的方式有很多种的,比如添加子视图、滚动scrollview、修改视图的frame等。

参考答案:

对于自定义并发NSOperation,只需要实现main方法就可以了。对于自定义非并发NSOperation,需要重写main、start、isFinished、isExecuting,还要注意在相关地方加上kvo的代码,通知其它线程。

更多内容看这里:iOS NSOperation

参考答案:

不太清楚题目的语义,好像是说扩展类方法?通过category很容易做到,这里就不说了!

参考答案:

随手写一个吧:

 + (instancetype)sharedInstance { static id s_manager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ s_manager = [[HYBTestSingleton alloc] init]; }); return s_manager; }

参考答案:

冒泡算法的核心算法思想是每趟两两比较,将小的往上浮,大的往下沉,就像气泡一样从水底往水面浮。

 void bubbleSort(int a[], int len) { for (int i = 0; i < len - 1; ++i) { // 从水底往水面浮,所以从最后一个开始 for (int j = len - 1; j > i; j--) { // 后者比前者还小,将需要交换 if (a[j] < a[j - 1]) { int temp = a[j]; a[j] = a[j - 1]; a[j - 1] = temp; } } } }


参考答案:

UITableView提供了一个属性:visibleCells,它是记录当前在屏幕可见的cell,要想重用cell,我们需要明确指定重用标识(identifier)。

当cell滚动出tableview可视范围之外时,就会被放到可重用数组中。当有一个cell滚动出tableview可视范围之外时,同样也会有新的cell要显示到tableview可视区,因此这个新显示出来的cell就会先从可重用数组中通过所指定的identifier来获取,如果能够获取到,则直接使用之,否则创建一个新的cell。

参考答案:

要更高效地显示列表(不考虑种种优化),可以通过以下方法处理(只是部分):

提前根据数据计算好高度并缓存起来提前将数据处理、I/O计算异步处理好,并保存结果,在需要时直接拿来使用

参考答案:

这个问题三言两语讲不明白。简单来说,M对应于Model(数据层)、V对应于View(视图层)、C对应于Controller(控制器层)。

如下图:

用户在V上操作,需要通过C更新M,然后将新的M交到C,C让M更新。

参考答案:

KVC即是指NSKeyValueCoding,是一个非正式的Protocol,提供一种机制来间接访问对象的属性。KVO 就是基于KVC实现的关键技术之一。

KVO即Key-Value Observing,是建立在KVC之上,它能够观察一个对象的KVC key path值的变化。 当keypath对应的值发生变化时,会回调observeValueForKeyPath:ofObject:change:context:方法,我们可以在这里处理。

更详细的内容,请自行百度吧,现在笔者没有写相关文章!

参考答案:

笔者只说说我们在开发中真正常用到的设计模式(包括架构设计模式):

单例设计模式MVC构架设计模式工厂设计模式观察者设计模式(比如KVC/KVO/NSNotification,也有人说不是设计模式)代理设计模式

概述

代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实现匿名函数的特性,Block是一种特殊的数据类型,其可以正常定义变量、作为参数、作为返回值,特殊地,Block还可以保存一段代码,在需要的时候调用,目前Block已经广泛应用于iOS开发中,常用于GCD、动画、排序及各类回调

注: Block的声明与赋值只是保存了一段代码段,必须调用才能执行内部代码

Block变量的声明、赋值与调用

Block变量的声明

Block变量的声明格式为: 返回值类型(^Block名字)(参数列表);

// 声明一个无返回值,参数为两个字符串对象,叫做aBlock的Block

void(^aBlock)(NSString *x, NSString *y);

// 形参变量名称可以省略,只留有变量类型即可

void(^aBlock)(NSString *, NSString *);

注: ^被称作"脱字符"

Block变量的赋值

Block变量的赋值格式为: Block变量 = ^(参数列表){函数体};

aBlock = ^(NSString *x, NSString *y){

    NSLog(@"%@ love %@", x, y);

};

注: Block变量的赋值格式可以是: Block变量 = ^返回值类型(参数列表){函数体};,不过通常情况下都将返回值类型省略,因为编译器可以从存储代码块的变量中确定返回值的类型

声明Block变量的同时进行赋值

int(^myBlock)(int) = ^(int num){

    return num * 7;

};

// 如果没有参数列表,在赋值时参数列表可以省略

void(^aVoidBlock)() = ^{

    NSLog(@"I am a aVoidBlock");

};

Block变量的调用

// 调用后控制台输出"Li Lei love Han Meimei"

aBlock(@"Li Lei",@"Han Meimei");

// 调用后控制台输出"result = 63"

NSLog(@"result = %d", myBlock(9));

// 调用后控制台输出"I am a aVoidBlock"

aVoidBlock();

使用typedef定义Block类型

在实际使用Block的过程中,我们可能需要重复地声明多个相同返回值相同参数列表的Block变量,如果总是重复地编写一长串代码来声明变量会非常繁琐,所以我们可以使用typedef来定义Block类型

// 定义一种无返回值无参数列表的Block类型

typedef void(^SayHello)();

// 我们可以像OC中声明变量一样使用Block类型SayHello来声明变量

SayHello hello = ^(){

    NSLog(@"hello");

};

// 调用后控制台输出"hello"

hello();

Block作为函数参数

Block作为C函数参数

// 1.定义一个形参为Block的C函数

void useBlockForC(int(^aBlock)(int, int))

{

    NSLog(@"result = %d", aBlock(300,200));

}

// 2.声明并赋值定义一个Block变量

int(^addBlock)(int, int) = ^(int x, int y){

    return x+y;

};

// 3.以Block作为函数参数,把Block像对象一样传递

useBlockForC(addBlock);

// 将第2点和第3点合并一起,以内联定义的Block作为函数参数

useBlockForC(^(int x, int y) {

    return x+y;

});

Block作为OC函数参数

// 1.定义一个形参为Block的OC函数

- (void)useBlockForOC:(int(^)(int, int))aBlock

{

    NSLog(@"result = %d", aBlock(300,200));

}

// 2.声明并赋值定义一个Block变量

int(^addBlock)(int, int) = ^(int x, int y){

    return x+y;

};

// 3.以Block作为函数参数,把Block像对象一样传递

[self useBlockForOC:addBlock];

// 将第2点和第3点合并一起,以内联定义的Block作为函数参数

[self useBlockForOC:^(int x, int y){

    return x+y;

}];

使用typedef简化Block

// 1.使用typedef定义Block类型

typedef int(^MyBlock)(int, int);

// 2.定义一个形参为Block的OC函数

- (void)useBlockForOC:(MyBlock)aBlock

{

    NSLog(@"result = %d", aBlock(300,200));

}

// 3.声明并赋值定义一个Block变量

MyBlock addBlock = ^(int x, int y){

    return x+y;

};

// 4.以Block作为函数参数,把Block像对象一样传递

[self useBlockForOC:addBlock];

// 将第3点和第4点合并一起,以内联定义的Block作为函数参数

[self useBlockForOC:^(int x, int y){

    return x+y;

}];

Block内访问局部变量

在Block中可以访问局部变量

// 声明局部变量global

int global = 100;

void(^myBlock)() = ^{

    NSLog(@"global = %d", global);

};

// 调用后控制台输出"global = 100"

myBlock();

在声明Block之后、调用Block之前对局部变量进行修改,在调用Block时局部变量值是修改之前的旧值

// 声明局部变量global

int global = 100;

void(^myBlock)() = ^{

    NSLog(@"global = %d", global);

};

global = 101;

// 调用后控制台输出"global = 100"

myBlock();

在Block中不可以直接修改局部变量

// 声明局部变量global

int global = 100;

void(^myBlock)() = ^{

    global ++; //这句报错

    NSLog(@"global = %d", global);

};

// 调用后控制台输出"global = 100"

myBlock();

注: 原理解析,通过clang命令将OC转为C++代码来查看一下Block底层实现,clang命令使用方式为终端使用cd定位到main.m文件所在文件夹,然后利用clang -rewrite-objc main.m将OC转为C++,成功后在main.m同目录下会生成一个main.cpp文件

// OC代码如下

void(^myBlock)() = ^{

    NSLog(@"global = %d", global);

};

// 转为C++代码如下

void(*myBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, global));

// 将变量类型精简之后C++代码如下,我们发现Block变量实际上就是一个指向结构体__main_block_impl_0的指针,而结构体的第三个元素是局部变量global的值

void(*myBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, global);

// 我们看一下结构体__main_block_impl_0的代码

struct __main_block_impl_0 {

struct __block_impl impl;

struct __main_block_desc_0* Desc;

int global;

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _global, int flags=0) : global(_global) {

    impl.isa = &_NSConcreteStackBlock;

    impl.Flags = flags;

    impl.FuncPtr = fp;

    Desc = desc;

  }

};

// 在OC中调用Block的方法转为C++代码如下,实际上是指向结构体的指针myBlock访问其FuncPtr元素,在定义Block时为FuncPtr元素传进去的__main_block_func_0方法

((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

// __main_block_func_0方法代码如下,由此可见NSLog的global正是定义Block时为结构体传进去的局部变量global的值

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    int global = __cself->global; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6y_vkd9wnv13pz6lc_h8phss0jw0000gn_T_main_d5d9eb_mi_0, global);

}

// 由此可知,在Block定义时便是将局部变量的值传给Block变量所指向的结构体,因此在调用Block之前对局部变量进行修改并不会影响Block内部的值,同时内部的值也是不可修改的

Block内访问__block修饰的局部变量

在局部变量前使用下划线下划线block修饰,在声明Block之后、调用Block之前对局部变量进行修改,在调用Block时局部变量值是修改之后的新值

// 声明局部变量global

__block int global = 100;

void(^myBlock)() = ^{

    NSLog(@"global = %d", global);

};

global = 101;

// 调用后控制台输出"global = 101"

myBlock();

在局部变量前使用下划线下划线block修饰,在Block中可以直接修改局部变量

// 声明局部变量global

__block int global = 100;

void(^myBlock)() = ^{

    global ++; //这句正确

    NSLog(@"global = %d", global);

};

// 调用后控制台输出"global = 101"

myBlock();

注: 原理解析,通过clang命令将OC转为C++代码来查看一下Block底层实现

// OC代码如下

void(^myBlock)() = ^{

    NSLog(@"global = %d", global);

};

// 转为C++代码如下

void(*myBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_global_0 *)&global, 570425344));

// 将变量类型精简之后C++代码如下,我们发现Block变量实际上就是一个指向结构体__main_block_impl_0的指针,而结构体的第三个元素是局部变量global的指针

void(*myBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &global, 570425344);

// 由此可知,在局部变量前使用__block修饰,在Block定义时便是将局部变量的指针传给Block变量所指向的结构体,因此在调用Block之前对局部变量进行修改会影响Block内部的值,同时内部的值也是可以修改的

Block内访问全局变量

在Block中可以访问全局变量

// 声明全局变量global

int global = 100;

void(^myBlock)() = ^{

    NSLog(@"global = %d", global);

};

// 调用后控制台输出"global = 100"

myBlock();

在声明Block之后、调用Block之前对全局变量进行修改,在调用Block时全局变量值是修改之后的新值

// 声明全局变量global

int global = 100;

void(^myBlock)() = ^{

    NSLog(@"global = %d", global);

};

global = 101;

// 调用后控制台输出"global = 101"

myBlock();

在Block中可以直接修改全局变量

// 声明全局变量global

int global = 100;

void(^myBlock)() = ^{

    global ++;

    NSLog(@"global = %d", global);

};

// 调用后控制台输出"global = 101"

myBlock();

注: 原理解析,通过clang命令将OC转为C++代码来查看一下Block底层实现

// OC代码如下

void(^myBlock)() = ^{

    NSLog(@"global = %d", global);

};

// 转为C++代码如下

void(*myBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

// 将变量类型精简之后C++代码如下,我们发现Block变量实际上就是一个指向结构体__main_block_impl_0的指针,而结构体中并未保存全局变量global的值或者指针

void(*myBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);

// 我们看一下结构体__main_block_impl_0的代码

struct __main_block_impl_0 {

struct __block_impl impl;

struct __main_block_desc_0* Desc;

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {

    impl.isa = &_NSConcreteStackBlock;

    impl.Flags = flags;

    impl.FuncPtr = fp;

    Desc = desc;

  }

};

// 在OC中调用Block的方法转为C++代码如下,实际上是指向结构体的指针myBlock访问其FuncPtr元素,在定义Block时为FuncPtr元素传进去的__main_block_func_0方法

((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

// __main_block_func_0方法代码如下,由此可见NSLog的global还是全局变量global的值

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6y_vkd9wnv13pz6lc_h8phss0jw0000gn_T_main_f35954_mi_0, global);

}

// 由此可知,全局变量所占用的内存只有一份,供所有函数共同调用,在Block定义时并未将全局变量的值或者指针传给Block变量所指向的结构体,因此在调用Block之前对局部变量进行修改会影响Block内部的值,同时内部的值也是可以修改的

Block内访问静态变量

在Block中可以访问静态变量

// 声明静态变量global

static int global = 100;

void(^myBlock)() = ^{

    NSLog(@"global = %d", global);

};

// 调用后控制台输出"global = 100"

myBlock();

在声明Block之后、调用Block之前对静态变量进行修改,在调用Block时静态变量值是修改之后的新值

// 声明静态变量global

static int global = 100;

void(^myBlock)() = ^{

    NSLog(@"global = %d", global);

};

global = 101;

// 调用后控制台输出"global = 101"

myBlock();

在Block中可以直接修改静态变量

// 声明静态变量global

static int global = 100;

void(^myBlock)() = ^{

    global ++;

    NSLog(@"global = %d", global);

};

// 调用后控制台输出"global = 101"

myBlock();

注: 原理解析,通过clang命令将OC转为C++代码来查看一下Block底层实现

// OC代码如下

void(^myBlock)() = ^{

    NSLog(@"global = %d", global);

};

// 转为C++代码如下

void(*myBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &global));

// 将变量类型精简之后C++代码如下,我们发现Block变量实际上就是一个指向结构体__main_block_impl_0的指针,而结构体的第三个元素是静态变量global的指针

void(*myBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &global);

// 我们看一下结构体__main_block_impl_0的代码

struct __main_block_impl_0 {

struct __block_impl impl;

struct __main_block_desc_0* Desc;

int *global;

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_global, int flags=0) : global(_global) {

    impl.isa = &_NSConcreteStackBlock;

    impl.Flags = flags;

    impl.FuncPtr = fp;

    Desc = desc;

  }

};

// 在OC中调用Block的方法转为C++代码如下,实际上是指向结构体的指针myBlock访问其FuncPtr元素,在定义Block时为FuncPtr元素传进去的__main_block_func_0方法

((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

// __main_block_func_0方法代码如下,由此可见NSLog的global正是定义Block时为结构体传进去的静态变量global的指针

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    int *global = __cself->global; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6y_vkd9wnv13pz6lc_h8phss0jw0000gn_T_main_4d124d_mi_0, (*global));

}

// 由此可知,在Block定义时便是将静态变量的指针传给Block变量所指向的结构体,因此在调用Block之前对静态变量进行修改会影响Block内部的值,同时内部的值也是可以修改的

Block在MRC及ARC下的内存管理

Block在MRC下的内存管理

默认情况下,Block的内存存储在栈中,不需要开发人员对其进行内存管理

// 当Block变量出了作用域,Block的内存会被自动释放

void(^myBlock)() = ^{

    NSLog(@"------");

};

myBlock();

在Block的内存存储在栈中时,如果在Block中引用了外面的对象,不会对所引用的对象进行任何操作

Person *p = [[Person alloc] init];

void(^myBlock)() = ^{

    NSLog(@"------%@", p);

};

myBlock();

[p release]; // Person对象在这里可以正常被释放

如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,这时需要开发人员对其进行release操作来管理内存

void(^myBlock)() = ^{

    NSLog(@"------");

};

myBlock();

Block_copy(myBlock);

// do something ...

Block_release(myBlock);

如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行一次retain操作,即使在Block自身调用了release操作之后,Block也不会对所引用的对象进行一次release操作,这时会造成内存泄漏

Person *p = [[Person alloc] init];

void(^myBlock)() = ^{

    NSLog(@"------%@", p);

};

myBlock();

Block_copy(myBlock);

// do something ...

Block_release(myBlock);

[p release]; // Person对象在这里无法正常被释放,因为其在Block中被进行了一次retain操作

如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行一次retain操作,为了不对所引用的对象进行一次retain操作,可以在对象的前面使用下划线下划线block来修饰

__block Person *p = [[Person alloc] init];

void(^myBlock)() = ^{

    NSLog(@"------%@", p);

};

myBlock();

Block_copy(myBlock);

// do something ...

Block_release(myBlock);

[p release]; // Person对象在这里可以正常被释放

如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用

情况一

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

@end

@implementation Person

- (void)dealloc

{

    NSLog(@"Person dealloc");

    Block_release(_myBlock);

    [super dealloc];

}

@end

Person *p = [[Person alloc] init];

p.myBlock = ^{

    NSLog(@"------%@", p);

};

p.myBlock();

[p release]; // 因为myBlock作为Person的属性,采用copy修饰符修饰(这样才能保证Block在堆里面,以免Block在栈中被系统释放),所以Block会对Person对象进行一次retain操作,导致循环引用无法释放

情况二

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

- (void)resetBlock;

@end

@implementation Person

- (void)resetBlock

{

    self.myBlock = ^{

        NSLog(@"------%@", self);

    };

}

- (void)dealloc

{

    NSLog(@"Person dealloc");

    Block_release(_myBlock);

    [super dealloc];

}

@end

Person *p = [[Person alloc] init];

[p resetBlock];

[p release]; // Person对象在这里无法正常释放,虽然表面看起来一个alloc对应一个release符合内存管理规则,但是实际在resetBlock方法实现中,Block内部对self进行了一次retain操作,导致循环引用无法释放

如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用,解决循环引用的办法是在对象的前面使用下划线下划线block来修饰,以避免Block对对象进行retain操作

情况一

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

@end

@implementation Person

- (void)dealloc

{

    NSLog(@"Person dealloc");

    Block_release(_myBlock);

    [super dealloc];

}

@end

__block Person *p = [[Person alloc] init];

p.myBlock = ^{

    NSLog(@"------%@", p);

};

p.myBlock();

[p release]; // Person对象在这里可以正常被释放

情况二

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

- (void)resetBlock;

@end

@implementation Person

- (void)resetBlock

{

    //这里为了通用一点,可以使用__block typeof(self) p = self;

    __block Person *p = self;

    self.myBlock = ^{

        NSLog(@"------%@", p);

    };

}

- (void)dealloc

{

    NSLog(@"Person dealloc");

    Block_release(_myBlock);

    [super dealloc];

}

@end

Person *p = [[Person alloc] init];

[p resetBlock];

[p release]; // Person对象在这里可以正常被释放

Block在ARC下的内存管理

在ARC默认情况下,Block的内存存储在堆中,ARC会自动进行内存管理,程序员只需要避免循环引用即可

// 当Block变量出了作用域,Block的内存会被自动释放

void(^myBlock)() = ^{

    NSLog(@"------");

};

myBlock();

在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行强引用,但是在Block被释放时会自动去掉对该对象的强引用,所以不会造成内存泄漏

Person *p = [[Person alloc] init];

void(^myBlock)() = ^{

    NSLog(@"------%@", p);

};

myBlock();

// Person对象在这里可以正常被释放

如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用

情况一

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

@end

@implementation Person

- (void)dealloc

{

    NSLog(@"Person dealloc");

}

@end

Person *p = [[Person alloc] init];

p.myBlock = ^{

    NSLog(@"------%@", p);

};

p.myBlock();

// 因为myBlock作为Person的属性,采用copy修饰符修饰(这样才能保证Block在堆里面,以免Block在栈中被系统释放),所以Block会对Person对象进行一次强引用,导致循环引用无法释放

情况二

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

- (void)resetBlock;

@end

@implementation Person

- (void)resetBlock

{

    self.myBlock = ^{

        NSLog(@"------%@", self);

    };

}

- (void)dealloc

{

    NSLog(@"Person dealloc");

}

@end

Person *p = [[Person alloc] init];

[p resetBlock];

// Person对象在这里无法正常释放,在resetBlock方法实现中,Block内部对self进行了一次强引用,导致循环引用无法释放

如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用,解决循环引用的办法是使用一个弱引用的指针指向该对象,然后在Block内部使用该弱引用指针来进行操作,这样避免了Block对对象进行强引用

情况一

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

@end

@implementation Person

- (void)dealloc

{

    NSLog(@"Person dealloc");

}

@end

Person *p = [[Person alloc] init];

__weak typeof(p) weakP = p;

p.myBlock = ^{

    NSLog(@"------%@", weakP);

};

p.myBlock();

// Person对象在这里可以正常被释放

情况二

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

- (void)resetBlock;

@end

@implementation Person

- (void)resetBlock

{

    //这里为了通用一点,可以使用__weak typeof(self) weakP = self;

    __weak Person *weakP = self;

    self.myBlock = ^{

        NSLog(@"------%@", weakP);

    };

}

- (void)dealloc

{

    NSLog(@"Person dealloc");

}

@end

Person *p = [[Person alloc] init];

[p resetBlock];

// Person对象在这里可以正常被释放

Block在ARC下的内存管理的官方案例

在MRC中,我们从当前控制器采用模态视图方式present进入MyViewController控制器,在Block中会对myViewController进行一次retain操作,造成循环引用

MyViewController *myController = [[MyViewController alloc] init];

// ...

myController.completionHandler =  ^(NSInteger result) {

   [myController dismissViewControllerAnimated:YES completion:nil];

};

[self presentViewController:myController animated:YES completion:^{

   [myController release];

}];

在MRC中解决循环引用的办法即在变量前使用下划线下划线block修饰,禁止Block对所引用的对象进行retain操作

__block MyViewController *myController = [[MyViewController alloc] init];

// ...

myController.completionHandler =  ^(NSInteger result) {

    [myController dismissViewControllerAnimated:YES completion:nil];

};

[self presentViewController:myController animated:YES completion:^{

   [myController release];

}];

但是上述方法在ARC下行不通,因为下划线下划线block在ARC中并不能禁止Block对所引用的对象进行强引用,解决办法可以是在Block中将myController置空(为了可以修改myController,还是需要使用下划线下划线block对变量进行修饰)

__block MyViewController *myController = [[MyViewController alloc] init];

// ...

myController.completionHandler =  ^(NSInteger result) {

    [myController dismissViewControllerAnimated:YES completion:nil];

    myController = nil;

};

[self presentViewController:myController animated:YES completion:^{}];

上述方法确实可以解决循环引用,但是在ARC中还有更优雅的解决办法,新创建一个弱指针来指向该对象,并将该弱指针放在Block中使用,这样Block便不会造成循环引用

MyViewController *myController = [[MyViewController alloc] init];

// ...

__weak MyViewController *weakMyController = myController;

myController.completionHandler =  ^(NSInteger result) {

    [weakMyController dismissViewControllerAnimated:YES completion:nil];

};

[self presentViewController:myController animated:YES completion:^{}];

虽然解决了循环引用,但是也容易涉及到另一个问题,因为Block是通过弱引用指向了myController对象,那么有可能在调用Block之前myController对象便已经被释放了,所以我们需要在Block内部再定义一个强指针来指向myController对象

MyViewController *myController = [[MyViewController alloc] init];

// ...

__weak MyViewController *weakMyController = myController;

myController.completionHandler =  ^(NSInteger result) {

    MyViewController *strongMyController = weakMyController;

    if (strongMyController)

    {

        [strongMyController dismissViewControllerAnimated:YES completion:nil];

    }

    else

    {

        // Probably nothing...

    }

};

[self presentViewController:myController animated:YES completion:^{}];

这里需要补充一下,在Block内部定义的变量,会在作用域结束时自动释放,Block对其并没有强引用关系,且在ARC中只需要避免循环引用即可,如果只是Block单方面地对外部变量进行强引用,并不会造成内存泄漏

注: 关于下划线下划线block关键字在MRC和ARC下的不同

__block在MRC下有两个作用

1. 允许在Block中访问和修改局部变量

2. 禁止Block对所引用的对象进行隐式retain操作

__block在ARC下只有一个作用

1. 允许在Block中访问和修改局部变量

使用Block进行排序

在开发中,我们一般使用数组的如下两个方法来进行排序

不可变数组的方法: - (NSArray *)sortedArrayUsingComparator:(NSComparator)cmptr

可变数组的方法 : - (void)sortUsingComparator:(NSComparator)cmptr

其中,NSComparator是利用typedef定义的Block类型

typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);

其中,这个返回值为NSComparisonResult枚举,这个返回值用来决定Block的两个参数顺序,我们只需在Block中指明不同条件下Block的两个参数的顺序即可,方法内部会将数组中的元素分别利用Block来进行比较并排序

typedef NS_ENUM(NSInteger, NSComparisonResult)

{

    NSOrderedAscending = -1L, //升序,表示左侧的字符在右侧的字符前边

    NSOrderedSame, //相等

    NSOrderedDescending //降序,表示左侧的字符在右侧的字符后边

};

我们以Person类为例,对Person对象以年龄升序进行排序,具体方法如下

@interface Student : NSObject

@property (nonatomic, assign) int age;

@end

@implementation Student

@end

Student *stu1 = [[Student alloc] init];

stu1.age = 18;

Student *stu2 = [[Student alloc] init];

stu2.age = 28;

Student *stu3 = [[Student alloc] init];

stu3.age = 11;

NSArray *array = @[stu1,stu2,stu3];

array = [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {

    Student *stu1 = obj1;

    Student *stu2 = obj2;

    if (stu1.age > stu2.age)

    {

        return NSOrderedDescending; //在这里返回降序,说明在该种条件下,obj1排在obj2的后边

    }

    else if (stu1.age < stu2.age)

    {

        return NSOrderedAscending;

    }

    else

    {

        return NSOrderedSame;

    }

}];

iOS
Web note ad 1