问题1:Objective-C编译成C++代码报错
问题2:iPhone X的适配问题
首先尺寸是:1125*2436
1.启动页适配
-
先用PS把以往启动页图片素材改成11252436的尺寸。(怎么改?新建个11252436的画板,把素材图拉进去,command+T,shift+alt+鼠标拉动缩放,导出~)
-
进入LaunchImage,点击一张之前的启动图,在右侧LaunchImage选项下的iOS8.0 and Later选项下勾选Portrait。此时LaunchImage就多出了个iPhone X的启动图位置。
-
把做好的图片拉入这个空位就可以了。
2.导航栏的适配
现在的手机的宽度没有改变,但是高度有所拉伸667->812,首先状态栏由以前的20改变为现在的44,整个导航栏加状态栏由以前的64->88、
3.纯代码适配iPhoneX脚底:建议是列表页面不去适配,底部有按钮的界面要适配。
底部角圆角的距离是34,我们可以在任意界面打印安全试图就可以找到,NSLog(@"%@",NSStringFromUIEdgeInsets(self.view.safeAreaInsets));即可知道安全区域的边界
注意:设置你的底部按钮距离底部的距离是34即可。采用宏去判断
问题3:iOS 用了Object-C++ /有.mm文件 怎么才能再使用@import(第三方库有使用@import)?www.jianshu.com/p/9ec54a969…
**问题3:数的层序排列,不能用递归 **
**问题4.dispatch里面采用的是黑盒,内部实现 **
问题5.多线程锁,多读单写如何处理,多线程有没有遇到什么问题
多读单写:
1)可以同时有多个读操作,而在读操作的时候,不能有写操作。
2)在写操作的过程中,不能有其他写操作,并且在写操作之前,读操作都完成。
目前大部分采用的是dispatch_barrier_async来实现单写,通过并行+同步来实现多读
@interface UserCenter()
{
// 定义一个并发队列
dispatch_queue_t concurrent_queue;
// 用户数据中心, 可能多个线程需要数据访问
NSMutableDictionary *userCenterDic;
}
@end
// 多读单写模型
@implementation UserCenter
- (id)init
{
self = [super init];
if (self) {
// 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 创建数据容器
userCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
- (id)objectForKey:(NSString *)key
{
__block id obj;
// 同步读取指定数据
dispatch_sync(concurrent_queue, ^{
obj = [userCenterDic objectForKey:key];
});
return obj;
}
- (void)setObject:(id)obj forKey:(NSString *)key
{
// 异步栅栏调用设置数据
dispatch_barrier_async(concurrent_queue, ^{
[userCenterDic setObject:obj forKey:key];
});
}
问题6.performSelector: afterDelay的内部实现
当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
问题7.runloop和事件触发
对于RunLoop而言最核心的事情就是保证线程在没有消息的时候休眠,在有消息时唤醒,以提高程序性能。RunLoop这个机制是依靠系统内核来完成的(苹果操作系统核心组件Darwin中的Mach)。
问题8.元类存在的意义,它的数据结构是什么,类的数据结构是什么
元类中保存了创建类对象的类方法的全部信息,他指向父类的元类,最终指向了Root class(meta),而根类的元类直接指向了根类
设计metaclass的目的是什么?
1)类对象,元类对象能够复用消息发送流程机制
2)单一职责原则:metaclass代表的是类对象的对象,它存储了类的类方法,它的目的是将实例和类相关方法列表以及构建信息区分开来,方便各司其职,符合单一职责设计原则
问题9.增加关联属性,它是怎么实现关联的,他的内存是存储在关联对象的内存中的么?
首先要讲到一点
就是extension和category是不同的概念。
特别是针对属性这一点的区别上面,前者是在编译期决定的对象的内存分配,后者并不能在编译期去决定一个对象的内存分配。
前者会生成一个私有成员变量,并且通过setter和getter去访问和操作这个成员变量。
但是在category中,并不能去生成这个私有变量,因为对象的结构在编译期已经被决定了。
所以想在category中实现属性的概念,那么就要用到关联类型了。
Category不能直接添加成员属性,可以通过runtime的方式间接添加的方式实现。
会用到这两个函数,一个用来设置,一个用来获取
objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(self, @"name");
把对应代码放到属性的getter和setter里就可以实现需要的动态增加属性功能。
关联对象实现原理:
关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个AssociationsManager中,如果设置关联对象为nil,就相当于是移除关联对象。****
实现关联对象技术的核心对象有
-
AssociationsManager
-
AssociationsHashMap
-
ObjectAssociationMap
-
ObjcAssociation
其中Map同我们平时使用的字典类似。通过key-value一一对应存值。
全局保存一个AssociationManager
AssociationManger存有一个AssociationHashMap(哈希表,值类型为ObjectAssociationMap)
根据传入的object做DISGUISE(系列的变形操作),并以此作为AssociationHashMap的键。
ObjcAssociation是一个类型(包含了policy和value)
如果是设值,就可以 创建一个ObjcAssociation(由传入的value和policy),并且根据传入的key,以键值的形式存储到object对应的ObjectAssociationMap里。
如果是取值,就是通过object取到对应的ObjectAssociationMap,并且通过key,取到对应的ObjcAssociation, 再通过对应的value。
问题10.weak的属性会关联么?(关联对象是如何进行内存管理的?关联对象如何实现weak属性)
1, 关联对象的ObjectAssociation中有两个属性(uintptr_t _policy, id value),
_policy 包含 retain, assgin copy, 会对应的对对象进行和普通对象一样的内存管理操作.
2 ,实现weak,用__weak修饰对象,并将其用block包裹,关联时,关联block对象
-(void)setWeakvalue:(NSObject *)weakvalue {
__weak typeof(weakvalue) weakObj = weakvalue;
typeof(weakvalue) (^block)() = ^(){
return weakObj;
};
objc_setAssociatedObject(self, weakValueKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSObject *)weakvalue {
id (^block)() = objc_getAssociatedObject(self, weakValueKey);
return block();
}
问题11.耗电量如何检测,线上的如何检测
问题12.设计模式,项目中用到的设计模式实例
传统的MVC的认知:
iOS的MVC:
model :处理数据
View: 显示界面
controller:处理一些业务逻辑
Controller请求网络链接,得到的数据update Model,然后model 数据刷新,通过KVO/Notification. 到controller。controller的delegate让view的内容显示发生变更。
缺点:如果controller层面的处理的逻辑太多,将会造成这一次层面的大量臃肿,所以这时候就要想办法处理controller的分离。
而且有时候model会直接和View有直接联系,这样还是产生了耦合
例如cell利用这种方式来计算高度
- (CGFloat)cellHeightWithModel:(SomeModel *)model;
MVVM
这个框架实际就是MVC将controller分离出来的结果
view:是由View和controller组成。负责UI的展示,绑定viewmodel中的属性,触发view model中的命令。
viewModel:从mvc的controller中抽取出来的展示逻辑,负责从model中获取view所需的数据,转换成view可以展示的数据,并暴露公开的属性和命令供View进行绑定。
model :和mvc中的model一致
Binder:在MVVM中,声明式的数据和命令绑定是一个隐含的约定,它可以让开发者非常方便的实现view和view model的同步,避免编写大量繁琐的样板化代码。
绑定是一种响应式的通信方式。当被绑定对象某个值的变化时,绑定对象会自动感知,无需被绑定对象主动通知绑定对象。可以使用KVO和RAC实现。例如在Label中显示倒计时,是V绑定了包含定时器的VM。
双向绑定在MVVM中指的是V和VM之间相互绑定。例如TextField的text长度达到阈值,另一个Button改变背景颜色。这个过程中首先VM感知V中TextField的text属性长度变化,V感知VM中对应的状态属性。一旦V中TextField的text属性长度超出VM中的阈值,VM中的状态属性改变,触发V中Button的背景色发生改变。
MVP
MVP中的V在iOS中指的是ViewController和View。MVP将MVC的ViewController进行拆分:视图数据逻辑处理部分为P,ViewController剩余部分与View合并成V。V和P之间通过Protocol进行通信。
MVP实现了各模块的解藕,具有更好的可测试性。但是总体代码量比MVC大。
另外,iOS MVC更适用于快速开发,即代码规模较小的项目。因此将简单的MVC的Demo改成MVP,反而会显得笨拙。
选择
-
越复杂的框架耦合度越小,但是开发速度越慢,反之亦然。所以要根据具体项目需求,在不同阶段决定框架。
-
如果模式之间存在兼容性,可选择混合开发
问题13.如何实现大量的js交互
问题14.路由的设计模式,中间者存在耦合性
问题15.字典的底层是什么,如何从hash表中写入读取
NSDictionary(字典)是使用hash表来实现key和value之间的映射和存储,hash函数设计的好坏影响着数据的查找访问效率。
hash表其实也是一个数组,区别数组的地方是它会建立 存储的值 到 存储的下标 索引的一个映射,也就是散列函数。
现在我们有个hash表,表长度count = 16,现在我们依次把3,12,24,30依次存入hash表中。
首先我们来约定一个简单的映射关系:存储的索引下表(index) = 存储值(value) % hash表长度(count);
[注:实际的映射并不是简单的存储值,而是经过计算得到的 hash 值]****
算下来hash表的存储分布是这样的:hash[3] = 3、hash[12] = 12、hash[8] = 24、hash[14] = 30
还是一样的需求,当我们给出24的时候,求出hash表中是否存有24?
此时,按照原先约定的映射关系:index = 24 % 16 = 8,然后我们在hash[8]查询等于24。这样,通过数组需要O(n)的时间复杂度,通过hash表只需要O(1);
散列碰撞
上面提到的hash表在存入3,12,24,30后,如果要面临存入19呢?
此时index = 19 % 16 = 3,而之前hash[3] 已经存入了3这个值了!这种情况就是发送了散列碰撞。
此时,我们可以改进一下我们的hash表,让它存储的是一个链表。这样发送散列碰撞的元素就可以以链表的形式共处在hash表的某一个下标位置了。
upload-images.jianshu.io/upload_imag…
所以,只要发生了散列碰撞,我们查找的时间复杂度就不能像O(1)这么小了,因为还要考虑链表的查找时间复杂度O(n)。
负载因子、自动扩容
哈希表还有一个重要的属性: 负载因子(load factor),它用来衡量哈希表的 空/满 程度
负载因子 = 总键值对数 / 箱子个数(hash表长度)
当存储的元素个数越来越多,在hash表长度不变的前提下,发生散列碰撞的概率就会变大,查找性能就变低了。所以当负载因子达到一定的值,hash表会进行自动扩容。
哈希表在自动扩容时,一般会扩容出一倍的长度。元素的hash值不变,对哈希表长度取模的值也会改变,所以元素的存储位置也要相对应重新计算,这个过程也称为重哈希(rehash)。
哈希表的扩容并不总是能够有效解决负载因子过大而引起的查询性能变低的问题。假设所有 key 的哈希值都一样,那么即使扩容以后他们的位置也不会变化。虽然负载因子会降低,但实际存储在每个箱子中的链表长度并不发生改变,因此也就不能提高哈希表的查询性能。所以,设计一个合理有效的散列函数显得相当的有必要,这个合理有效应该体现在映射之后各元素均匀的分布在hash表当中。
**问题16.如何设计单独的功能模块 **
问题17.weak的实现原理
**问题18.Sizeof ,class_getInstanceSize,malloc_size的区别 **
sizeof:
是一个运算符,获取的是类型的大小(int、size_t、结构体、指针变量等),程序编译时获取
class_getInstanceSize:
是一个函数,程序运行时才获取,创建的对象加所有实例变量实际占用的内存大小,
内存对齐一般是以【8】对齐
#import <objc/runtime.h>
malloc_size:
在堆中开辟的大小,向系统申请的空间大小
在Mac、iOS中的malloc函数分配的内存大小总是【16】的倍数,
因为他有一个字节对齐的机制:算法原理:先右移4位 k = (size + 15) >> 4 , 然后结果左移4位 k << 4 ,其中 右移4位 左移4位相当于将后4位抹零,跟 k/16 * 16一样 ,是16字节对齐算法,小于16就成0了
#import <malloc/malloc.h>
2.weak 和 assign 的问题延伸
weak 不会产生野指针问题。因为weak修饰的对象释放后(引用计数器值为0),指针会自动被置nil,之后再向该对象发消息也不会崩溃。 weak是安全的。
assign 如果修饰对象,会产生野指针问题;如果修饰基本数据类型则是安全的。修饰的对象释放后,指针不会自动被置空,此时向对象发消息会崩溃
2.1野指针有指向么?
有的,只不过指向的内存中的对象被释放了,但是还是指向了这个内存地址,指向的是不可用内存,如果再次访问这个内存就会泄漏
2.2new = alloc + init,分配内存+初始化,new一个对象,然后给这个对象赋值,他的内存变么,不变的!对于这个对象已经分配了内存,后面的赋值只是将内存中放有这个值,那么如果是给这个对象增加属性,占用内存是否改变,是改变的,那么系统分配的内存是否改变?一般来说是改变的。
3.经典形容内存泄漏和野指针的说法
问题18.为什么block要使用copy而不是strong或者其他属性修饰?
block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈上的,而不是在堆上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。因为栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.
使用retain也可以,但是block的retain行为默认是用copy的行为实现的,
因为block变量默认是声明为栈变量的,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆,所以说为了block属性声明和实际的操作一致,最好声明为copy。
问题19.OC深入理解+load 和initialize 引发的问题
A.ios runtime之消息转发。www.jianshu.com/p/5127ce062…
B.OC 的方法调用流程 。 www.jianshu.com/p/114782a90…
C、iOS runtime之Class和metaClass。 www.jianshu.com/p/8036f15c9…
D.OC 深入理解+load 和+initialize。www.jianshu.com/p/872447c6d…
E . iOS runtime 之 Category www.jianshu.com/p/8e6518b1b…
问题20.自动释放池的内部实现。 github.com/draveness/a…
问题21.retain和release的黑箱操作
问题21:知识点记录&&面试点
www.cocoachina.com/ios/2015080…
www.cocoachina.com/ios/2018053…
问题22:iOS持久化方式有哪些
首先这里的持久化指的是数据持久化,目前客户端的持久化也只有这一个含义。
为何要持久化:iOS开发可以没有持久化,持久化更多的是业务需求;比如记录用户是否登陆,下次进应用不需要再登陆。
因为iOS的沙盒机制,所以持久化分为两类:沙盒内和沙盒外。
沙盒内
NSKeyedArchiver
只要遵循了NSCoding协议并正确实现了initWithCoder和encodeWithCoder方法的类都可以通过NSKeyedArchiver来序列化。
归档使用archiveRootObject,解归档使用unarchiveObjectWithFile;需要指定文件路径。
NSUserDefaults
[NSUserDefaults standardUserDefaults]获取NSUserDefaults对象,以key-value方式进行持久化操作。
plist
写入使用writeToFile,读取使用xxxWithContentsOfFile;需要指定文件路径。
数据库
数据库无疑是大量数据最好的持久化方案,数据库目前有:sqlite、CoreData和Realm等。这里就不用回答FMDB它只是封装了sqlite而已。
文件
这里要和plist区分一下,plist方式是字典/数组数据格式写入文件;而这里的文件方式不限数据格式。
沙盒外
KeyChain(密码管理系统)
沙盒内的方式在应用被删除后数据都会丢失,如果想要不丢失则需要使用KeyChain。
KeyChain本质是一个sqlite数据库,其保存的所有数据都是加密过的。
KeyChain分为私有和公有,公有则需要指定group,一个group中的应用可以共享此KeyChain。
使用KeyChain过程中要理解下面几个问题:
1:自己使用的KeyChain和系统自带的KeyChain数据是隔离的,内部应该是不同数据库文件;
2:KeyChain数据可备份到iCloud中;
3:不需要联网,也不用登陆iCloud账号;一个设备一个sqlite数据库,但是不同应用组不共享数据;
4:要在另一台设备上使用当前设备存储的KeyChain信息,需要当前设备进行数据备份,
再在另一设备上复原数据;比较常用的是iCloud备份方式;
5:系统自带的KeyChain中账号密码分类数据可在系统设置->账号与密码里面看到,
你退出iCloud账号还是存在,只是iCloud会帮你备份如果你设置了的话;这个和照片是一样的道理。
了解更多
持久化
iOS 数据持久化的几种方法
聊聊iOS KeyChain
问题24:密码系统管理详情
问题25.程序内存分区
以下是比较常用的五分区方式,当然也不排除网上有其他的分区方式。
栈
栈的大小在编译时就已经确定了,一般是2M;栈是一块从高到低地址的连续区域,存放临时变量和执行函数时的内存等。栈内存分配分为动态和静态,静态如自动变量(局部变量)等,动态如alloc等。
堆
堆是从低到高地址的不连续区域,类似链表;用来存放malloc或new申请的内存。
全局/静态
存放静态/全局变量;全局区细分为未初始化/初始化区。
常量
存放常量;程序中使用的常量会到常量区获取。
可以看看这个例子来理解一下。
...int a;//a在全局未初始化区int a = 10;//a在全局初始化区 10在常量区static int a = 10;//a在静态区 10在常量区//程序入口int main(...) { int a = 10;//a在栈区 10在常量区
static int a = 10;//a在静态区 10在常量区
char *a = (char *)malloc(10); //a在栈区 malloc后的内存在堆区
...
}
代码
存放二进制代码,运行程序就是执行代码,代码要执行就要加载进内存(RAM运行内存)。
了解更多
iOS程序中的内存分配分区
iOS基础全局变量·静态变量·局部变量·自动变量
问题26:extern的作用
告诉编译器,这个全局变量在本文件找不到就去其他文件去找。如有必要需要使用#import "x.h"这样编译器才知道到哪里去找。
//.h
int age = 10;//error 不能.h此处声明全局非静态变量,
.m中可以extern int age = 10;//error 和int age = 10;等价extern static int age = 10;//全局静态变量声明不和extern一起用@interface Class : NSObject...@end//.mextern static int age = 10;//全局静态变量声明不和extern一起用@implementation Class { int age;//成员变量不能用作extern;}
- (void)test { extern int age = 10;//error 因为这并不是全局变量
static int age = 10;//error 因为这并不是全局变量
extern int age;//error 因为这并不是全局变量}
...@end
使用extern前要保证对应变量被编译过
//.hextern int age;//error extern在声明前extern static int age;//error extern没有static@interface Class : NSObject...@end//.mstatic int age = 10;@implementation Class ...@end
//.hstatic int age = 10;extern int age;//正确 @interface Class : NSObject...@end//.m@implementation Class ...
- (void)test { extern int age;//正确 声明和extern可以在不同文件中}@end
全局非静态变量
//.hextern int age;//正确@interface Class : NSObject...@end//.mint age = 10;@implementation Class ...@end
了解更多
问题27:指针函数/函数指针/Block
(block的四种类型:有参无返回值,无参无返回值,有参数无返回值,有参数有返回值)
指针函数
C语言的概念;本质是函数,返回指针。
char *fun() { char *p = ""; return p;
}
函数指针
C语言的概念;本质是指针,指向函数。
int fun(int a,int b) { return a + b;
}int (*func)(int,int);
func = fun;
func(1,2);//3
Block
OC语言的概念;表示一个代码块,OC中视为对象;挺像C函数指针的。
//typedeftypedef int (^SumBlock)(int a,int b);
SumBlock sumBlock = ^(int a,int b) { return a + b;
};
sumBlock(1,2);//3//普通
int (^sumBlock)(int a,int b) = ^(int a,int b) { return a + b;
};
sumBlock(1,2);//3
了解更多
指针函数与函数指针(C语言)
iOS开发-由浅至深学习block
**问题28:__weak、__strong、__block理解
我们基本都是ARC环境,所以回答以ARC角度。
__block
讲这个之前,我们需要搞清楚一个概念,这个block存在内存什么区域的?
如果这个block内部没有访问栈、堆的变量,那么这个block存在代码区;反之存在堆区。block内部修改栈区的变量,该变量需要加__block修饰,这样会将变量从栈上复制到堆上。栈上那个变量会指向复制到堆上的变量。block内部修改堆区的变量不用加__block。
因为堆区不断有变量创建和销毁,block作为属性时我们需要加copy或者strong修饰。
在一个 block里头如果使用了在 block之外的变数,会将这份变数先复制一份再使用,也就是,在没有特别宣告下,对于目前的block 来说,所有的外部的变数都是只可读的,不可改的。
如果我们想让某个 block 可以改动某个外部的变数,我们则需要将这个可以被 block 改动的变数前面加上__block。
__weak
__weak我们就在block和声明属性中看到过。
如block是被self强引用的。
@property (nonatomic, copy) void (^Block)(void);
那么在Block内部使用self时,Block内部又会对self进行一次强引用;这就形成了循环引用,所以需要对self进行__weak。
__weak typeof(self) weakSelf = self;
弱引用不会影响对象的释放,当对象被释放时,所有指向它的弱引用都会自定被置为nil。
当然了,self没有强引用block时是不需要__weak的。
- (void)func() { void (^Block)(void) = ^(void) {
[self test];
};
}
__strong
对self进行了__weak,那么在block执行时weakSelf随时可能被释放,所以内部需要对weakSelf进行__strong让self不被释放。
__strong typeof(self) strongSelf = weakSelf;
在block执行完成后,strongSelf会被释放,不会造成循环引用。
了解更多
iOS中block块的存储位置&内存管理
__block & __weak & __strong
问题29:事件传递链/事件响应链
事件传递链
当点击一个按钮的时候,事件如果传递到按钮这个第一响应者上,这就是事件传递链要做的事情。系统根据下面两个方法来传递事件。
//该点是否在本视图点击范围内 point已经被转换了成本视图对应frame- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { //内部实现大概是这样
return CGRectContainsPoint(self.bounds, point);
}//本视图/子视图是否能够传递本事件 point已经被转换了成本视图对应frame- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { //内部实现大概是这样
//用户交互为NO,不处理
if(self.userInteractionEnabled == NO) { return nil;
} if([self pointInside:point withEvent:event]) { NSArray * superViews = self.subviews; //倒序从最上面的一个视图开始查找
for (NSUInteger i = superViews.count; i > 0; i--) { UIView * subview = superViews[i - 1]; //转换坐标系 使坐标基于子视图
CGPoint newPoint = [self convertPoint:point toView:subview]; //得到子视图 hitTest 方法返回的值
UIView * view = [subview hitTest:newPoint withEvent:event]; //如果子视图返回一个view 就直接返回 不在继续遍历
if (view) { return view;
}
} //所有子视图都没有返回 则返回自身
return self;
} return nil;
}
当点击按钮的时候,其实事件是这样传递的:AppDelegate->UIApplication->UIWindow->xxx->UIViewController->UIView->UIButton。
事件响应链
当找到事件第一响应者之后,该事件如何响应,就是事件响应链要做的事情。
接着上面的例子,UIButton就是系统找出来的第一响应者,那么会执行如下方法:
//触摸事件开始- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {}//触摸事件移动- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {}//触摸事件结束- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {}//触摸事件取消- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {}
如果你自己不处理,你可以self.nextResponder让下一个响应者处理。
//触摸事件开始- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { //让下一个响应者处理
[self.nextResponder touchesBegan:touches withEvent:event];
}
...
响应者链也就是传递链的倒序。
这里需要注意的就是,如果给UIButton添加了target和UITapGestureRecognizer那么点击按钮只会执行UITapGestureRecognizer,也就是说如果手势和target同时满足条件则只会执行手势。target也是touchesxxx中判定的,你可以重写touchesxxx内部实现为空,你会发现并不影响手势但会影响target。
了解更多
iOS事件拦截和事件转发
UIView之userInteractionEnabled属性介绍
iOS触摸事件那点儿事
问题30:简述RunLoop
RunLoop是iOS中的Event Loop实现,简单来说是一个do while循环,需要GCD等协作执行;循环体内没事件需要处理就休眠,被mach_port唤醒之后处理相应事件后判断条件继续进入循环。一个线程只能有一个根RunLoop,RunLoop保存在TSD中;一次RunLoop执行只能指定一个RunLoopMode,mode有timer、source、common和observer等;几乎所有的操作都是通过Call out方法进行回调的,比如点击是通过source1到source0再到action回调;要切换mode必须退出当前RunLoop并指定新mode重新执行。
了解更多
iOS刨根问底-深入理解RunLoop
Run Loop 记录与源码注释
深入理解RunLoop
RunLoop 源码阅读
问题31:UIView/CALayer关系
view是layer的代理对象;view负责管理layer,layer负责渲染;view初始化的时候默认会创建一个layer;设置view的frame和bounds等内部其实是修改layer对应属性。
了解更多
问题32:简述你了解的锁
互斥锁:NSLock、pthread_mutex、@synchronized。
加锁后,其他加锁操作阻塞直到解锁。
递归锁:NSRecursiveLock。
一个线程可以多次加锁,相应的要对应多次解锁其他线程才可以加锁。
条件锁:NSCondition、NSConditionLock。
锁满足指定条件时才继续执行,否则阻塞。
信号量:dispatch_semaphore。
wait操作阻塞直到signal被调用。
读写锁:pthread_rwlock。
读模式占有锁时其他线程只能读;写模式占有锁时其他线程不能进行任何操作。
了解更多
问题33:ISO七层、TCP/IP四层协议
ISO七层协议
应用、表示、会话、传输、网络、数据链路、物理。
TCP/IP四层协议
应用、传输、网络、数据链路。
传输层单位:段;
网络层单位:报;
链路层单位:帧。
了解更多
问题34:什么是ARC
ARC是引用计数,是一个简单而有效的管理对象生命周期的方式;编译器在代码合适的地方自动给我们加了一些关键字,比如:retain、release和autorelease等;这样我们就不用手动管理对象生命周期。
了解更多
问题35:iOS通知和协议的区别
一个协议一时间只能有一个代理对象,而一个通知一时间可以有多个监听者。
通知的发送和监听依靠通知中心,协议则可以自己创建,通过setDelegate指定代理对象。
了解更多
IOS中的协议Protocol与代理Delegate以及通知
iOS内存使用注意事项和优化
使用注意事项
访问野指针:数据越界、对象已经释放但对其发送消息等;
内存泄漏:循环引用、imageNamed读取图片等;
触碰内存峰值:for循环声明变量等;
申请了不使用的内存:声明变量但未使用、只在某个逻辑分支用到某些变量但一开始就初始化等。
内存优化
访问野指针:访问前加判断;
这部分问题大多是多线程造成的,比如两个线程同时执行一个方法,方法内部对数组有更新操作。
内存泄漏:Instrument Leaks/Allocations检测、使用imageWithContentsOfFile读取图片等;
imageNamed会缓存加载的图片;imageWithContentsOfFile只是简单的加载图片。
触碰内存峰值:手动添加释放池;
for循环内大量创建局部变量,这些局部变量会等到RunLoop的下一个循环才释放,
而手动加入释放池则会提前释放。
申请了不使用的内存:懒加载。
问题36:@synthesize和@dynamic的作用
@synthesize
如果属性没有自动生成getter/setter方法,则告诉编译器去生成。
@dynamic
告诉编译器不要生成此属性的getter/setter方法,开发者自己去实现。
了解更多
iOS @property、@synthesize和@dynamic
问题37:AFNetWorking作用
我看的是AFNetWorking 3.0版本,是基于NSURLSession封装的,NSURLSession使用起来已经足够方便了,所以AFNetWorking做的内容相对来说少了很多。
1:设置请求/响应序列化对象,这样可以帮你检查请求/响应参数是否正确;
2:https验证功能,可以用于自签名证书等特殊情况,如果不启用则让系统帮你验证,
那么就只能是苹果官方认可的CA证书才能通过;
3:对于不同的请求方式和参数,帮你设置请求,比如头部的Content-type。
了解更多
Http协议特点,GET/POST请求区别
Http协议特点,基于Http2.0
快速:协议简单,通讯速度快;
灵活:通过Content-Type可以传递任何类型的数据;
长连接:一次连接处理多个请求,并支持管线(同时发出多个请求,不用等到前面请求响应)、多路复用(一个请求多次响应);
无状态:协议对于事务处理没有记忆能力,如果本次请求需要上次的数据则需要重传。
GET/POST请求区别
GET在URL后以?形式拼接参数,POST将参数放在body中;
GET也可以在body里面放数据,POST也可以在URL放数据,Http只是规定,你也可以不遵守啊。
至于服务器获不获取就看具体实现了。
POST比GET安全,因为GET参数在URL中,用户一眼就能看到;
POST当然也可以用工具看到参数。
语意理解,GET用来获取数据,POST用来上传数据;
客户端和服务器对URL数据一般有1kb的限制。
Http协议对URL并没有限制。
了解更多
iOS-网络编程(一)HTTP协议
http2.0的时代真的来了...
Socket连接和Http连接区别
Http是基于Tcp的,而Socket是一套编程接口让我们更方便的使用Tcp/Ip协议;Http是应用层协议,在Tcp/Udp上一层。
1:Http是基于"请求-响应"的,服务器不能主动向客户端推送数据,只能借助客户端请求到后向客户端推送数据,而Sokcet双方随时可以互发数据;
2:Http不是持久连接的,Socket用Tcp是持久连接;
3:Http基于Tcp,Socket可以基于Tcp/Udp;
4:Http连接是通过Socket实现的;
5:Http连接后发送的数据必须满足Http协议规定的格式:请求头、请求头和请求体,而Socket连接后发送的数据没有格式要求。
了解更多
Tcp三次握手、四次挥手
Tcp是传输层可靠传输协议,发送数据前要进行握手,数据发送完时可以挥手断开连接释放资源;挥手比握手多一次是因为断开需要双方单独断开,而握手被连接端是被动打开的。
首先,我们必须要明白一些重要的Tcp头部标志。
序号:每一个包都有一个序号,此序号mod2^32;
确认号:用来确认收到对方的包,此序号mod2^32;
SYN:为1时表示希望与对方建立连接;
FIN:为1时表示我已经没有数据发送了,希望断开连接;
ACK:为1时确认号有效,Tcp规定除了第一次建立连接的包ACK都要置为1。
这是我抓的访问百度握手/挥手过程,Http协议也是基于Tcp的。
10.10.9.141:客户端ip;180.97.33.107:百度ip。
第一次握手 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 64)
10.10.9.141.50806 > 180.97.33.107.http: Flags [S], cksum 0x95b1 (correct), seq 589117916, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 815096723 ecr 0,sackOK,eol], length 0
第一次握手 SYN=1 seq=589117916 第二次握手 IP (tos 0x0, ttl 53, id 0, offset 0, flags [DF], proto TCP (6), length 64)
180.97.33.107.http > 10.10.9.141.50806: Flags [S.], cksum 0x2cb0 (correct), seq 1775662715, ack 589117917, win 8192, options [mss 1408,nop,wscale 5,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,sackOK,eol], length 0
第二次握手 SYN=1 ACK=1 seq=1775662715 ack=589117917第三次握手 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
10.10.9.141.50806 > 180.97.33.107.http: Flags [.], cksum 0xa25d (correct), seq 589117917, ack 1775662716, win 8192, length 0
第三次握手 ACK=1 seq=589117917 ack=1775662716数据传输...
数据传输...
第一次挥手 IP (tos 0x0, ttl 50, id 43779, offset 0, flags [DF], proto TCP (6), length 40)
180.97.33.107.http > 10.10.9.141.50806: Flags [F.], cksum 0xb3c8 (correct), seq 1775665497, ack 589118060, win 808, length 0
第一次挥手 FIN=1 ACK=1 seq=1775665497 ack=589118060第二次挥手 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
10.10.9.141.50806 > 180.97.33.107.http: Flags [.], cksum 0x973b (correct), seq 589118060, ack 1775665498, win 8117, length 0
第二次挥手 ACK=1 seq=589118060 ack=1775665498第三次挥手 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
10.10.9.141.50806 > 180.97.33.107.http: Flags [F.], cksum 0x96ef (correct), seq 589118060, ack 1775665498, win 8192, length 0
第三次挥手 ACK=1 FIN=1 seq=589118060 ack=1775665498第四次挥手 IP (tos 0x0, ttl 50, id 0, offset 0, flags [DF], proto TCP (6), length 40)
180.97.33.107.http > 10.10.9.141.50806: Flags [.], cksum 0xb3c7 (correct), seq 1775665498, ack 589118061, win 808, length 0
第四次挥手 ACK=1 seq=1775665498 ack=589118061
了解更多
通俗大白话来理解TCP协议的三次握手和四次分手
tcpdump查看三次握手
问题38:main方法前过程
1:系统内核做好准备工作,比如把应用数据从ROM移到RAM;
2:libdyld接管后续工作,加载动态库到内存等;
3:ImageLoader把动态库和可执行文件加载到内存;
4.1:Runtime解析文件符号表,注册类、调用load方法;
4.2:libdispath等其他库初始化;
5:libdyld调用main方法。
了解更多
问题39:线程安全方法
线程安全:多线程环境下保证数据的完整性。
队列
把操作放入队列线性执行,可用GCD和NSOperationQueue。
锁/信号量
用锁/信号量形成操作互斥。
让操作原子化
让操作原子执行,系统提供了一些原子执行的方法。
了解更多
问题40:NSOperationQueue和GCD区别联系
区别
NSOperationQueue没有串行/并发队列,但可以设置最大并发数;
NSOperationQueue支持方法和block,GCD只支持block;
NSOperationQueue可以暂停/取消操作;
NSOperationQueue支持更多的功能,比如KVO和自定义操作;
NSOperationQueue可以设置操作的优先级,GCD只能设置队列的优先级。
联系
提供的功能是相似的;
NSOperationQueue是GCD的封装。
了解更多
问题41:iOS常用设计模式
其实设计模式有很多,你只要答上6个就可以了。
装饰模式:分类;
代理模式:协议;
工厂模式:UIButton创建;
原型模式:[object copy];
观察者模式:KVO;
迭代器模式:数组的遍历;
单例模式:Appdelegate;
命令模式:给对象发消息;
职责链模式:事件传递链;
中介者模式:模块解耦;
解释器模式:Siri语意识别;
了解更多
问题42:简述Block
Block本质就是函数,根据有无返回值/参数有4种Block;
在ARC下根据是否访问栈/堆变量可分为全局/堆Block;
Block内修改栈变量时需要__block修饰,__block的作用其实就是在堆上创建一个指向栈变量的指针达到修改栈变量值的目的。
了解更多
问题42:消息动态处理/转发流程
动态处理
当向对象发送了一个不存在的消息时,会先走resolvexxxMethod动态处理流程,你可以在这之中用Runtime动态添加该方法;你也可以不处理,则进入转发流程。
转发
先走forwardingTargetForSelector,你可以在这里返回一个可以处理aSelector的对象,否则走forwardInvocation操作anInvocation;forwardInvocation需要实现methodSignatureForSelector得到一个方法签名。
了解更多
weak变量怎么置为nil
系统维护了一个weak变量组成的hash表,key为weak指向的变量地址,value为weak变量的地址;当对象引用计数为0时,遍历hash表,设置对应的weak变量为nil。
了解更多
对nil发消息会发生什么
我们可以从Runtime的源码中看到,发消息最终会走objc_msgSend()并把nil最为第一个参数,其内部用汇编实现,里面会判断第一个参数是否为nil,如果为nil则返回0,所以iOS允许对nil发送消息;这个0针对不同的selector返回值有不同的表示,比如:0、nil和结构体等。
了解更多
问题43:UITableView优化方法
cell重用,异步执行耗时操作这种就不用提了,这都是大家能想到的,我们可以说一点其他的。
1:减少视图层次;
2:正确使用api,比如设置rowHeight而不去取dataSource;
3:减少离屏渲染;
圆角图片
阴影
...
4:设置视图不透明减少渲染代价,如果有透明度则会根据多个视图才知道一个像素点显示什么颜色;
5:RunLoop空闲计算cell高度并缓存;
6:利用RunLoop的mode在滚动时不执行耗时操作。
了解更多
问题44:ssl/tls证书作用
如果没有ssl/tls证书,每个不同客户端访问服务器都需要生成一对密钥,这会造成服务器端存储的密钥太多等问题;让所用客户端使用统一的服务器ssl/tls证书则可以解决这些问题。
https=http+ssl/tls,拿我们客户端请求api来说,我们适配https时要后台给我们一个cer证书,这个证书里面包括了:颁发机构、有效期、RSA公钥和Hash指纹等信息;客户端会把这个证书交给请求库,请求库负责完成加/解密;为什么需要从颁发机构申请证书呢?因为我们的api和web可能在一个域名下,一个域名只能一个证书,也就是说如果只有api访问这个域名我们完全可以自己创建一个证书设置受信用;然而如果web也要访问该域名则浏览器不会认为这个证书受信用会拒绝访问。
了解更多
HTTPS 原理详解
图解 HTTPS:Charles 捕获 HTTPS 的原理
问题46:安全区域的理解
SafeArea是View的属性,是iOS11出来用来代替bottomLayoutGuide/topLayoutGuide的,bottomLayoutGuide/topLayoutGuide是ViewController的属性;从这里可以看出,SafeArea更灵活,可以对每一个View进行配置;他们都是让控件不被父View遮挡系统自动计算的距离,你当然也可以关闭这个功能;iOS11前用automaticallyAdjustsScrollViewInsets,iOS11后用contentInsetAdjustmentBehavior。
启动页优化:二进制重排
Pre-main
TCP:对头阻塞,TCP滑动窗口原理,TCP流量控制原理
HTTP3.0的知识
HTTP的发展史:0.9,1.0,1.1,1.2,3.0各自的特点,都存在什么问题
HTTPS的加密算法,对称如何加密的,非对称如何加密的,原理是什么
函数调用栈的一些获取
设计一个收集崩溃信息的工具
DSYM ,bugly是什么搞的
Weak的底层实现,hash表,为什么这么设计,有没有其他的设计思路,里面所设计到的锁,比如以前weak下面用的自旋锁,为何要换成互斥锁,换了互斥锁为何不会出现优先级反转,说出原因
超大图片怎么展示在手机上,怎么处理?说了分片下载还不行,还得说如何控制线程数量,然后会问你下载完的分片信息,如何能保证他按照顺序放到对应位置上,如果下载了一部分,他滑动页面,为何不会出现卡顿,解释理由
然后就是问离屏渲染的本质,什么导致离屏渲染的本质是因为啥,还有就是屏幕出现了裂屏问题,是什么原因导致
然后就是自动释放池,自动释放池需要你讲清楚里面的双向链表AutoreleasePage
什么叫函数式编程,响应式编程里面的流,什么叫流
一张图片,如何计算图片的大小
多线程就问一个子线程任务,如何同步到主线程去,思路
如何监控一个对象从分配内存,到函数调用,到销毁的整个过程,如何计算他在这个过程中内存分配大小,叫你写一个这种工具,你需要怎么写
如何异步绘制多个cell,到帧缓冲区,方便下次使用
还有就是main函数之前做了哪些事情
EAS加密,如何防止被解密
然后接着就是一个程序从Txt区段,读取,进过了哪些步骤编程可执行程序的,这个你先要从宏观说,从硬盘读取->加入内存->CPU->控制设备,这种宏观层面说了以后,还得说预编译->编译->汇编->链接,这些过程,中间做了哪些事情,然后就是一个函数->虚拟符号表->物理内存之间的映射,他是怎么做的(重要)
你为你的网络框架做了哪些优化
DNS解析过程,你需要讲,然后你们公司是如何做DNS缓存的e,你们DNS缓存成功率如何,是否做了相关统计,怎么统计的
然后冷启动优化,你做了哪些事情,最后你如何监控的
还有采用NSURLProctol实现请求拦截,这个怎么做,有哪些坑,主要想问你有哪些坑
然后应用瘦身问题,如何瘦身
还有就是问,网上的那些无损压缩图片的工具,他们的实现原理是怎么样的
1.NSString使用copy和strong修饰的区别
回答:
A 当string为不可变字符串时
不管是strong还是copy属性的对象,其指向的地址都是同一个,即为string指向的地址。
当string的值发生改变时,两个对象的值也保持原来的值
如果我们换作MRC环境,打印string的引用计数的话,会看到其引用计数值是3,即strong操作和copy操作都使原字符串对象的引用计数值加了1
B.当string为不可变字符串时
此时copy属性字符串已不再指向string字符串对象,而是深拷贝了string字符串,并让_copyedStr对象指向这个字符串
_strongStr与string是指向同一对象,所以_strongString的值也会跟随着改变(需要注意的是,此时_strongStr的类型实际上是NSMutableString,而不是NSString);而_copyedStr是指向另一个对象的,所以并不会改变。
在MRC环境下,打印两者的引用计数,可以看到string对象的引用计数是2,而_copyedStr对象的引用计数是1
由于NSMutableString是NSString的子类,所以一个NSString指针可以指向NSMutableString对象,让我们的strongString指针指向一个可变字符串是OK的。
而上面的例子可以看出,当源字符串是NSString时,由于字符串是不可变的,所以,不管是strong还是copy属性的对象,都是指向源对象,copy操作只是做了次浅拷贝。
当源字符串是NSMutableString时,strong属性只是增加了源字符串的引用计数,而copy属性则是对源字符串做了次深拷贝,产生一个新的对象,且copy属性对象指向这个新的对象。另外需要注意的是,这个copy属性对象的类型始终是NSString,而不是NSMutableString,因此其是不可变的。
这里还有一个性能问题,即在源字符串是NSMutableString,strong是单纯的增加对象的引用计数,而copy操作是执行了一次深拷贝,所以性能上会有所差异。而如果源字符串是NSString时,则没有这个问题。
所以,在声明NSString属性时,到底是选择strong还是copy,可以根据实际情况来定。不过,一般我们将对象声明为NSString时,都不希望它改变,所以大多数情况下,我们建议用copy,以免因可变字符串的修改导致的一些非预期问题。
2.16进制的0x01020304转换为0x02010403
将分成四个byte,01,02,03,04,然后位移
3.podfile,podSepc,还有podlock 的功能作用
Podfile.lock就是当团队中的某一人pod install之后,就会生成Podfile.lock文件,记录了当时pod记录下了当时最新Pods依赖库的版本,这时团队中的其它人check下来这份包含Podfile.lock文件的工程以后,再去执行pod install命令时,获取下来的Pods依赖库的版本就和最开始用户获取到的版本一致。如果没有Podfile.lock文件,后续所有用户执行pod install命令都会获取最新版本的SBJson,这就有可能造成同一个团队使用的依赖库版本不一致,
4.block作为属性,变量,参数的样式
5.git add怎么恢复操作
1.组件化以及模块化。framework的制作
2.多线程,同异步问题,锁的使用
A. 线程安全问题——多个线程都去修改某一个属性的值,这样就会造成结果混乱。这里可以采用同步锁或者自旋锁的方式来解决
同步锁:@synchronized(锁对象){
//需要锁定的代码
}
自旋锁:加了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线 程会用死循环的方式,一直等待锁定的代码执行完成。相当于不停尝试执行代码,比较消耗性能。属性修饰atomic本身就有一把自旋锁。atomic线程安全,但是耗费资源大。
B.几种常见的线程使用
NSThread
GCD。分为同步和异步,然后队列有串行队列和并发队列
NSOperation:NSOperation是基于GCD之上的更高一层封装
问题拓展:
www.cocoachina.com/ios/2018041…
• iOS的多线程方案有哪几种?你更倾向于哪一种?
4⃣️种,NSThread,GCD,NSOperation.倾向于GCD
- GCD的队列类型。
串行队列,并发队列,主队列 - 说一下OperationQueue和GCD的区别,以及各自的优势
- Operation是在GCD基础上的一次封装
性能:①:GCD更接近底层,而NSOperationQueue则更高级抽象,所以GCD在追求性能的底层操作来说,是速度最快的。这取决于使用Instruments进行代码性能分析,如有必要的话
②:从异步操作之间的事务性,顺序行,依赖关系。GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持
③:如果异步操作的过程需要更多的被交互和UI呈现出来,NSOperationQueue会是一个更好的选择。底层代码中,任务之间不太互相依赖,而需要更高的并发能力,GCD则更有优势
-
线程安全的处理手段有哪些?
-
互斥锁和自旋锁
-
OC你了解的锁有哪些?在此基础上进行二次提问“
1.自旋和互斥对比
2.使用以上锁需要注意哪些?
3.用C/OC/C++,任选其一,实现自旋或互斥。 -
@synrochzied(self){
-
//需要锁定的代码块
-
}
-
GCD中死锁的问题
面试题一:以下代码是否会发生死锁:
- - (void)viewDidLoad {
[super viewDidLoad];
//问题:以下代码在主线程中执行,会不会产生死锁?
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
}
很显然这会奔溃,因为发生了死锁的问题。
我们知道,dispatch_sync()是同步执行,不会开辟新线程,并且要dispatch_sync()的block执行完了才会继续往下执行,所以任务2是加入到了主队列中。主队列是串行队列,,所以会串行执行加入其中的任务,等一个任务执行完了再执行另外一个,在任务2加入主队列之前,viewDidLoad这个大任务已经加入了主队列,所以任务2要等ViewDIdLoad执行完,才会执行任务2,也就是等到任务3执行完,才会执行任务2,但是由于任务3在任务2后面,所以要等到任务2执行完了,才执行任务3。这样就造成了任务2等任务3,任务3等任务2,造成死锁。****
面试题二:以下代码是否会发生死锁:
- (void)viewDidLoad {
[super viewDidLoad];
//问题:以下代码在主线程中执行,会不会产生死锁?
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
}
为什么把同步改成异步,就不死锁了呢?我们分析一下,由于队列是主队列,一定是把任务2加到主队列中,并且在此之前viewDidLoad的任务已经加入到了主队列中,所以要viewDidLoad执行完了才能执行任务2,由于dispatch_async()是异步执行,所以不用等到任务2执行完了再执行任务3,可以直接执行任务3,任务3执行完了,viewDidLoad也就执行完了,也就可以执行任务2了,所以打印的结果一定是先执行任务3再执行任务2。
面试题:问下列代码的打印结果:
dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue1, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
});
-(void)test{
nslog(@“2”);
}
这种情况下只打印了1,3.然而2却没有打印,如果将中间那句换成[self performSelector:@selector(test) withObject:nil];
打印结果就是1,2,3.
说明这样是能成功执行test函数的,我们在runtime中找一下- (id)performSelector:(SEL)aSelector withObject:(id)object;的源码:
- (id)performSelector:(SEL)sel withObject:(id)obj {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}
我们再修改一下代码,让其直接在主线程中执行:
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
那么就有理由猜测NSLog(@"1"); [self performSelector:@selector(test) withObject:nil afterDelay:.0]; NSLog(@"3");的执行和线程有关。
[self performSelector:@selector(test) withObject:nil afterDelay:.0];这句代码的本质是往runloop中去添加一个NSTimer,由于主线程中有runloop,所以可以正常执行,但是在子线程中默认是没有启动runloop的,所以NSTimer也就没有办法成功执行
我们可以启动子线程中的runloop试一下:
dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue1, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
打印结果:
1
3
2
C.多线程的高级运用(信号量)
www.cocoachina.com/ios/2018080…
信号量的本质是数据操作锁, 它本身不具有数据交换的功能,而是通过控制其他的通信资源来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能.
信号量就是一个资源计数器,对信号量有两个操作来达到互斥,分别是P和V操作(信号通知和信号等待)。 一般情况是这样进行临界访问或互斥访问的: 设信号量值为1, 当一个进程1运行是,使用资源,进行P操作,即对信号量值减1,也就是资源数少了1个。这是信号量值为0。系统中规定当信号量值为0是,必须等待,知道信号量值不为零才能继续操作。 这时如果进程2想要运行,那么也必须进行P操作,但是此时信号量为0,所以无法减1,即不能P操作,也就阻塞。这样就到到了进程1排他访问。 当进程1运行结束后,释放资源,进行V操作。资源数重新加1,这是信号量的值变为1, 这时进程2发现资源数不为0,信号量能进行P操作了,立即执行P操作。信号量值又变为0,次数进程2咱有资源,排他访问资源,这就是信号量来控制互斥的原理。
总的来说,信号量为0时就阻塞线程,大于0就不会阻塞,通过改变信号量的值控制线程的阻塞,达到线程的同步。
- dispatch_semaphore_create: 创建一个信号量,具有整形的数值,即为信号的总量。
dispatch_semaphore_signal :
- 返回值为long类型,当返回值为0时,表示当前并没有线程等待其处理的信号量,其处理的信号总量增加1。
- 当返回值不为0时,表示其当前有一个或者多个线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级的时候,唤醒优先级最高的线程,否则随机唤醒)。
dispatch_semaphore_wait:
-
等待信号,具体操作是首先判断信号量desema是否大于0,如果大于0就减掉1个信号,往下执行;
-
如果等于0函数就阻塞该线程等待timeout(注意timeout类型为dispatch_time_t)时,其所处线程自动执行其后的语句。
3.runtime和runloop的开发实际应用
Runtime :运行时
Objective-C 是基于 C 的,它为 C 添加了面向对象的特性。它将很多静态语言在编译和链接时期做的事放到了 runtime 运行时来处理,可以说 runtime 是我们 Objective-C 幕后工作者。 1.runtime(简称运行时),是一套 纯C(C和汇编)写的API。而 OC 就是运行时机制,也就是在运行时候的一些机制,其中最主要的是 消息机制。
对于 C 语言,函数的调用在编译的时候会决定调用哪个函数。
3.运行时机制原理:OC的函数调用称为消息发送,属于 动态调用过程。在 编译的时候 并不能决定真正调用哪个函数,只有在真 正运行的时候 才会根据函数的名称找到对应的函数来调用。
我们写 OC 代码,它在运行的时候也是转换成了 runtime 方式运行的。任何方法调用本质:就是发送一个消息(用 runtime发送消息,OC 底层实现通过 runtime 实现),每一个 OC 的方法,底层必然有一个与之对应的 runtime 方法。
Runtime的实际开发应用:
1.交换方法(交换类与类之间的方法或者第三方框架或者说是系统原生方法在不影响原有的基础上的功能,添加额外的功能)
方案一:继承系统的类,重写方法.(弊端:每次使用都需要导入) 方案二:使用 runtime,交换方法.
步骤: 1.给系统的方法添加分类 2.自己实现一个带有扩展功能的方法 3.交换方法,只需要交换一次,
2.程序运行过程中,动态的为某个类添加或者修改属性/方法
场景:给系统的类添加额外属性的时候,可以使用runtime动态添加属性方法。 原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。 注解:给系统 NSObject 添加一个分类,我们知道在分类中是不能够添加成员属性的,虽然我们用了@property,但是仅仅会自动生成get和set方法的声明,并没有带下划线的属性和方法实现生成。但是我们可以通过runtime就可以做到给它方法的实现。
3.字典转模型
4.动态添加方法
因为只要类实现了方法,就会加载进内存,这样非常耗费内存,当我们不需要那么多的方法时候,我们可以采用runtime动态加载的方式。
5.实现NSCoding的自动归档和解档
所谓文件归档,就是把需要存储的对象数据存储到沙盒的Documents目录下的文件中,即存储到了磁盘上,实现数据的持久性存储和备份。解归档,就是从磁盘上读取该文件下的数据,用来完成用户的需求。对象归档是将对象归档以文件的形式保存到磁盘中(也称为序列化,持久化),使用的时候读取该文件的保存路径的读取文件的内容(也称为接档,反序列化),(对象归档的文件是保密的,在磁盘上无法查看文件中的内容,而属性列表是明文的,可以查看)。
通过文件归档产生的文件是不可见的,如果打开归档文件的话,内容是乱码的;它不同于属性列表和plist文件是可见的,正因为不可见的缘故,使得这种持久性的数据保存更有可靠性。
步骤:
首先,获取沙盒的Documents目录,并通过拼接方式在该目录下创建一个文件夹用来存储对象数据;
其次,准备需要归档的对象数据;
然后,用相应的方法对数据进行归档,即将数据存储到该文件夹下;
最后,用相应的方法对数据进行解归档,即从该文件夹下获取存储的数据
上面说的都是单个对象的实现,而不适用于多对象的归档。
多对象的归档的步骤如下:
1.获取的文件路径(归档文件名可以自己随意取名)
NSArray *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = [documents lastObject];
NSString *archiveFileName = [documentPath stringByAppendingPathComponent:@"objects.arc"];
2.准备要归档的多个归档对象数据
NSString *useName = @"admin";
NSArray *scores = @[@90,@89,@90.5,@98];
NSString *adress = @"北京市昌平区东三旗";
3.对归档对象进行归档
1 //归档多个对象(将多个对象读到data中)
2 //1.用一个可变的data对象创建归档对象
3 NSMutableData *data = [[NSMutableData alloc]init];
4 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
5
6 //2.归档多个对象
7 [archiver encodeObject:useName forKey:@"userName"];
8 [archiver encodeObject:scores forKey:@"scores"];
9 [archiver encodeObject:adress forKey:@"adress"];
10
11 //3.完成归档(必须要实现的方法,要不然归档失败)
12 [archiver finishEncoding];
13
14 //4.将可变的data写进文件
15 [data writeToFile:archiveFileName atomically:YES];
说明:这种归档方式,其实就是先将所有的要归档的对象先存入到一个可变的NSMutableData对象data中,然后通过data创建的归档对象将data的数据归档到归档文件中就是了。
4.将归档对象进行解归档
1 //解归档多个对象
2 //1.从文件中读出一个data
3 NSData *data2 = [NSData dataWithContentsOfFile:archiveFileName];
4 //2.由data创建解归档对象
5 NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data2];
6
7 //3.按照key解出多个对象
8 NSString *userName2 = [unarchiver decodeObjectForKey:@"userName"];
9 NSArray *scores2 = [unarchiver decodeObjectForKey:@"scores"];
10 NSString *adress2 = [unarchiver decodeObjectForKey:@"adress"];
11
12 NSLog(@"uesrName:%@,scores:%@,adress:%@",userName2,scores2,adress2);
说明:这种解归档方式,其实就是从归档文件先读取data,然后通过data创建解归档对象根据指定的键将多个对象都解归档出来就是了。
4.block , delegate , KVC的区别和底层解析
block的运用
1.首先,就是block与变量的运用,分为全局变量和局部变量。如果就是全部变量,在block内部是可以随时被修改的,但是如果是局部变量,那么就需要用到__block修饰变量。
2.block的调用分为三大步,声明,调用和实现,使用起来相对delegate比较简单,因为它不需要构建协议之类的,但是使用过程中需要注意以下几点问题。
A.首先使用方将self或成员变量加入block之前要先将self变为__weak,这样避免造成循环引用。
B.在多线程环境下(block中的weakSelf有可能被析构的情况下),需要先将self转为strong指针,避免在运行到某个关键步骤时self对象被析构。
C.在使用block前需要对block指针做判空处理。不判空直接使用,一旦指针为空直接产生崩溃。
D.在block使用之后要对,block指针做赋空值处理,如果是MRC的编译环境下,要先release掉block对象。block作为类对象的成员变量,使用block的人有可能用类对象参与block中的运算而产生循环引用。将block赋值为空,是解掉循环引用的重要方法。(不能只在dealloc里面做赋空值操作,这样已经产生的循环引用不会被破坏掉)
delegate
代理是一种设计模式,它的运行效率较高,和通知有一些类似,但是它是一对一,通知却可以一对多,而且一般都是采用assign来修饰,避免造成循环引用的问题。
Notification
采用的是一对多,或者一对一都是可以的,而且通知使用要记得移除。
KVC /KVO
KVO这个底层涉及到runtime的实现,就是动态在子类中添加了方法,从而实现一对一访问属性的机制。
5.系统框架MVC,MVVM,MVP
MVC:
model :处理数据
View: 显示界面
controller:处理一些业务逻辑
Controller请求网络链接,得到的数据update Model,然后model 数据刷新,通过KVO/Notification. 到controller。controller的delegate让view的内容显示发生变更。
缺点:如果controller层面的处理的逻辑太多,将会造成这一次层面的大量臃肿,所以这时候就要想办法处理controller的分离。
而且有时候model会直接和View有直接联系,这样还是产生了耦合
MVP:
MVP中的V在iOS中指的是ViewController和View。MVP将MVC的ViewController进行拆分:视图数据逻辑处理部分为P,ViewController剩余部分与View合并成V。V和P之间通过Protocol进行通信。
这样做的话,减小了耦合度。
MVVM:
view:是由View和controller组成。负责UI的展示,绑定viewmodel中的属性,触发view model中的命令。
viewModel:从mvc的controller中抽取出来的展示逻辑,负责从model中获取view所需的数据,转换成view可以展示的数据,并暴露公开的属性和命令供View进行绑定。
model :和mvc中的model一致
Binder:在MVVM中,声明式的数据和命令绑定是一个隐含的约定,它可以让开发者非常方便的实现view和view model的同步,避免编写大量繁琐的样板化代码。
绑定是一种响应式的通信方式。当被绑定对象某个值的变化时,绑定对象会自动感知,无需被绑定对象主动通知绑定对象。可以使用KVO和RAC实现。例如在Label中显示倒计时,是V绑定了包含定时器的VM。
双向绑定在MVVM中指的是V和VM之间相互绑定。例如TextField的text长度达到阈值,另一个Button改变背景颜色。这个过程中首先VM感知V中TextField的text属性长度变化,V感知VM中对应的状态属性。一旦V中TextField的text属性长度超出VM中的阈值,VM中的状态属性改变,触发V中Button的背景色发生改变。
6.数据库操作,FMDB,sqlite等,其他的数据缓存的方式有Plist,归档,偏好设置,沙盒文件,sqlite数据库
归档:使用归档保存数据只能一次性归档保存和一次性解档,而且这种方法只适合小数据保存,如果想改动局部数据就要解压整个归档数据或归档整个数据,操作比较笨拙。
NSUserDefaults:用户开机后或者再次打开应用程序,这些保存的数据仍然存在;如果清除了缓存,那么这个数据就清除掉了。可存储的数据类型包括NSData,NSString,NSDate,NSNumber,NSArray,NSDictionary,如果需要存储其他类型,需要先转换为可存储类型再进行存储操作。
沙盒文件:简单说沙盒就是与其他文件系统隔离的一个应用文件夹,应用必须待在自己的沙盒里,其他应用不能访问该沙盒。沙盒内的文件目录主要有三个.
(1)Documents目录:主要用来保存应用运行时生成的需要持久化的数据 ;
(2)Library/Caches目录: 保存应用运行时生成的一些缓存数据 , iTunes同步设备时不会备份该目录;
(3)Library/Preference目录: 保存应用的所有偏好设置信息 ;****
(4)tmp: 保存应用运行时所需的临时数据;****
SQLite:以SQLite为例,它是一种轻量级数据库,可移植性好,内存开销小,可以保存任意类型的数据。但保存数据的方式相对前面三种复杂一些,保存大量数据时性能还是不错的。
FMDB:iOS中使用C语言函数对原生SQLite数据库进行增删改查操作,复杂麻烦,于是,就出现了一系列将SQLite API封装的库,如FMDB
FMDB是针对libsqlite3框架进行封装的三方,它以OC的方式封装了SQLite的C语言的API,使用步骤与SQLite相似.
FMDB的优点是:
(1) 使用时面向对象,避免了复杂的C语言代码
(2) 对比苹果自带的Core Data框架,更加轻量级和灵活
(3) 提供多线程安全处理数据库操作方法,保证多线程安全跟数据准确性
FMDB缺点:
(1) 因为是OC语言开发,只能在iOS平台上使用,所以实现跨平台操作时存在限制性
2.主要类型
FMDatabase:一个FMDatabase对象代表一个单独的SQLite数据库,通过SQLite语句执行数据库的增删改查操作
FMResultSet:使用FMDatabase对象查询数据库后的结果集
FMDatabaseQueue:用于多线程操作数据库,它保证线程安全
3.FMDB使用方式
(1) GItHub中下载FMDB,将文件导入工程中
(2) 导入libsqlite3.0框架,导入头文件FMDatabase.h
(3) 创建数据库路径,创建数据库,操作前开启数据库,构建操作SQLite语句,数据库执行增删改查操作,操作完关闭数据库
FMDB中的事务:事务(Transaction)是不可分割的一个整体操作,要么都执行,要么都不执行。
FMDB中有事务的回滚操作,也就是说,当一个整体事务在执行的时候出了一点小问题,则执行回滚,之后这套事务中的所有操作将整体无效。
Core Data:这是iOS自带的一款数据库,Core Data有着图形化的操作界面,并且是操作模型数据的,
7.熟悉使用git,svn , 写博客
常用的工具是SourceTree,通过远程push可以直接上传代码到git上,提交,推送,分支,合并等一系列的操作。
8.网络框架的源码学习和二次封装
A.数据库FMDB的二次封装
首先做的事简易的封装,就是将sql语句封装,放在一个操作类中,类中的函数中执行sql语句
B.网络请求框架AFNetworking的二次封装(这里要注意CA证书的校验,有预埋证书和非预埋证书的差别)
C.对.a库的二次封装(就是将我们常用的一些第三方的SDK,然后继续封装为自己的静态库,这样可以直接在多个项目中直接调用)
9.UI页面特别效果处理,动画,瀑布效果,朋友圈的开发
A.tableView的高度自适应,比如朋友圈的绘制
B.瀑布效果,就是比如商城的商品展示
实现瀑布流,一般有三种常见的方式。
1,使用UIScrollView,主要技术点在于视图的重用。
2,使用UITableView,这种方式应该是最易想到的,因为需要展现几列就用几个tabelview就ok了,而且不需要考虑重用,因为苹果已经做好了,只需要考虑如何在几列tabelView滑动的时候,保持同步不出现BUG。
3,使用UICollectionView,UICollectionView在iOS6中第一次被介绍,它与UITableView有许多相似点,但它多了一个布局类,而实现瀑布流,就与这个布局类有关。此种方式实现,也不需要考虑视图重用。
C.影片的选择。
10.XIB和StoryBoard的快速开发
11.算法学习,特别是链表,二叉树,图的学习
12.基本的知识点深入学习(比如引用计数)
13.运用到的加密方式以及差别,详解
14.什么是OC中的反射机制?
简单使用:
class反射
通过类名的字符串形式实例化对象
Class class NSClassFromString@(@"student");
Student *stu = [[class alloc ]init];
将类名变为字符串
Class class =[Student class];
NSString *className = NSStringFromClass(class);
SEL的反射
通过方法的字符串形式实例化方法
SEL selector = NSSelectorFromClass(@"setName");
[stu performSelector:selector withObject:@"Mike"];
将方法变成字符串
NSStringFomrSelector(@selector*(setName:))
既然是这样,我们就可以根据服务端返回的字段来获取方法,创建对象,这样就能达到实时获取的场景。
Sel是方法的包装。
包装的SEL类型数据它对应相应的方法地址,找到方法地址就可以调用方法。在内存中每个类的方法都存储在类对象中,每个方法都有一个与之对应的SEL类型的数据,根据一个SEL数据就可以找到对应的方法地址,进而调用方法。
-
SEL s1 = @selector(test1); // 将test1方法包装成SEL对象
-
SEL s2 = NSSelectorFromString(@"test1"); // 将一个字符串方法转换成为SEL对象
-
调用方法有两种方式:
-
1.直接通过方法名来调用 [person text]
-
2.间接的通过SEL数据来调用 SEL aaa=@selector(text); [person performSelector:aaa];
最常见的就是UIbutton的添加点击事件的时候,就是采用Sel的调用方法
15.程序运行周期
appdelegate 和UIviewController的生命周期~
程序启动的执行顺序
1 程序的入口
进入main函数, 设置AppDelegate称为函数的代理
2 程序完成加载
-[AppDelegate application:didFinishLaunchingWithOptions:]
3 创建window窗口
4 程序被激活
-[AppDelegate applicationDidBecomeActive:]
5 当点击command+H时
程序取消激活状态
-[AppDelegate applicationWillResignActive:]
程序进入后台
-[AppDelegate applicationDidEnterBackground:]
6 点击进入工程
程序进入前台
-[AppDelegate applicationWillEnterForeground:]
程序被激活
-[AppDelegate applicationDidBecomeActive:]
1、对于applicationWillResignActive(非活动)与applicationDidEnterBackground(后台)这两个的区别。
****(1)applicationWillResignActive(非活动):
比如当有电话进来或短信进来,在或者锁屏等,这时你的应用程序挂起进入非活动状态,也就是你的手机其实界面还是显示着你当前的App窗口,只不过被别的任务强制占用了,或者后台状态(因为要先进入非活动状态,然后进入后台)。
(2) applicationDidEnterBackground(后台) :****
指当前窗口不是你的App,大多数程序进入这个后台后会在在这个状态上停留一会,时间到之后会进入挂起状态(Suspended)。如果你程序特殊处理后可以长期处于后台状态即在后台状态也可以运行。Suspended(挂起):程序在后台不能执行代码。系统会自动把程序变成这个状态而且不会发出通知。当挂起时,程序还是停留在内存中的,当系统内存低时,系统就把挂起的程序清除掉,为前台程序提供更多的内存。
16.http和https的区别,以及https的其中的实现过程,加密,签名等
17.贝塞尔曲线制表
18.极光推送的原理
19.ReactiveCocoa函数式响应式编程框架
20.有IM通信(XMPP), VOIP应用开发经验者优先考虑
21.对OOD/OOP有深刻的理解
22.熟悉weex优先(或熟悉Native)
23.有组件化和模块化开发经验,有CI经验
24.熟悉ios平台下的GUI设计和实现,有网络编程经验;
25.内存管理以及造成内存泄漏的情况,解决方式等
方法中的对象:会在方法的最后,release对象来销毁它
类中的对象:会在dealloc方法中释放对象
事实上,你并不需要写dealloc方法或调用父类的dealloc方法,ARC会自动帮你完成一切。
由编译器生成的代码甚至会比你自己写的release语句的性能还要好,因为编辑器可以作出一些假设。在ARC中,没有类可以覆盖release方法,也没有调用它的必要。ARC会通过直接使用objc_release来优化调用过程。而对于retain也是同样的方法。ARC会调用objc_retain来取代保留消息。
当使用ARC来管理内存时,在线程中大量分配对象而不用autoreleasepool则可能会造成内存泄露。比如说我们在for循环中大量的分配内存,需要在每次循环之后加上自动释放池,这样就可以及时清空出内存,不会造成内存的泄漏。
26.静态库和动态库的差别
静态库: 一堆目标文件(.o/.obj)的打包体(并非二进制文件)
动态库: 一个没有main函数的可执行文件
静态库: 链接时会被完整的复制到可执行文件中,所以如果两个程序都用了某个静态库,那么每个二进制可执行文件里面其实都含有这份静态库的代码
动态库: 链接时不复制,在程序启动后用dyld加载,然后再决议符号,所以理论上动态库只用存在一份,好多个程序都可以动态链接到这个动态库上面,达到了节省内存(不是磁盘是内存中只有一份动态库),还有另外一个好处,由于动态库并不绑定到可执行程序上,所以我们想升级这个动态库就很容易,windows和linux上面一般插件和模块机制都是这样实现的。
But我们的苹果爸爸在iOS平台上规定不允许存在动态库,并且所有的 IPA 都需要经过苹果爸爸的私钥加密后才能用,基本你用了动态库也会因为签名不对无法加载,(越狱和非 APP store 除外)。于是就把开发者自己开发动态库掐死在幻想中。
直到有一天,苹果爸爸的iOS升级到了8,iOS出现了APP Extension,swift编程语言也诞生了,由于iOS主APP需要和Extension共享代码,Swift语言的机制也只能有动态库,于是苹果爸爸尴尬了,不过这难不倒我们的苹果爸爸,毕竟我是爸爸,规则是我来定,我想怎样就怎样,于是提出了一个概念Embedded Framework,这种动态库允许APP和APP Extension共享代码,但是这份动态库的生命被限定在一个APP进程内。简单点可以理解为被阉割的动态库。
27.ios 的内存性能检测
Analyze静态分析
IOS性能调优系列:使用Instruments动态分析内存泄漏****
IOS性能调优系列:使用Allocation动态分析内存使用情况****
IOS性能调优系列:使用Zombies动态分析内存中的僵尸对象****
IOS性能调优系列:使用Time Profiler发现性能瓶颈****
28.OC与JS的交互
9.0之前都是采用的UIWebview,但是之后采用了WKwebview
29.为什么说OC是运行时语言是什么意思?
主要是将数据类型的确定由编译时,推迟到了运行时。这个问题其实浅涉及到两个概念,运行时和多态。
简单来说, 运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。
多态:不同对象以自己的方式响应相同的消息的能力叫做多态。
意思就是假设生物类(life)都拥有一个相同的方法-eat;那人类属于生物,猪也属于生物,都继承了life后,实现各自的eat,但是调用是我们只需调用各自的eat方法。也就是不同的对象以自己的方式响应了相同的消 息(响应了eat这个选择器)。因此也可以说,运行时机制是多态的基础.
30.完整性校验
31.什么是GDB和LLDB.
编译器LLVM在Xcode4.0以后换成了2.0版本,所以控制台调试命令的前缀也由GDB变成了LLDB。
其实使用p,po,call都可以调用方法,只是p和po都是用于输出的有返回值的。call一般只在不需要显示输出,或是方法无返回值时使用。
po命令:
命令po跟p很像。p输出的是基本类型,po输出的Objective-C对象。调试器会输出这个 object 的 description。
expression命令:
expression的简写就是e。可以用expression来声明新的变量,也可以改变已有变量的值。我们看到e声明的都是符号。
注意:如果上面这里输入以下命令,会发生错误。说明lldb无法判定某一步的计算结果是什么数据类型,这时需要强制类型转换来告诉lldb。
image命令
image 命令可用于寻址,有多个组合命令。比较实用的用法是用于寻找栈地址对应的代码位置。 下面我写了一段代码
常见命令:
1> po:打印对象,会调用对象 description 方法。是 print-object 的简写;命令po跟p很像,p输出的是基本类型,po输出的Objective-C对象。调试器会输出这个 object 的 description。
2> expr:可以在调试时动态执行指定表达式,并将结果打印出来,很有用的命令
3> print:也是打印命令,需要指定类型
4> bt:打印调用堆栈,是 thread backtrace 的简写,加 all 可打印所有thread 的堆栈
5> br l:是 breakpoint list 的简写
6> n:是换行
7> p:是打印这个对象所属的类,即其父类
32.React Native的学习
33.iOS的动画以及图形绘制