平常只顾撸码,导致忽视了知识点的总结,最近有些闲暇时间来整理整理一些知识点,供时不时的翻阅。如有侵权,请及时联系我。
1,什么情况下会出现循环引用?
循环引用是两个或者两个以上对象互相强引用,导致所有对象无法被释放的现象。这是内存泄漏的一种情况。
//---- class Father ---
@interface Father :NSObject
@property (nonatomic,strong)Son *son;
@end
//---- class Son ---
@interface Son :NSObject
@property (nonatomic,strong)Father *father;
@end
上述的代码有两个类,分别为Father和Son,Father对Son是强引用,Son对Father强引用。所以,要释放Son,则先释放Father,要释放Father则必须释放Son,如此一来,两个对象都无法被释放。
解决方法是将Father中的Son对象的属性由Strong改为Weak。
内存泄漏可以用Xcode中的Debug Memory Graph检查:
同时,Xcode也会在runtime中自动汇报内存泄漏的问题:
2,strong,weak,assign,和copy区别:
strong表示指向并拥有该对象。其修饰的对象引用计数会增加1.该对象只要引用计数不为0,就不会销毁。当然,强行将其设为nil也可以销毁它。
weak表示指向但不拥有该对象。其修饰的对象引用计数不会增加。无需手动设置,该对象会自行在内存中被销毁。
assign主要修饰基本的数据类型,如NSInteger和CGFloat,这些数值主要存于栈中。
weak一般用于修饰对象,assign一般用于修饰基本的数据类型。原因是assign修饰的对象被释放后,指针的地址依然存在,造成“野指针”,在堆上容易造成崩溃。而栈上的内存系统自动处理,不会造成“野指针”。
assign、weak不同点:
assign 可以用非 OC 对象,而 weak 必须用于 OC 对象。
weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空(nil)。
copy与strong类似。不同之处是,strong的复制是多个指针指向同一个地址,而copy的复制是每次会在内存中复制一份对象,指针指向不同地址。copy一般用在修饰有对应可变类型的不可变对象上,如NSString,NSArray,NSDictionary。
注:在Objection-C中,基本的数据类型默认的关键字是atomic,readwrite和assign;普通属性的默认关键字是atomic,readwrite和strong
3,atomic和nonatomic区别
atomic修饰的对象会保证setter和getter的完整性,任何线程访问它都可以得到一个完整的初始化对象。因为要保证操作完成。所以速度比较慢。atomic比nonatomic安全,但也不是绝对的线程安全,例如,当多个线程同时调用set和get时,就会导致获取的对象值不一样。要想线程绝对安全,就要用@synchronized。
nonatomic修饰的对象不保证setter和getter的完整性,所以,当多个线程访问它时,它可能会返回未初始化的对象。正因为如此,nonatomic比atomic的速度快,但是线程也是不安全的。
4,runloop和线程有什么关系
runloop是每一个线程一直运行的一个对象,它主要用来负责响应需要处理的各种事件和消息。每一个线程都有且仅有一个runloop与其对应,没有线程,就没有runloop。
在所有线程中,只有主线程的runloop是默认启动的,main函数会设置一个NSRunloop对象。而其他线程的runloop默认是没有启动的,可以通过 [NSRunLoop currentRunLoop]来启动。
5,__weak和__block区别
__weak与weak基本相同。前者用于修饰变量(variable),后者用于修饰属性(property)。
__weak主要用于防止block中的循环引用。
__block也用于修饰变量。它是引用修饰,所以,其修饰的值是动态变化的,既可以被重新赋值的。__block用于修饰某些block内部将要修改的外部变量。
__weak和__block的使用场景几乎与block息息相关。而所谓block,就是Objection-C对于闭包的实现。闭包就是没有名字的函数,或者可以理解为指向函数的指针。
6,请问下面的代码有什么问题(属性申明代码风格考查)
@property (nonatomic,strong) NSString *title;
@property (assign,nonatomic) int workID;
title不应该用strong来修饰,而应该用copy。因为NSString是不可变的数据类型,它有对应的NSMutableString数据类型,用strong来修饰会有NSString被修改的可能。
例子:
self.title = @"title";
NSMutableString *mutableTitle = @"mutableTitle";
self.title = mutableTitle;
原来title的值是“title”,后来被改成了“mutableTitle”。
有对应可变数据类型的不可变数据类型都应该修饰为copy。copy表示该属性不能被修改,如果对可变类型如NSMutableString用copy修饰,那么当对其进行修改时,程序就会崩溃。
workID不应该用int类型,而应该用NSInteger类型。int只表示32位的整形数据,而NSInteger在32位计算机中与int一样,在64位计算机中则是64位的整形数据。对于不用类型的计算机,NSInteger更加灵活和准确。同理,可以用NSUInteger替代unsigned,用CGFloat替代float。
在属性生命时,最好遵循原子性,以及读写顺序、内存管理顺序,这样可读性更高。
上面的代码正确写法如下:
@property (nonatomic,copy) NSString *title;
@property (nonatomic,assign) NSInteger workID;
7,请问下面代码什么问题
typedef enum {
Normal,
VIP
}CustomerType;
@interface Customer: NSObject
@property (nonatomic,copy)NSString *name;
@property (nonatomic,strong)UIImage *profileImage;
@property (nonatomic,assign)CustomerType customerType;
@end
enum定义的写法不好。苹果官方推荐使用NS_ENUM来定义枚举。同时,在枚举的每个类型前加上enum的名称,这样方便在混合编程时直接在swift中调用。
UIImage不应该出现在Customer中。Customer明显时一个model类,在UIImage应该归属于View部分。无论是MVC还是MVVM,抑或是VIPER,model都应该和View画清界限,避免整个架构耦合。下面是正确代码:
typedef NS_ENUM(NSInteger,CustomerType) {
CustomerTypeNormal,
CustomerTypeVIP
};
@interface Customer: NSObject
@property (nonatomic,copy)NSString *name;
@property (nonatomic,strong)NSData *profileImageData;
@property (nonatomic,assign)CustomerType customerType;
@end
8,@synthesize和@dynamic的作用
同一个属性@synthesize和@dynamic不能一起用。
@synthesize 告诉编译器去生成该属性的成员变量、getter、setter方法。
@dynamic 告诉编译器不要生成此属性的成员变量、getter、setter方法,开发者自己去实现。
9,UIView/CALayer关系
view是layer的代理对象;view负责管理layer,layer负责渲染;view初始化的时候默认会创建一个layer;设置view的frame和bounds等内部其实是修改layer对应属性。
10,#include与#import 的区别、#import与@class的区别
#include与#import的区别:#include与#import其效果相同,只是后者不会引起交叉编译,确保头文件只会被导入一次
#import与@class的区别:import会包含这个类的所有信息,包含实体变量和方法,而@class只会告诉编译器,其后面的声明的名称是类的名称,至于这些类是如何定义的,暂时不用考虑,后面会再告诉你。使用#import编译效率高,防止相互包含的编译错误
11,MVC 和 MVVM 的区别
1). MVVM是对胖模型进行的拆分,其本质是给控制器减负,将一些弱业务逻辑放到VM中去处理。
2). MVC是一切设计的基础,所有新的设计模式都是基于MVC进行的改进。
12、浅拷贝和深拷贝的区别?
浅拷贝:只复制指向对象的指针,而不复制引用对象本身。
深拷贝:复制引用对象本身。内存中存在了两份独立对象本身,当修改A时,A_copy不变。
13,id 声明的对象有什么特性?
id 声明的对象具有运行时的特性,即可以指向任意类型的Objcetive-C的对象。
14,isKindOfClass、isMemberOfClass、selector作用分别是什么
isKindOfClass:作用是某个对象属于某个类型或者继承自某类型。
isMemberOfClass:某个对象确切属于某个类型。
selector:通过方法名,获取在内存中的函数的入口地址。
15,BAD_ACCESS在什么情况下出现?
这种问题在开发时经常遇到。原因是访问了野指针,比如访问已经释放对象的成员变量或者发消息、死循环等。
16,你一般是怎么用Instruments的?
Instruments里面工具很多,常用:
1). Time Profiler: 性能分析
2). Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能。
3). Allocations:用来检查内存,写算法的那批人也用这个来检查。
4). Leaks:检查内存,看是否有内存泄露。
17,iOS多线程技术有哪几种方式?
pthread、NSThread、GCD、NSOperation
18,GCD 与 NSOperation 的区别:
GCD 和 NSOperation 都是用于实现多线程:
GCD 基于C语言的底层API,GCD主要与block结合使用,代码简洁高效。
NSOperation 属于Objective-C类,是基于GCD更高一层的封装。复杂任务一般用NSOperation实现。
19,如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
// 使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
// 创建队列组
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_async(group, queue, ^{ /*加载图片3 */ });
// 当并发队列组中的任务执行完毕后才会执行这里的代码
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 合并图片
});
20,什么是 Runtime
Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一,我们平时编写的OC代码,底层都是基于它来实现的。
21,Runtime实现的机制是什么,怎么用,一般用于干嘛?
1)在程序运行过程中, 动态创建一个类(比如KVO的底层实现)
2)在程序运行过程中, 动态地为某个类添加属性\方法, 修改属性值\方法
3)遍历一个类的所有成员变量(属性)\所有方法
例如:我们需要对一个类的属性进行归档解档的时候属性特别的多,这时候,我们就会写很多对应的代码,但是如果使用了runtime就可以动态设置!
作用:能动态生成、修改、删除一个类、一个成员变量、一个方法
添加头文件:import objc/runtime.h>
22,什么是 TCP / UDP ?
TCP:传输控制协议。
UDP:用户数据协议。
TCP 是面向连接的,建立连接需要经历三次握手,是可靠的传输层协议。
UDP 是面向无连接的,数据传输是不可靠的,它只管发,不管收不收得到。
简单的说,TCP注重数据安全,而UDP数据传输快点,但安全性一般。
23,如何高性能的给 UIImageView 加个圆角?
不好的解决方案:使用下面的方式会强制Core Animation提前渲染屏幕的离屏绘制, 而离屏绘制就会给性能带来负面影响,会有卡顿的现象出现。
self.view.layer.cornerRadius = 5.0f;
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;
}
24,HTTP协议中 POST 方法和 GET 方法有那些区别?
- GET用于向服务器请求数据,POST用于提交数据
- GET请求,请求参数拼接形式暴露在地址栏,而POST请求参数则放在请求体里面,因此GET请求不适合用于验证密码等操作
- GET请求的URL有长度限制,POST请求不会有长度限制
25,请简单的介绍下APNS发送系统消息的机制
APNS优势:杜绝了类似安卓那种为了接受通知不停在后台唤醒程序保持长连接的行为,由iOS系统和APNS进行长连接替代。
APNS的原理:
1). 应用在通知中心注册,由iOS系统向APNS请求返回设备令牌(device Token)
2). 应用程序接收到设备令牌并发送给自己的后台服务器
3). 服务器把要推送的内容和设备发送给APNS
4). APNS根据设备令牌找到设备,再由iOS根据APPID把推送内容展示
26,谈谈 UITableView 的优化
1). 正确的复用cell。
2). 设计统一规格的Cell
3). 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;
4). 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
5). 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!
6). 减少子视图的层级关系
7). 尽量使所有的视图不透明化以及做切圆操作。
8). 不要动态的add 或者 remove 子控件。最好在初始化时就添加完,然后通过hidden来控制是否显示。
9). 使用调试工具分析问题。
27,面向对象:
面向对象的三大特征:继承,封装,多态
继承:子类自动共享父类非私有数据结构和方法的机制,是类之间的一种关系。
封装:在面向对象的语言中,对象、类、方法都是一种封装,对象是封装的最基本单位。
多态:用父类指针指向子类的对象。通过不同的对象调用相同的名称的方法,却产生不同的结果。
28,YTKNetwork 的基本思想
YTKNetwork 的基本的思想是把每一个网络请求封装成对象。所以使用 YTKNetwork,你的每一个请求都需要继承 YTKRequest 类,通过覆盖父类的一些方法来构造指定的网络请求。
把每一个网络请求封装成对象其实是使用了设计模式中的 Command 模式,它有以下好处:
将网络请求与具体的第三方库依赖隔离,方便以后更换底层的网络库。
方便在基类中处理公共逻辑,例如猿题库的数据版本号信息就统一在基类中处理。
方便在基类中处理缓存逻辑,以及其它一些公共逻辑。
方便做对象的持久化。
当然,如果说它有什么不好,那就是如果你的工程非常简单,这么写会显得没有直接用 AFNetworking 将请求逻辑写在 Controller 中方便,所以 YTKNetwork 并不合适特别简单的项目。
YTKNetwork提供的主要功能:
支持按时间缓存和版本号缓存网络请求内容
支持统一设置服务器和 CDN 的地址
支持检查返回 JSON 内容的合法性
支持 block 和 delegate 两种模式的回调方式
支持批量的网络请求发送,并统一设置它们的回调(实现在 YTKBatchRequest 类中)
支持方便地设置有相互依赖的网络请求的发送,例如:发送请求 A,根据请求 A 的结果,选择性的发送请求 B 和 C,再根据 B 和 C 的结果,选择性的发送请求 D。(实现在 YTKChainRequest 类中)
支持网络请求 URL 的 filter,可以统一为网络请求加上一些参数,或者修改一些路径。
定义了一套插件机制,可以很方便地为 YTKNetwork 增加功能。猿题库官方现在提供了一个插件,可以在某些网络请求发起时,在界面上显示“正在加载”的 HUD。
29,简单说一下APP的启动过程,从main文件开始说起
1、main函数
2、执行UIAppliccationMain函数
<1>,创建UIApplication对象
<2>,创建UIApplicationDelegate对象并复制
<3>,读取配置文件info.plist, 设置程序启动的一些属性
<4>,创建应用程序的Main Runloop循环
3、UIApplicationDelegate对象开始处理监听到的事件
<1>,程序启动成功之后,首先调用application:didFinishLaunchingWithOptions: 方法
如果info.plist文件中配置了启动storyboard文件名,则加载storyboard文件。如果没有配置,则根据代码来创建UIWindow–>UIWindow的rootViewController显示
30,本地通知和远程通知的基本概念和用法:
远程通知是指服务器发出的通知,通过苹果的推送然后到达用户设备。本地通知是指不通过网络,直接安装应用后就可以接到通知了,典型的例子是日历、待办、闹钟等应用。
不过就表现形式来说两者基本一样,都会出现在通知中心,都可以出现在锁屏界面,都可以出现在界面上部,都可以添加应用上的红点。
本地通知具体的创建方法:
-》创建一个本地通知对象UILocalNotification
-》设置fireDate,AlertBody,AlertAction,soundName,applicationBadgeNumber,repeatInterval,alertLanuchImage属性
-》配置通知参数,userInfo。及通知的内容。我们可以在接收通知的方法中获取该对象。
-》调用通知,使用UIApplication的单例对象scheduleLocalNotificaiton按照计划启动通知
远程通知—>APNs(25,请简单的介绍下APNS发送系统消息的机制)
31, isa指针?
isa指针是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。
32,KVO的使用?实现原理?(为什么要创建子类来实现)
基本的原理:当观察某对象 A 时,KVO 机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性 keyPath 的 setter 方法。setter 方法随后负责通知观察对象属性的改变状况。
步骤:1.注册观察者,实施监听;
2.在回调方法中处理属性发生的变化;
3.移除观察者
KVO 的实现依赖于 Objective-C 强大的 Runtime
33,为什么block要使用copy而不是strong或者其他属性修饰?
block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈上的,而不是在堆上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。因为栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.
使用retain也可以,但是block的retain行为默认是用copy的行为实现的,
因为block变量默认是声明为栈变量的,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆,所以说为了block属性声明和实际的操作一致,最好声明为copy。
Tips:循环引用发生的条件就是持有这个block的对象,被block里边加入的对象持有。
34,第三方框架原理
SDWebImage原理: 1,从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用。 2, 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中。 3,从网络上获取,使用,缓存到内存,缓存到沙盒。
35,ios之数据加密
1,使用https协议请求网页,post来请求网页数据,保证用户的账号密码不被被人获取到。
2,MD5的应用:
1>,由于MD5加密算法具有较好的安全性,而且免费,因此该加密算法被广泛使用
2>,主要运用在数字签名、文件完整性验证以及口令加密等方面.(应用场景:登陆界面,签名,时间戳等)
36,什么是method swizzling
每个类都维护一个方法列表,其中方法名与其实现是一一对应的关系,即SEL(方法名)和IMP(指向实现的指针)的对应关系。method swizzling可以在runtime将SEL和IMP进行更换。比如,SELa原来对应IMPa,SELb原来对应IMPb,而在method swizzling之后,SELa原来对应IMPb,SELb原来对应IMPa,下面是一个封装好的实现示范:
//方法一的SEL和Method SEL
SEL oneSEL = @selector(methodOne:);
Method oneMethod = class_getInstanceMethod(selfClass, oneSEL);
//方法二的SEL和Method SEL
SEL twoSEL = @selector(methodTwo:);
Method twoMethod = class_getInstanceMethod(selfClass, twoSEL);
//给方法一添加实现,可以避免方法一没有实现
BOOL addSucc = class_addMethod(selfClass, oneSEL, method_getImplementation(twoMethod), method_getTypeEncoding(twoMethod));
if (addSucc) {//添加成功:将方法一的实现替换到方法二
class_replaceMethod(selfClass, twoSEL,method_getImplementation(oneMethod), method_getTypeEncoding(oneMethod))
}else
{ //添加失败:方法一已经有实现,直接将方法一和方法二的实现交换
method_exchangeImplementations(oneMethod, twoMethod)
}
Tips:
1>,方法交换应该保证唯一性和原子性。唯一性是指应该尽可能在+load方法中实现,这样可以保证方法一定会被调用且不会出现异常。原子性是指使用dispatch_once来执行方法交换,这样可以保证只运行一次。
2>,不要轻易使用method swizzling。因为动态交换方法的实现并没有编译器的安全保证,可能会在运行时造成奇怪的问题。
36、用伪代码写一个线程安全的单例模式
static id _instance;
+ (id)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
+ (instancetype)sharedData {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
- (id)copyWithZone:(NSZone *)zone {
return _instance;
}
37、如何实现视图的变形?
答:通过修改view的 transform 属性即可。
38、友盟统计接口统计的所有功能
APP启动速度,APP停留页面时间等
39、NSTimer创建后,会在哪个线程运行。
用scheduledTimerWithTimeInterval创建的,在哪个线程创建就会被加入哪个线程的RunLoop中就运行在哪个线程
自己创建的Timer,加入到哪个线程的RunLoop中就运行在哪个线程。
40、浅谈iOS开发中方法延迟执行的几种方式
Method1. performSelector方法
Method2. NSTimer定时器
Method3. NSThread线程的sleep
Method4. GCD
41、对于Objective-C,你认为它最大的优点和最大的不足是什么?对于不足之处,现在有没有可用的方法绕过这些不足来实现需求。如果可以的话,你有没有考虑或者实践过重新实现OC的一些功能,如果有,具体会如何做?
答:最大的优点是它的运行时特性,不足是没有命名空间,对于命名冲
突,可以使用长命名法或特殊前缀解决,如果是引入的第三方库之间的
命名冲突,可以使用link命令及flag解决冲突。
42、既然提到G.C.D,那么问一下在使用G.C.D以及block时要注意些什么?它们两是一回事儿么?block在ARC中和传统的MRC中的行为和用法有没有什么区别,需要注意些什么?
答:使用block是要注意,若将block做函数参数时,需要把它放到最
后,GCD是Grand Central Dispatch,是一个对线程开源类库,而Block
是闭包,是能够读取其他函数内部变量的函数。
43、线程与进程的区别和联系
一般的应用程序是单个进程,也有多个进程(谷歌浏览器),进程是个静态的容器,里面容纳了很多线程,线程是一系列方法的线性执行路径。
44、@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的
@property 的本质是什么?
@property = ivar + getter + setter;
“属性” (property)有两大概念:ivar(实例变量)、getter+setter(存取方法)
“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。
45、用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?
用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作(就是把可变的赋值给不可变的),为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
1. 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。
2. 如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。
//总结:使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。