struct和class的区别
我们先了解栈和堆的区别:
1.栈的特点: 分配空间小 但是存在栈上的数据访问效率高。
2.堆的特点: 分配空间相对较大, 但是数据访问相对于栈,效率底下。
swift中struct与的class的区别
1.class是引用类型,struct是值类型。struct分配在栈中,class分配在堆中。二者本质区别:struct是值拷贝(深拷贝),class是引用拷贝(浅拷贝)。
2.类允许被继承,结构体不允许被继承。
3.property的初始化不同,class在初始化时不能直接把property放在默认的constructor的参数里,而是需要自己创建一个带参数的constructor;而struct可以,把属性放在默认的constructo参数里。
4.变量
swift的可变内容和不可变内容用var和let来甄别,如果初始化为let的变量再去修改会发生编译错误,struct遵循这一特性,class不存在这样的问题。
5.mutating function
struct和class的差别是struct的function要改变property的值的时候要加上mutating,而class不用。
OC语言中,struct与的class的区别:
共同点:
都可以将多个数据封装为一个整体
不同点:
1.类可以添加方法,结构体不可以
2.结构体在栈区,类在堆区
3.结构体是值类型,类是引用类型
4.类可以继承
使用场景:
1.如果封装的不仅有数据还有方法,只能使用类,
2.不涉及到方法的前提下:如果属性较少,就定义为结构体,存放栈中,方便调用,如果属性较多,就定义为类。
C语言中,struct与的class的区别:
struct只是作为一种复杂数据类型定义,不能用于面向对象编程。
C++中,struct和class的区别:
C++ 中保留了C语言的 struct 关键字,并且加以扩充,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。
C++中的 struct 和 class 基本是通用的,唯有几个细节不同:
1.使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
2.class 继承默认是 private 继承,而 struct 继承默认是 public 继承。
3.class 可以使用模板,而 struct 不能。
2.OC中的Array和Swift中的Array区别
Array是Swift中的结构体类型(struct), 属于是值类型.
NSArray是OC中的类型, 属于引用类型.
3.介绍一下观察者模式
观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
在IOS中典型的推模型实现方式为NSNotificationCenter和KVO。
4.在一个app中间有一个button,在你手触摸屏幕点击后,到这个button收到点击事件,中间发生了什么
- 设备将touch到的UITouch和UIEvent对象打包, 放到当前活动的Application的事件队列中
- 单例的UIApplication会从事件队列中取出触摸事件并传递给单例UIWindow
- UIWindow使用hitTest:withEvent:方法查找touch操作的所在的视图view
5.iOS事件传递与响应链
一、事件传递机制(Event Delivery)
事件传递指的是系统如何将用户操作(如触摸、手势)定位到具体的视图(UIView),核心是寻找最合适的视图(Hit-Testing View)。
1.事件传递的核心流程
1.1)**事件产生与接收:**用户触摸屏幕时,系统生成UIEvent对象(触摸事件),由UIApplication接收,传递给当前活动的UIWindow。
1.2)**从窗口到根视图:**UIWindow调用其rootViewController的视图(根视图)的hitTest(:with:)方法,开始向下遍历视图层级。
1.3)hitTest 与 pointInside 的关键作用:hitTest(:with:)方法会递归调用子视图的pointInside(_:with:)方法,判断触摸点是否在子视图范围内。若子视图返回true,则继续向该子视图的子视图递归;若所有子视图都返回false,则当前视图成为最合适视图。
1.4)**最终定位:**递归结束后,hitTest返回的视图即为事件的目标视图(targetView),事件将由该视图处理。
2.关键方法解析
// 1. hitTest:决定事件传递给谁
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 先判断自身是否可接收事件、是否隐藏、透明度是否足够
if !isUserInteractionEnabled || isHidden || alpha <= 0.01 {
return nil
}
// 从后往前遍历子视图(保证上层视图先被检查)
for subview in subviews.reversed() {
// 将触摸点转换为子视图的坐标系
let convertedPoint = subview.convert(point, from: self)
if let hitView = subview.hitTest(convertedPoint, with: event) {
return hitView
}
}
return self // 自己是最合适视图
}
// 2. pointInside:判断点是否在视图范围内
override func pointInside(_ point: CGPoint, with event: UIEvent?) -> Bool {
// 默认为true,可重写来排除特定区域(如按钮的透明区域)
return super.pointInside(point, with: event)
}
3.影响事件传递的属性
- isUserInteractionEnabled:设为false时,视图不接收事件,事件会传递给父视图。
- isHidden:隐藏的视图不接收事件。
- alpha:透明度低于 0.01 时,视图不接收事件。
- clipsToBounds:裁剪子视图超出范围的部分,超出部分不接收事件
二、响应链(Responder Chain)
1.响应链的层级结构
目标视图(UIView) → 父视图 → 视图控制器(UIViewController) →
UIWindow → UIApplication → UIApplicationDelegate
2.响应链的处理顺序
2.1)第一响应者(First Responder):事件的直接接收者(如点击的按钮),优先尝试处理事件。
2.2)向上传递:若第一响应者未处理事件,事件会传递给其父视图、视图控制器、窗口、应用程序,直到有人处理。
3.事件处理方法
核心总结
事件传递:从系统到目标视图,通过hitTest和pointInside向下遍历,确定事件接收者。
响应链:从目标视图向上传递,通过UIResponder层级寻找事件处理者。
开发实践:合理利用两者的机制,可实现自定义事件分发、全局交互控制等复杂功能。
科普:iOS事件分三种
Touch Events(触摸事件)
Motion Events(运动事件,比如重力感应和摇一摇等)
Remote Events(远程事件,比如用耳机上得按键来控制手机)
6、iOS调试技巧
1.断点调试
2.Analyze分析器
Analyze分析器是一种静态的工具,可以对我们的程序进行分析,找出我们未使用的变量,或一些死存储。在Product–>Analyze打开
3.Profile检查器
在Product–>Profile中打开
4.lldb命令调试
Xcode中使用llvm编译器,公认为最好的C、C++、OC、Swift编译器。而lldb是llvm中的调试器。
5.NSLog打印
6.视图调试
7.iOS开发定时器
请查看该博主定时器讲解
三种方案解决定时器的循环引用
补充:CADisplayLink在屏幕刷新时调用
8.UIView和CALayer之间的关系
UIView是iOS系统中界面元素的基础,UIView本身更像是一个CALayer的管理器,CALayer作为一个跨平台框架(OS X和iOS)QuatzCore的类,iOS系统为了处理用户交互事件(触屏操作)用UIView封装了一次CALayer,UIView本身负责处理交互事件,其持有一个Layer,用来负责绘制这个View的内容。而我们对UIView的和绘制相关的属性赋值和访问的时候(frame、backgroundColor等)UIView实际上是直接调用其Layer对应的属性(frame对
应frame,center对应position等)的getter和setter。
1.CALayer 基于 QuartzCore 框架,UIView 基于 UIKit 框架
2.CALayer是直接继承自NSObject的,而UIView是直接继承自UIResponder的。所以、相比于CALayer来说、UIView多了一个事件处理功能、也就是说、CALayer是不能处理用户的触摸事件的、而UIView可以。
9.ios开发动画
显式动画是指用户自己通过beginAnimations:context:和commitAnimations创建的动画。
隐式动画是指通过UIView的animateWithDuration:animations:方法创建的动画。
细微区别:使用隐式动画后,View会暂时不能接收用户的触摸、滑动等手势。
iOS 动画主要是指 Core Animation 框架
平时常用的动画,如基础动画(CABasicAnimation)、关键帧动画(CAKeyframeAnimation)、组动画(CAAnimationGroup)、过渡动画(CATransition)。
1.基础动画(CABasicAnimation)
基础动画主要提供了对于CALayer对象中的可变属性进行简单动画的操作。比如:位移、旋转、缩放、透明度、背景色等。
2. 关键帧动画( CAKeyframeAnimation )
CAKeyframeAnimation 和 CABasicAnimation 都属于CAPropertyAnimatin 的子类。不同的是 CABasicAnimation 只能从一个数值(fromValue)变换成另一个数值(toValue),而 CAKeyframeAnimation 则会使用一个数组(values) 保存一组关键帧, 也可以给定一个路径(path)制作动画。
3. 组动画( CAAnimationGroup )
CAAnimationGroup 是 CAAnimation 的子类,可以保存一组动画对象,可以保存基础动画、关键帧动画等,数组中所有动画对象可以同时并发运行, 也可以通过实践设置为串行连续动画.
4. 过渡动画( CATransition )
CATransition 是 CAAnimation 的子类,用于做过渡动画或者 转场 动画,能够为层提供移出屏幕和移入屏幕的动画效果。
10.Https的加密流程
HTTP: 直接通过明文在浏览器和服务器之间传递信息。
HTTPS: 采用 对称加密 和 非对称加密 结合的方式来保护浏览器和服务端之间的通信安全。对称加密算法加密数据+非对称加密算法交换密钥+数字证书验证身份=安全
1.什么是HTTP协议?
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议),是一个客户端和服务端请求应答的标准。
http位于TCP/IP模型当中的应用层。HTTP协议通过请求/响应的方式,在客户端和服务端之间进行通信。HTTP协议的信息传输完全以明文的方式,不做任何加密,相当于在网络上“裸奔”,所以容易遭受中间人的恶意截获甚至篡改(中间人攻击)。
2.什么是HTTPS协议?
HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。在HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。
3.http和https两者简单区别
1.http信息是明文传输,而https是安全的,https需要进行ssl加密传输
2.http的标准端口是80端口,https是443端口
3.http无需证书,https需要认证证书
4.对称加密和非对称加密
对称加密
对称加密采用了对称密码编码技术,它的特点是文件加密和解密都是使用相同的密钥。
这种方法在密码学中叫做对称加密算法,对称加密算法使用起来简单快捷,密钥较短,且破译困难,除了数据加密标准(DES),另一个对称密钥加密系统是国际数据加密算法(IDEA),它比DES的加密性好,而且对计算机功能要求也没有那么高。
非对称加密: 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
HTTPS其实是有两部分组成:HTTP + SSL / TLS,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行加密,所以传输的数据都是加密后的数据。
5.https加密流程
首先通过TCP三次握手建立连接,然后TSL4次握手完成数据传输所需要的会话秘钥的确认
1.客户端请求服务器,请求中内容包括:SSL/TSL版本号、客户端支持的加密算法列表、产生随机字符串A(预主密钥),向服务器请求公钥与CA证书。
2.服务器收到请求后,向客户端发出响应,确认SSL/TSL版本号,如果客户端不支持,那么就关闭通信、确认的加密算法列表、下发公钥与CA证书。
3.客户端验证CA证书的合法性,利用公钥对字符串A(预主密钥)进行加密。
4.服务器用私钥解密得到字符串A(预主密钥),建立连接。
5.双方使用共同的会话秘钥对数据进行加密和解密操作。
11.SDK开发需要注意的哪些?
1.能用系统API解决的就不要使用第三方,减少对其他库的依赖
2.依赖其他SDK的别打包在一起,不然出现符号表重复。
3.核心代码的安全性
4.资源文件使用bundle进行管理,能不用xib的就别用了吧
12.XMPP工作原理;xmpp系统特点
1.所有从一个client到另一个client的jabber消息和数据都要通过xmpp server
2.client链接到server
3.server利用本地目录系统的证书对其认证
4.server查找,连接并进行相互认证
5.client间进行交互
特点:1)客户机/服务器通信模式;2)分布式网络;3)简单的客户端;4)XML的数据格式
XMPP遇到的问题
发送附件(图片,语音,文档…)时比较麻烦
XMPP框架没有提供发送附件的功能,需要自己实现
实现方法,把文件上传到文件服务器,上传成功后获取文件保存路径,再把附件的路径发送给好友
13.OC对象的分类
OC对象主要分为三类:instance(实例对象),class (类对象),meta-class(元类对象)
1.实例对象
实例对象是我们对类对象alloc或者new操作时所创建的,每一次调用的alloc都是产生新的实例对象,内存地址都是不一样的,占据着不同的内存 。
我们平常说打印出来的实例对象的地址就是指的就是isa的地址,即isa地址排在最前面,就是我们实例对象的地址。
实例对象存储结构:
1.isa指针
2.其它成员变量
2.类对象
类对象是由程序员定义并在运行时由编译器创建的,它没有自己的实例变量(类的成员变量和实例方法是属于实例对象的,但存储在类对象里面)
所有指针指向某一个类对象的地址是一样的,也就是说一个类的类对象是唯一的。
类对象存储结构:
1.isa指针
2.superclass的指针
3.类的成员变量方法(即减号方法),类的属性,协议信息,成员变量信息。
3.元对象
获取元类对象的方法就是利用runtime方法,传入类对象,就可以获取该类的元类对象,从打印的结果可以看出,所有的指针地址一样,也就是说一个类的元类只有唯一的一个。
元类存储结构:
1.isa指针
2.superclass指针
3.类方法(即加号方法)
14.IOS 分类(category)、扩展(Extension)和继承(inheritance)的区别?
1.分类
在ios中当原有的类方法不够用时,这个时候分类就出现了,分类只能增加方法(category是在现有的基础上添加新的方法), 不能增加成员变量、@property(可能编译不报错,但是运行有问题)。虽然category不能直接添加成员变量,但是可以通过runtime的方式间接实现添加成员变量的效果。
运用oc的动态运行时分配机制,可以为现有类添加新方法,可以在分类中添加成员变量和方法,但是添加的成员变量不会自动生成setter和getter方法,需要在实现部分给出实现。
分类(category)的作用:
(1).作用:可以在不修改原来类的基础上,为一个类扩展方法。
(2).最主要的用法:给系统自带的类扩展方法。
使用分类需要注意的几个点:
1.分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,所以原则上讲他只能添加方法,不能添加属性(成员变量),实际上他是可以通过其他方式来添加属性,比如runtime。但是使用runtime也只能实现setter和getter方法,而没有_成员变量,如果调用_成员变量,程序还是会报错。
2、如果要使用@property来给分类添加成员变量的话,需要写变量的setter和getter方法,否则添加的成员变量无法使用。因为在分类中不会生成setter和getter方法,也不会生成以及实现私有的成员变量。
3、可以在分类中访问原有类中.h中的属性。
4、如果分类中有和原有类同名的方法,会优先调用分类中的方法,就是说会忽略原有类的方法。所以同名方法调用的优先级为:分类>本类>父类。
5、如果多个分类中都有和原有类同名的方法,那么调用该用法的时候执行谁由编译器决定,编译器会执行最后一个参与编译的分类中的方法。
为什么 Category(分类)不能像 Extension(扩展)一样添加成员变量?
因为 Extension(扩展)是在编译阶段与该类同时编译的,就是类的一部分。既然作为类的一部分,且与类同时编译,那么就可以在编译阶段为类添加成员变量。
而 Category(分类)则不同, Category(分类)的特性是:可以在运行时阶段动态地为已有类添加新行为。 Category(分类)是在运行时期间决定的。而成员变量的内存布局已经在编译阶段确定好了,如果在运行时阶段添加成员变量的话,就会破坏原有类的内存布局,从而造成可怕的后果,所以 Category(分类)无法添加成员变量。
如何给category添加成员方法:
使用:objc_setAssociatedObject
实现方法:
LeviPerson.h 文件中声明两个属性
#import "LeviPerson.h"
@interface LeviPerson (Test)
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int weight;
@end
LeviPerson.m 中利用关联对象进行实现
@implementation LeviPerson (Test)
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, _cmd);
}
- (void)setWeight:(int)weight
{
objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (int)weight
{
// _cmd == @selector(weight)
return [objc_getAssociatedObject(self, _cmd) intValue];
}
@end
**2.扩展**
iOS中的extension就是匿名的分类,类扩展是分类的一个特例。
**3.继承**
ios中OC单继承
分类的实现原理:
在 objc-runtime-new.h 中,Category(分类)被定义为 category_t 结构体。category_t 结构体 的数据结构如下:
typedef struct category_t *Category;
struct category_t {
const char *name; // 类名
classref_t cls; // 类,在运行时阶段通过 clasee_name(类名)对应到类对象
struct method_list_t *instanceMethods; // Category 中所有添加的对象方法列表
struct method_list_t *classMethods; // Category 中所有添加的类方法列表
struct protocol_list_t *protocols; // Category 中实现的所有协议列表
struct property_list_t *instanceProperties; // Category 中添加的所有属性
};
**加粗样式**
从 Category(分类)的结构体定义中也可以看出, Category(分类)可以为类添加对象方法、类方法、协议、属性。同时,也能发现 Category(分类)无法添加成员变量。
-
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
-
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)
Category和Class Extension的区别是什么? -
Class Extension在编译的时候,它的数据就已经包含在类信息中
-
Category是在运行时,才会将数据合并到类信息中
15.OC中load方法和initialize方法的异同
对于OC中的类来说,在runtime中会有两个方法会被调用:
- +load
- +initialize
在Objective-C中,load方法不会被覆盖
1.执行时机: load方法的执行时机在App启动后,main函数执行之前。具体过程包括系统调用当前App的进程、初始化运行环境、加载mach-o文件和动态库等。
2.执行顺序: load方法不需要显式调用[super load],父类会先执行自己的load方法,再执行子类的load方法。类的category中的load方法也会执行,顺序是先类后分类。即:父类>子类>父类分类>子类分类(父类 ->子类 -> 分类)。
**3.执行次数:**系统只会执行一次load方法。即使子类执行自己的load方法,父类的load方法也会先执行,且仅会执行一次。如果在子类中手动调用[super load],父类的load方法并不会再次执行。
4.覆盖特性: load方法在category中的使用有另外一个特性。在category中重写load方法后并不会影响其原来类的load方法执行,而是按照类后分类的顺序执行。
+initialize
在Objective-C中,initialize方法不会被覆盖,分类覆盖类的+initialize是因为分类和类属于同一个类对象的方法列表,而子类和父类是不同的类对象,方法列表相互独立。
调用时机和顺序
调用时机: initialize方法在类第一次接收到消息时调用。这意味着如果一个类没有被使用,其initialize方法可能永远不会执行。
**调用顺序:**首先调用父类的initialize方法,然后调用子类的initialize方法。如果子类没有实现initialize方法,则会继续调用父类的initialize方法(也就是说父类的initialize方法会被调用2次),如果子类没有实现,但子类的分类实现了,那么就是调用一次父类一个子类分类的initialize方法。
备注:如果父类的分类也实现了initialize方法,那父类的分类initialize方法会覆盖父类的initialize方法。
**分类:**如果分类实现了initialize方法,它会覆盖类本身的initialize方法。这意味着分类的initialize方法会先于主类的initialize方法执行。
**子类:**如果子类没有实现initialize方法,则会调用父类的initialize方法。如果子类实现了initialize方法,则会执行子类的initialize方法,而不会执行父类的initialize方法。
16.沙盒相关
**Document:**此目录用来保存应用程序在运行时生成的一些需要长久保存的重要数据。通过iTunes,iCloud备份时,会备份此目录下的数据。iTunes共享文件时可以共享此文件目录。
Library:
- Caches:此目录用来保存应用程序运行时生成的需要持久化的数据,这些数据一般储存体积比较大,又不是十分重要,比如网络请求数据等。这些数据需要用户负责删除。iTunes同步设备时不会备份该目录。
- Preference:此目录保存应用程序的所有偏好设置,ios的Settings应用会在该目录中查找应用的设置信息。在Preferences/下不能直接创建偏好设置文件,而是应该使用NSUserDefaults类来取得设置应用程序的偏好。ITunes同步设备时会备份该目录。
tmp:
此目录保存应用程序运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes不同设备时不会备份该目录。
//获取沙盒目录路径
NSString *homeDir = NSHomeDirectory();
//获取Documents目录路径
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
//获取Library目录路径
NSString *libDir = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
//获取Caches目录路径
NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
//获取tmp目录路径
NSString *tmpDir = NSTemporaryDirectory();
NSUserDefaults 原理探讨
NSUserDefaults 的存储目录是在/Library/Preferences下的一个.plist文件,该路径可通过配置实现iTunes共享文件。可被iTunes备份。
读取NSUserDefaults值得时候会默认在内存中缓存下来一份,所以NSUserDefaults的读取速度比较快。正常情况下,我们对NSUserfaluts的读取都是从内存中读取的,经测试删除.plist文件后,依然可以正常读取,基本可以证明这一点。该plist的完整路径是:Bundle Identifier.plist。所以如果需要存储多用户信息,可以为每一个用户在该文件夹下新建一个plist文件。
NSString *extension = @"plist";
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libPath = [paths objectAtIndex:0];
NSString *prePath = [libPath stringByAppendingPathComponent:@"Preferences"];
NSArray *contentsPath = [fileManager contentsOfDirectoryAtPath:prePath error:NULL];
NSLog(@"contentsPath = %@",contentsPath);
2021-04-27 16:13:49.015573+0800 NSUserDefault_Demo[2309:123886] contentsPath = (
"com.nSUserDefaultsDemo.plist"
)
17.ViewController生命周期
18.dynamic framework 和static framework 的区别?
静态库是每一个程序单独打包一份,而动态库则是多个程序之间共享。
静态库相对编译期,静态库在程序编译时会被链接到目标代码中,程序运行时将不再更改静态库。
动态库相对运行期,动态库在程序运行时才会被载入
静态库在链接时,会被完整的复制到可执行文件中,如果多个App都使用了同一个静态库,那么每个App都会拷贝一份,缺点是浪费内存.
动态库不会复制,只有一份,程序运行时动态加载到内存中,系统只会加载一次,多个程序共用一份,节约了内存.
共同点:
静态库和动态库都是闭源库,只能拿来满足某个功能的使用,不会暴露内部具体的代码信息.
19.Bitcode 理解
Bitcode 说明:
Bitcode是被编译程序的一种中间形式的代码。包含bitcode配置的程序将会在App store上被编译和链接。bitcode允许苹果在后期重新优化程序的二进制文件,而不需要重新提交一个新的版本到App store上。
当提交程序到App store上时,Xcode会将程序编译为一个中间表现形式(bitcode)。然后App store 会再将整个bitcode编译为可执行的64位或32位程序。
由上面的说明可以看出,BitCode包的作用其实就是让苹果对我们的编译代码进行一次优化。
20.CICD 持续集成与持续交付
CI/CD 是指持续集成(Continuous Integration)和持续部署(Continuous Deployment)或持续交付(Continuous Delivery)
常见的 CI/CD 工具包括 Jenkins、GitLab CI/CD、Travis CI 等。这些工具可以帮助团队实现自动化的构建、测试和部署流程。
21.isKindOfClass与isMemberOfClass
1、isKindOfClass可用于判断对象是否是一个类的成员,或者是该派生类的成员
2、isMemberOfClass可用于判断对象是否是当前类的成员
NSObject *obj = [[NSObject alloc] init];
FileManager *fileManager = [[FileManager alloc] init];//FileManager继承NSObject
if([obj isMemberOfClass:[NSObject class]]){
NSLog(@"3333");///打印
}else {
NSLog(@"4444");
}
if([fileManager isMemberOfClass:[NSObject class]]){
NSLog(@"999999");
}else {
NSLog(@"10100");///打印
}
附录:面试题