1、写出使用GCD方式从子线程回到主线程的方法代码
答:dispatch_sync(dispatch_get_main_queue(), ^{ });
2、如何用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(), ^{
// 合并图片
});
3、dispatch_barrier_async(栅栏函数)的作用是什么?
函数定义:dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
作用:
1.在它前面的任务执行结束后它才执行,它后面的任务要等它执行完成后才会开始执行。
2.避免数据竞争
// 1.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
// 2.向队列中添加任务
dispatch_async(queue, ^{ // 1.2是并行的
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]);
});
// 输出结果: 任务1 任务2 ——》 任务 barrier ——》任务3 任务4
// 其中的任务1与任务2,任务3与任务4 由于是并行处理先后顺序不定。
4、以下代码运行结果如何?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
// 只输出:1。(主线程死锁)
5、什么是 RunLoop
从字面上讲就是运行循环,它内部就是do-while循环,在这个循环内部不断地处理各种任务。
一个线程对应一个RunLoop,基本作用就是保持程序的持续运行,处理app中的各种事件。
通过runloop,有事运行,没事就休息,可以节省cpu资源,提高程序性能。
主线程的run loop默认是启动的。iOS的应用程序里面,程序启动后会有一个如下的main()函数
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
6、什么是 Runtime
Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一,我们平时编写的OC代码,底层都是基于它来实现的。
61、Runtime实现的机制是什么,怎么用,一般用于干嘛?
1). 使用时需要导入的头文件 <objc/message.h> <objc/runtime.h>
2). Runtime 运行时机制,它是一套C语言库。
3). 实际上我们编写的所有OC代码,最终都是转成了runtime库的东西。
比如:
类转成了 Runtime 库里面的结构体等数据类型,
方法转成了 Runtime 库里面的C语言函数,
平时调方法都是转成了 objc_msgSend 函数(所以说OC有个消息发送机制)
// OC是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
// [stu show]; 在objc动态编译时,会被转意为:objc_msgSend(stu, @selector(show));
4). 因此,可以说 Runtime 是OC的底层实现,是OC的幕后执行者。
有了Runtime库,能做什么事情呢?
Runtime库里面包含了跟类、成员变量、方法相关的API。
比如:
(1)获取类里面的所有成员变量。
(2)为类动态添加成员变量。
(3)动态改变类的方法实现。
(4)为类动态添加新的方法等。
因此,有了Runtime,想怎么改就怎么改。
7、什么是 Method Swizzle(黑魔法),什么情况下会使用?
1). 在没有一个类的实现源码的情况下,想改变其中一个方法的实现,
除了继承它重写、和借助类别重名方法暴力抢先之外,还有更加灵活的方法 Method Swizzle。
2). Method Swizzle 指的是改变一个已存在的选择器对应的实现的过程。OC中方法的调用能够在运行时通过改变,
通过改变类的调度表中选择器到最终函数间的映射关系。
3). 在OC中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用OC的动态特性,
可以实现在运行时偷换selector对应的方法实现。
4). 每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的方法实现。
5). 我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP。
6). 我们可以利用 class_replaceMethod 来修改类。
7). 我们可以利用 method_setImplementation 来直接设置某个方法的IMP。
8). 归根结底,都是偷换了selector的IMP。
8、_objc_msgForward 函数是做什么的,直接调用它将会发生什么?
答:_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,
但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
9、什么是 TCP / UDP ?
TCP:传输控制协议。
UDP:用户数据协议。
TCP 是面向连接的,建立连接需要经历三次握手,是可靠的传输层协议。
UDP 是面向无连接的,数据传输是不可靠的,它只管发,不管收不收得到。
简单的说,TCP注重数据安全,而UDP数据传输快点,但安全性一般。
10、通信底层原理(OSI七层模型)、
OSI采用了分层的结构化技术,共分七层:
物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
11、介绍一下XMPP?
XMPP是一种以XML为基础的开放式实时通信协议。
简单的说,XMPP就是一种协议,一种规定。就是说,在网络上传东西,XMM就是规定你上传大小的格式。
12、OC中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?
// 创建线程的方法
[NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]
[self performSelectorInBackground:nil withObject:nil];
[[NSThread alloc] initWithTarget:nil selector:nil object:nil];
dispatch_async(dispatch_get_global_queue(0, 0), ^{});
[[NSOperationQueue new] addOperation:nil];
// 主线程中执行代码的方法
[self performSelectorOnMainThread:nil withObject:nil waitUntilDone:YES];
dispatch_async(dispatch_get_main_queue(), ^{});
[[NSOperationQueue mainQueue] addOperation:nil];
13、tableView的重用机制?
答:UITableView 通过重用单元格来达到节省内存的目的: 通过为每个单元格指定一个重用标识符,即指定了单元格的种类,当屏幕上的单元格滑出屏幕时,系统会把这个单元格添加到重用队列中,等待被重用,当有新单元格从屏幕外滑入屏幕内时,从重用队列中找看有没有可以重用的单元格,如果有,就拿过来用,如果没有就创建一个来使用。
14、用伪代码写一个线程安全的单例模式
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;
}
15、如何实现视图的变形?
答:通过修改view的 transform 属性即可。
16、在手势对象基础类UIGestureRecognizer的常用子类手势类型中哪两个手势发生后,响应只会执行一次?
答:UITapGestureRecognizer,UISwipeGestureRecognizer是一次性手势,手势发生后,响应只会执行一次。
17、字符串常用方法:
NSString str = @"abc123";
NSArray arr = [str componentsSeparatedByString:@""];
//以目标字符串把原字符串分割成两部分,存到数组中。@[@"abc", @"123"];
18、如何高性能的给 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;
}
还有一种方案:使用了贝塞尔曲线"切割"个这个图片, 给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];
19、你是怎么封装一个view的
1). 可以通过纯代码或者xib的方式来封装子控件
2). 建立一个跟view相关的模型,然后将模型数据传给view,通过模型上的数据给view的子控件赋值
/**
纯代码初始化控件时一定会走这个方法
*/
(instancetype)initWithFrame:(CGRect)frame {
if(self = [super initWithFrame:frame]) {
[self setupUI];
}
return self;
}
/**
通过xib初始化控件时一定会走这个方法
*/
(id)initWithCoder:(NSCoder *)aDecoder {
if(self = [super initWithCoder:aDecoder]) {
[self setupUI];
}
return self;
}
(void)setupUI {
// 初始化代码
}
20、HTTP协议中 POST 方法和 GET 方法有那些区别?
GET用于向服务器请求数据,POST用于提交数据
GET请求,请求参数拼接形式暴露在地址栏,而POST请求参数则放在请求体里面,因此GET请求不适合用于验证密码等操作
GET请求的URL有长度限制,POST请求不会有长度限制
21、请简单的介绍下APNS发送系统消息的机制
APNS优势:杜绝了类似安卓那种为了接受通知不停在后台唤醒程序保持长连接的行为,由iOS系统和APNS进行长连接替代。
APNS的原理:
1). 应用在通知中心注册,由iOS系统向APNS请求返回设备令牌(device Token)
2). 应用程序接收到设备令牌并发送给自己的后台服务器
3). 服务器把要推送的内容和设备发送给APNS
4). APNS根据设备令牌找到设备,再由iOS根据APPID把推送内容展示
其实做为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS学习交流群341162898,不管你是小白还是大牛欢迎入驻,大家一起交流学习
83ED51949D3090CF2410D113936E3039.png
网友评论