一,此代码在主线程调用有何不妥:
- (void)foo { NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(printSomething) userInfo:nil repeats:YES]; [timer fire]; } - (void)printSomething { static NSInteger count = 0; count ++; NSLog(@"timer count: %ld - - - %@",count,[NSThread currentThread].description); }
不妥,计时器造成了循环引用,因为计时器内部对target没有进行弱引用处理,所以会造成循环引用,处理办法:1,要么用block类型创建计时器;2,用第三方对象proxy作为target,拦截selector后让self进行处理,不会造成循环引用。
其余不妥,重点:
1,static关键字修饰局部变量,只会初始化一次;
2,static关键字修饰局部变量,在程序中只有一份内存;
3,static修饰的局部变量生命周期为,运行到当前代码块为开始,程序结束才销毁。
所以,当前count变量正常累加,在初始化一次之后,不会再去初始化。
// //所以输出的是: timer count: 1 - - - <NSThread: 0x6000038f4900>{number = 1, name = main} 2020-11-10 23:42:58.528381+0800 MianShiDemo[75471:4483232] 0x107dfb890 2020-11-10 23:43:01.529220+0800 MianShiDemo[75471:4483232] timer count: 2 - - - <NSThread: 0x6000038f4900>{number = 1, name = main} 2020-11-10 23:43:01.529440+0800 MianShiDemo[75471:4483232] 0x107dfb890 2020-11-10 23:43:04.529535+0800 MianShiDemo[75471:4483232] timer count: 3 - - - <NSThread: 0x6000038f4900>{number = 1, name = main} 2020-11-10 23:43:04.529851+0800 MianShiDemo[75471:4483232] 0x107dfb890
如果是异步线程中,应该如何处理:
- (void)foo { NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(printSomething) userInfo:nil repeats:YES]; [timer fire]; //必须开启当前线程的Runloop [[NSRunLoop currentRunLoop] run]; } - (void)printSomething { static NSInteger count = 0; count ++; NSLog(@"\ntimer count: %ld - - - %@",count,[NSThread currentThread].description); NSLog(@"\n%p",&count); } - (void)viewDidLoad { [super viewDidLoad]; //开启子线程调用NSTimer [NSThread detachNewThreadWithBlock:^{ [self foo]; }];
延展:
1,全局变量的作用域是整个项目文件,例如此全局变量:
#import "ViewController.h" #import <objc/message.h> NSInteger var = 100; @interface ViewController () @end @implementation ViewController
2,如果要在其他文件中获取此全局变量,那么在变量前增加extern修饰,例如:
@implementation ViewController_1 - (void)viewDidLoad { [super viewDidLoad]; extern NSInteger var; NSLog(@"%ld",var); // Do any additional setup after loading the view. } //输出 2020-11-10 23:54:09.214375+0800 xxxxDemo[75617:4494813] 100
3,如果不想让外部变量进行访问,可在定义的时候,前边加static。
二,缓存和内存中数据的一致性;
内存中数据,指的就是内存中的缓存数据,程序要读取一个数据,那么它从磁盘中将数据放在寄存器里边,然后通过寄存器加载到内存中再进行读取,这样对于一些高频使用的数据来说,时常去读取磁盘,程序的效率没有内存缓存去读取的高,而且会对磁盘寿命造成损耗。
所以为何要写内存缓存数据:
优点:主要是为提高执行效率。
缺点:增加了资源成本,也就是内存会增大。其实就是拿空间去换时间的一个操作。
objective-c里边的的内存缓存用NSCache去实现,第三方还有很多缓存框架。
内存缓存在实际场景中保存什么:
1,基础配置的数据,基本上不改动的数据;
2,或者高频率读取等。
如何保证内存中的磁盘中缓存数据一致:
读:先读缓存,如缓存没有,就读数据库(磁盘数据),然后取出数据后放入缓存。
写:先更新数据库,然后再删除缓存。
PS,如果数据有改动,就要删除内存中的数据,然后更新磁盘中的数据。
三,模拟通知;
暂无
四,线上奔溃如何处理,如何将崩溃日志传递给后台;
1,自身实现:
void uncaughtExceptionHandler(NSException *exception) { // 异常的堆栈信息 NSArray *stackArray = [exception callStackSymbols]; // 出现异常的原因 NSString *reason = [exception reason]; // 异常名称 NSString *name = [exception name]; NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray]; NSLog(@"%@", exceptionInfo); NSMutableArray *tmpArr = [NSMutableArray arrayWithArray:stackArray]; [tmpArr insertObject:reason atIndex:0]; //保存到本地 -- 当然你可以在下次启动的时候,上传这个log [exceptionInfo writeToFile:[NSString stringWithFormat:@"%@/Documents/error.log",NSHomeDirectory()] atomically:YES encoding:NSUTF8StringEncoding error:nil]; }
2,第三方,例如友盟,bugly。
五,两个子线程,有什么方法可以让其串联执行;
1,信号量,等待第一个执行完之后再执行第二个;
dispatch_queue_t userQue_1 = dispatch_queue_create("userQueue.com", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t userQue_2 = dispatch_queue_create("userQueue.com", DISPATCH_QUEUE_CONCURRENT); dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); dispatch_async(userQue_1, ^{ //先执行此任务 NSLog(@"userQue_1%@",[NSThread currentThread].description); sleep(2); NSLog(@"1执行完"); dispatch_semaphore_signal(semaphore); }); dispatch_async(userQue_2, ^{ //得到发送的信号后,再去执行等待的任务,也就是信号量大于0的时候 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"userQue_2%@",[NSThread currentThread].description); });
2,栅栏函数,前提是如果两个子线程在同一队列中,可将两子线程进行分割;
3,pthread互斥锁,pthread_mutex_t,如下:
__block pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); [NSThread detachNewThreadWithBlock:^{ pthread_mutex_lock(&mutex); NSLog(@"%@,线程1 \n",[NSThread currentThread].description); pthread_mutex_unlock(&mutex); }]; [NSThread detachNewThreadWithBlock:^{ pthread_mutex_lock(&mutex); NSLog(@"%@,线程2 \n",[NSThread currentThread].description); pthread_mutex_unlock(&mutex); }]; [NSThread detachNewThreadWithBlock:^{ pthread_mutex_lock(&mutex); NSLog(@"%@,线程3 \n",[NSThread currentThread].description); pthread_mutex_unlock(&mutex); }]; [NSThread detachNewThreadWithBlock:^{ pthread_mutex_lock(&mutex); NSLog(@"%@,线程4 \n",[NSThread currentThread].description); pthread_mutex_unlock(&mutex); }]; //输出 2020-11-19 19:51:43.535783+0800 xxxxxDemo[33897:1965863] <NSThread: 0x600001afc880>{number = 8, name = (null)},线程1 2020-11-19 19:51:43.536004+0800 xxxxxDemo[33897:1965864] <NSThread: 0x600001afe880>{number = 9, name = (null)},线程2 2020-11-19 19:51:43.536189+0800 xxxxxDemo[33897:1965865] <NSThread: 0x600001afa740>{number = 10, name = (null)},线程3 2020-11-19 19:51:43.536345+0800 xxxxxDemo[33897:1965866] <NSThread: 0x600001abb340>{number = 11, name = (null)},线程4
六,Mysql索引,事务。
索引:
索引是帮助数据库高效获取数据的数据结构。
表现形式:
PRIMARY KEY 主键
UNIQUE 唯一键
INDEX 普通索引
事务:
事务指逻辑上的一组操作,组成这组操作的各个单元,此操作单元具有原子性,要不全部成功,要不全部不成功。
个人理解:事务是一个存储SQL事件有所约束的队列,也就是执行多条SQL的一组逻辑操作。
BEGIN TRANSACTION //事务开始
SQL1
SQL2
COMMIT/ROLLBACK //事务提交或回滚
七,如果一个for循环里边有子线程去操作一个可变数组(对可变数组进行增加元素操作),那么当前代码环境会发生什么,为什么会发生?
1,多个线程去操纵一个数组,非原子性,会导致数据不完整,有冲突,用栅栏函数进行加锁;
2,因为多个任务去操作一个内存地址,可能会导致数组越界,导致崩溃。
八,什么是元类?
元类,是一个类对象的类,也就是当前类对象结构体中isa指向的类。
例如per对象对应的类是Person类,那么Person类也是一个类对象,因为它可以调用类方法,从而这个Person类对象的obj_class中isa指向的类称为元类。
所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。 既然是对象,那么它也是一个objc_class结构体指针,它包含一个指向其类的一个isa指针。当前isa指针指向了meta-class,meta-class的结构体objc_class里边包含了其自身的类方法,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。
例如输出元类:
objc_getMetaClass(const char * _Nonnull name);
NSLog(@"%@",[[^{ NSLog(@"this is block 3 - - -"); } superclass]superclass]);
//打印block的元类地址 void (^block_1)(void) = ^{ NSLog(@"this is block 1"); }; const char *name = object_getClassName(block_1); NSLog(@"%p",objc_getMetaClass(name));
九,block为什么是一个对象?
block对象本身是由objc_class组成,并且objc_class结构体拥有指针isa指向其类对象,block的结构体为:
struct objc_class : objc_object { //class isa,这里是继承与objc_object的,所以有isa指针 Class superclass; // 指向父类的地址 cache_t cache; // 方法缓存列表 //结构体,内部存储着主要的数据,通过&不同的掩码值获取不同的信息地址 class_data_bits_t bits; // 获取存放在bits中的可读可写的数据信息 class_rw_t *data() { return bits.data(); } ...... } //因为objc_class结构体继承与objc_object,所以里边有指向class对象的指针isa,
在Objective-C中,任何以一个指向 Class 结构体的指针isa开始的结构体都可以视为一个 objc_object,也就是对象。
isa是一个Class 类型的指针,每个实例对象有个isa的指针,它指向对象的类。每个对象都有一个类,对象的类是isa指针决定的,即 isa 指针指向对象所属的类(也可以立即成指向当前block对象的内存地址),block结构中有isa指向class,Class里有isa的指针,它指向对象的元类,所以它是一个对象,此对象的元类是NSObject。
十,block有几种?
//全局block void (^block_1)(void) = ^{ NSLog(@"this is block 1"); }; //栈block, __block NSInteger num = 10; void (^block_2)(void) = ^{ NSLog(@"this is block 2 - - - %ld",num); }; NSLog(@"%@\n%@\n%@\n",[block_1 class],[block_2 class],[^{ NSLog(@"this is block 3 - - -%ld , 这里是栈",num); } class]);
如果block作为函数的参数,它保存在内存中的栈区;
//输出 2020-11-11 16:39:51.570257+0800 xxxxDemo[77500:4699292] __NSGlobalBlock__ //第一个没有引用外部变量,属于全局block。 __NSMallocBlock__ //第二个没有进行copy应该是栈block,但输出的是堆block,因为环境是ARC,系统自动将栈block转换成了堆block(只要当前block一旦赋值,就会触发copy),便于调用。(给第二个block赋值了变量,也就是进行了copy,所以在堆里) __NSStackBlock__ //第三个是栈block,它的作用域是block执行结束,所以他的出了作用域会自动销毁,如果是MRC环境,需要对此类block进行copy操作。
三种:全局数据区,堆,栈。
1,不使用外部变量的block是全局block,对全局block进行copy,仍是全局block,程序结束后由系统释放;
2,使用外部变量并且未进行copy操作的block是栈block,block块运行结束后系统自动释放;
3,对栈Block进行copy,将会copy到堆区,变为堆block,对堆Block进行copy,将会增加引用计数,需手动释放。
十一,block是否可以修改外部变量(这里的变量是普通非对象的变量)?
首先,创建一个局部变量,例如NSInteger num = 10; 它存储在栈区,如果当前定义是一个全局变量,那么他存放在静态区,也就是全局区,如果是staic修饰的变量,也存在(全局区)静态区。
//修改示例 NSInteger val = 4; void (^block)(void) = ^{ val++; NSLog(@"val by value - - - %ld",val); }; block(); //此代码是不能够修改外部变量val,并且会报错 Variable is not assignable (missing __block type specifier) //程序告诉我们,丢失了变量修饰符__block。
为何不能修改:首先ARC下的创建的block,如果没有引用外部变量,一旦给block赋值变量,系统会自动将它处理成堆block(系统自动视为copy),也就是说当前block存在于堆中。
那么当前外部传进来的变量,只捕获了自动变量的值,并非内存地址,所以Block内部不能改变自动变量的值。
block捕获的外部变量可以改变其值的是:静态变量,静态全局变量,全局变量,因为他们存在于全局区,初始化后只有一份内存地址。
当前捕获的外部变量,block捕获了其变量的值,并且保存在block结构体中的成员变量里边,其内存地址跟block内存地址相等。
那么怎么样才能够修改其外部变量的值呢?
给局部变量(也就是所谓的自动变量)增加__block修饰,增加__block的原理:
将__block修饰的变量,如果当前的block块是在堆中,那么__block变量就会copy到堆上,然后将__block转化成一个结构体,此结构体为:
1,isa指针;
2,指向自身类型的__forwarding指针;
3,一个标记flag;
4,它的大小:
5,变量值。
那么当__block变量被copy到堆上,那么当前堆上的block块就持有了此堆上的变量,此变量结构体中有指针,所以可以修改其值。
如果是对象类型的局部变量,那么不需要加__block也可以进行操作,因为其传递的是对象指针,当传递过来的对象,block也会在堆里边重新拷贝一份,操作新拷贝的局部变量,例如:
NSMutableString *mstr = [NSMutableString stringWithString:@"hi"]; void (^block)(void) = ^{ [mstr appendString:@" objective-c!"]; NSLog(@"%@ - - -%p",mstr,&mstr); }; NSLog(@"%@ - - -%p",mstr,&mstr); block(); NSLog(@"%@ - - -%p",mstr,&mstr); //输出,可以看出block执行完之后mstr的地址和block块里边的地址是相同的。 2020-11-11 23:55:10.216056+0800 xxxxDemo[80069:5007352] hi - - -0x7ffee7e73128 2020-11-11 23:55:10.216243+0800 xxxxDemo[80069:5007352] hi objective-c! - - -0x600000a11b50 2020-11-11 23:55:10.216360+0800 xxxxDemo[80069:5007352] hi objective-c! - - -0x7ffee7e73128 //如果加了__block输出也是如此,因为加不加__block,只要改对象在block中引用了,都会在堆里边重新copy一份。 //======= __block NSMutableString *mstr = [NSMutableString stringWithString:@"hi"]; void (^block)(void) = ^{ [mstr appendString:@" objective-c!"]; NSLog(@"%@ - - -%p",mstr,&mstr); }; NSLog(@"%@ - - -%p",mstr,&mstr); block(); NSLog(@"%@ - - -%p",mstr,&mstr); //输出 2020-11-11 23:58:15.981031+0800 xxxxDemo[80093:5009922] hi - - -0x600003024478 2020-11-11 23:58:15.981191+0800 xxxxDemo[80093:5009922] hi objective-c! - - -0x600003024478 2020-11-11 23:58:15.981352+0800 xxxxDemo[80093:5009922] hi objective-c! - - -0x600003024478
延展:
NSInteger val = 4; void (^block)(void) = ^{ NSLog(@"%ld - - - %p\n",val,&val); }; val++; NSLog(@"%ld - - - %p\n",val,&val); block(); NSLog(@"%ld - - - %p\n",val,&val); //输出 2020-11-12 00:02:26.268682+0800 xxxxDemo[80160:5015099] 5 - - - 0x7ffeea98f128 2020-11-12 00:02:26.268839+0800 xxxxDemo[80160:5015099] 4 - - - 0x6000023980b0 2020-11-12 00:02:26.268954+0800 xxxxDemo[80160:5015099] 5 - - - 0x7ffeea98f128
这里就比较好理解,在外部++了,但是为什么block块内部输出的还是4,因为block只是捕获了val(自动变量)的值,并非内存地址,并且将值保存在了block结构体中的成员变量里边,所以Block内部输出的是block之前获取的那个值,也就是自身结构体中的那个值。
十二,objc_class包含什么?
objc_class是指向class的结构体类型。objective_c中,类是由Class类型来表示的。
里边包含了:
struct object_class{ Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; // 父类 const char *name OBJC2_UNAVAILABLE; // 类名 long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0 long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识 long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小 struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表 struct objc_method_list *methodLists OBJC2_UNAVAILABLE; // 方法定义的链表 struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存 struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表 #endif }OBJC2_UNAVAILABLE;
十三,以下代码输出顺序是什么?
- (void)printTest { NSLog(@"输出 - - - 2\n"); } - (void)viewDidLoad { [super viewDidLoad]; dispatch_queue_t gQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(gQueue, ^{ NSLog(@"输出 - - - 1\n"); [self performSelector:@selector(printTest) withObject:nil afterDelay:0.0]; NSLog(@"输出 - - - 3\n"); }); }
输出1和3,2不去输出,因为performSelector:withObject:nil:afterDelay底层在定义的时候系统自动创建一个NSTimer,然后去执行selector,因为当前为子线程,子线程的Runloop得手动去开启,所以不会去执行2。正确的做法为:
- (void)printTest { NSLog(@"输出 - - - 2\n"); } - (void)viewDidLoad { [super viewDidLoad]; dispatch_queue_t gQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(gQueue, ^{ NSLog(@"输出 - - - 1\n"); [self performSelector:@selector(printTest) withObject:nil afterDelay:0.0]; [[NSRunLoop currentRunLoop] run]; NSLog(@"输出 - - - 3\n"); }); } //控制台输出 2020-11-11 21:53:50.134437+0800 xxxxDemo[79188:4905794] 输出 - - - 1 2020-11-11 21:53:50.134733+0800 xxxxDemo[79188:4905794] 输出 - - - 2 2020-11-11 21:53:50.134889+0800 xxxxDemo[79188:4905794] 输出 - - - 3
如果是换成performSelector:withObject方法,而没有afterDelay参数,在子线程中的执行顺序如何?
答:直接输出1,2,3,因为performSelector:withObject方法没有创建NSTimer,所以不需要开启Runloop,所以performSelector:withObject的事件直接会在子线程中执行。
//输出 2020-11-11 21:58:59.650205+0800 xxxxDemo[79237:4910349] 输出 - - - 1 2020-11-11 21:58:59.650367+0800 xxxxDemo[79237:4910349] 输出 - - - 2 2020-11-11 21:58:59.650506+0800 xxxxDemo[79237:4910349] 输出 - - - 3
十四:App从启动到结束都做了什么?
1,进入main函数;
1,创建UIApplication创建主线程,其包括RunLoop(一一对应);
2,对象加载配置文件,info.plist,确定沙盒路径,创建沙盒;
3,加载动态库;
5,加载+load方法和category的扩展;
6,监听系统事件,处理等一系列操作;
7,程序结束。
十五,KVC的原理;
//应用 KVCClass *kvcObj = [KVCClass new]; kvcObj.kvModel = [KVCClass new]; [kvcObj setValue:@"this is name value" forKey:@"name"]; NSLog(@"%@",[kvcObj valueForKey:@"name"]); [kvcObj setValue:@"this is name value by kvModel" forKeyPath:@"kvModel.name"]; NSLog(@"description - - - %@",[kvcObj valueForKeyPath:@"kvModel.name"]); //被应用的类里边 -(void)setValue:(id)value forUndefinedKey:(NSString *)key; //保证kvc读取不到对应键的值,返回的内容 -(id)valueForUndefinedKey:(NSString *)key; //设置空值时 - (void)setNilValueForKey:(NSString *)key;
所谓KVC就是用key,value的形式去访问类属性,更深层一点就是用keyPath访问内部属性对象的属性。
实现原理:
KVC的赋值本质上只是调用了属性的setter方法,setter方法会按照setVariable、_setVariable、setIsVariable的优先级进行调用,还没有,则按_ariable、_isVariable、variable、isVariable查找成员变量。
取值调用了属性的getter方法,按照方法的优先级进行搜寻,然后再搜寻成员变量。
赋值:
1,搜索是否有setVariable(也就是属性的set方法)方法,Variable是成员变量名,如果没有则会搜索setIsVariable方法;
2,如果没有setIsVariable方法,则会查看当前类中的+ (BOOL)accessInstanceVariablesDirectly;方法是否返回YES,当前方法是定义是否去搜索访问当前类的成员变量。如果返回NO,则抛出异常,进入valueForUndefinedKey方法中。
3,如果返回返回YES,按 _variable、_isVariable、variable、isVariable的顺序搜索成员变量,直到返回。
4,如果都没有此些方法和成员变量,直接抛出异常,进入valueForUndefinedKey方法。
取值:
1.按先后顺序搜索getVar、var、isVar、_getVar、_var五个方法,按顺序哪个方法被实现,返回值就是改方法的返回值,后面的方法不再运行。如果是BOOL或者Int等值类型, 会将其包装成一个NSNumber对象。
2.若这五个方法都没有找到,则会调用+ (BOOL)accessInstanceVariablesDirectly方法判断是否允许取成员变量的值。
若返回NO,直接调用- (nullable id)valueForUndefinedKey:(NSString *)key方法,默认是奔溃。
若返回YES,会按先后顺序取_var、_isVar、 var、isVar成员变量的值。
3.返回YES时,_var、_isVar、 var、isVar的值都没取到,调用- (nullable id)valueForUndefinedKey:(NSString *)key方法。
实用场景:
1,给属性/成员变量赋值;
2,字段典转模型(可解析json);
3,修改系统控件的内部属性,例如: [pageControl setValue:[ UIImageimageNamed: @”xx.png”] forKeyPath: @”_pageImage”];
十六,通知,
通知,哪个线程发送通知,那么接受的通知事件就在哪个线程中执行,例如:
[NSThread detachNewThreadWithBlock:^{ NSLog(@"%@",[NSThread currentThread].description); [[NSNotificationCenter defaultCenter] postNotificationName:@"notiTest" object:nil]; }]; // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notiOperate) name:@"notiTest" object:nil]; - (void)notiOperate { NSLog(@"%@",[NSThread currentThread].description); } //输出 2020-11-15 18:45:51.593169+0800 xxxxDemo[15338:747324] <NSThread: 0x600003454f80>{number = 7, name = (null)} 2020-11-15 18:45:51.593456+0800 xxxxDemo[15338:747324] <NSThread: 0x600003454f80>{number = 7, name = (null)}
十六,如何实现KVO?
使用:
//注册观察者
– (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
//监听事件
– (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
//移除监听
– (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
苹果官方推荐的方式是,在init的时候进行addObserver,在dealloc时removeObserver,这样可以保证add和remove是成对出现的,否则会崩溃。
十七,数据存储的有几种形式?
内存:栈,堆,全局区,常量区,代码区。
磁盘:Userdefault,Sqlite,Data(二进制),钥匙串。
十八,UIImage优化;
http://www.iashes.com/2020/11/13-405.html
十九,socket断点续传;
二十,常用的LLDB命令有哪些?
1,p命令,查看对象的指针地址;
2,po,查看对象的description;
3,expression,动态调试,执行某个赋值表达式;
4,call,动态调用某个函数;
5,bt,打印出当前线程的堆栈信息。
二十一,isKindOfClass和isMemberOfClass的区别。
– (BOOL) isKindOfClass: classObj判断当前对象是否是当前类或者当前类的子类的实例。
– (BOOL) isMemberOfClass: classObj 判断当前对象是否是当前类的实例。
二十二,OC如何寻找类方法和实例方法?
//类方法寻找 Student -> Member -> NSObject(继承关系) Student : + create; [Student create]; 1,元类的类方法列表查询; 2,在去当前对象指向的类对象的方法列表中查询Member - > Student。 //实例方法寻找 Person - > SuperClass -> S_SuperClass(继承关系) 实例方法寻找: 1,寻找Person类里是否有(person类结构体中的缓存列表),如果有直接执行,如果没有,则向下; 2,再寻找SuperClass(类结构体中的缓存列表),同上; 3,再寻S_SuperClass(类结构体中的缓存列表),同上; 直到根类。
二十三,对象是如何去查找方法的,如果方法没有定义会如何处理?
首先,每一个对象是一个结构体,此结构体中有指向objc_class类的指针isa,也就是元类的地址,那么当前结构体中有一个元素,cache_t cache,是用来缓存最近调用过的方法。
那么当对象调用某一个实例方法的时候:
1,系统会按照方法名去cache_t中查找方法,找到了则直接调用;
2,如果未找到,进入类对象方法列表中查找(也就是isa所指向的对象中查找),此列表包含了分类的方法。
3,如果在类对象方法列中找到了此方法,先将方法加入cache_t中,然后调用方法。
4,如在方法列表中未找到,则通过superclass找到父类,在父类中进行1、2、3步骤。(父类的cache,父类类对象方法中,然后写入缓存);
5,如果最终未找到方法,会进入方法的动态解析阶段。
方法的静态解析阶段,如果没有找到方法,那么:
1,首先将调用其本类的resolveInstanceMethod:方法,其返回值为Boolean类型,表示这个类是否能新增一个实例方法用以处理该 unknown selector。在继续往下执行转发机制之前,本类有机会新增一个处理此 selector 的方法。所以resolveInstanceMethod:的使用:
1,首先会对当前实例方法进行第一次拦截,如果没有当前方法,应该如何处理:resolveInstanceMethod,让当前对象去补救,如果没有补救或者没有此方法,则返回NO,进入第二步。
PS,resolveInstanceMethod:的使用场景一般用来动态添加 setter 和 getter。
@implementation MethodObject + (BOOL)resolveInstanceMethod:(SEL)sel { NSLog(@"为找到方法,进行第一次拦截%@",NSStringFromSelector(sel)); if(sel == @selector(thisFirstMethd)){ return class_addMethod(self, sel, (IMP)thisFirstMethd, "v@::");; } return [super resolveInstanceMethod:sel]; } void thisFirstMethd(id self,SEL _cmd) { NSLog(@"这是一个新增的方法,第一次拦截补救"); } @end
2,第二步,将当前方法交给另外一个对象去补救,如果有补救,则返回补救的对象,如果没有则返回空。
PS,基于-forwardingTargetForSelector:,可以通过组合的方式,模拟出多继承的某些特性。
为协议遵循者提供默认实现,譬如某个协议定义了多个方法,有必要为这几个方法交给其他对象提供默认实现。
@implementation MethodObject - (id)forwardingTargetForSelector:(SEL)aSelector { if(aSelector == @selector(thisFirstMethd)){ return [[TransitTimer alloc]init]; } return [super forwardingTargetForSelector:aSelector]; } @end //======== @implementation TransitTimer - (void)thisFirstMethd { NSLog(@"%@",@"这是第二次补救方法"); } @end
3,- (NSMethodSignature *)methodSignatureForSelector,此方法会将当前类未实现的方法进行签名,添加到NSInvocation对象中,作为回调方法-forwardInvocation的回调参数,然后去处理当前类未实现的方法。
@implementation MethodObject /* * 第三次拦截处理 */ - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { /* 为指定的方法手动生成签名 */ if(aSelector == @selector(thisFirstMethd)){ //签名,将selector加入到NSMethodSignature生成的anInvocation对象中去 return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { //获取当前的消息 SEL sel = [anInvocation selector]; //要处理selector的对象 TransitTimer *tObject = [[TransitTimer alloc] init]; /* 此注释为相同判断,当前类中是否有实现sel if([TransitTimer instancesRespondToSelector:sel]){ //唤醒当前对象 [anInvocation invokeWithTarget:tObject]; } */ if ([tObject respondsToSelector:sel]) { //唤醒当前对象 [anInvocation invokeWithTarget:tObject]; }else{ [super forwardInvocation:anInvocation]; } //当前类如果不执行任何操作,可以防止其找不到方法崩溃doesNotRecognizeSelector:异常 } @end
这样,2、3也就完成了消息转发,1对消息进行了拦截解析,决策是否可以增加消息。
二十四,NSTimer为什么系统不会自动释放,为什么会造成循环引用,如何解决?
为什么不会被自动释放:
因为创建的NSTimer已经被加到了当前的Runloop里边,RunLoop持有当前Timer,RunLoop会对timer有强引用,所以要释放,必须得先从Runloop里边移除。
循环引用关系如下:
1,CurrentClass -> NSTimer
2,NSTimer -> target
3,NSTimer.target -> CurrentClass
造成了循环引用,所以不会去自动释放,只能手动调用Timer的invalidate方法释放,并且置空。
解决循环引用,参考连接:http://www.iashes.com/2020/11/20-576.html
二十五,对象和类的区别。
对象和类都是一个拥有指向objc_class类指针isa的结构体,类对象中结构体的isa指向元类,实例对象中的isa指向实例对象的类型。
类本身也是一个对象。
1,实例方法的resolveInstanceMethod 方法走的是类 — 父类 — 根类 — nil流程最终调用的是NSObject的+resolveInstanceMethod方法,类方法。
2,类方法的resolveClassMethod走的是元类 — 根元类 — 根类 — nil流程.最终也是调用NSObject的+resolveClassMethod方法;
那么就可以复写NSObject的+resolveInstanceMethod的方法实现崩溃拦截。