查看答案
#include和#import都可以引入头文件,但是#import只会引入一次。#import虽然只会引入一次,但是还会导致相互引入的问题。@class可以在头文件引入类,类可以是不存在的,可以在头文件@class引入类,在.m用#import引入类实现从而解决循环引用的问题。查看答案
@public代表可以在任何地方都可以被访问,@protected表示只能可以在子类和本类可以访问,@private表示只允许在本类允许访问查看答案
因为子类是继承与父类的,如果父类都初始化失败返回nil,那么子类也没有必要的执行下去了。其实是一种容错的处理。查看答案
当使用代理 或者声明@IBOutlet和不持有对象的时候可以使用weak关键词。weak是弱引用,只是用指针指向对应对象的内存地址,不会对对象进行引用技术加1。assign会将声明的基本变量放在栈区,交给系统自动管理内存。查看答案
@protocol中的@property和@category中的@property其实都是包含了set和get方法。对于@protocol所有实现协议的类都要实现对应@property属性的set和get方法。@category中的要自己实现set和get方法,虽然分类不支持属性,但是我们可以通过关联对象来实现。- @protocol
@protocol ProtocolA <NSObject>
@property (nonatomic, assign) int number;
@end
@interface ClassA : NSObject<ProtocolA>
@end
@implementation ClassA {
int _number;
}
- (void)setNumber:(int)number {
_number = number;
}
- (int)number {
return _number;
}
@protocol ProtocolA <NSObject>
@property (nonatomic, assign) int number;
@end
@interface ClassA : NSObject<ProtocolA>
@end
@implementation ClassA
@synthesize number = _number;
@end
@interface AppDelegate ()
@end我们看出来通过中间变量自己实现set和get方法和通过@synthesize关键字让系统自动生成都是可以的,不过还是使用@synthesize让系统自动生成set和get方法比较简单
- @category
@interface ClassA : NSObject
@end
@implementation ClassA
@end
@interface ClassA (Number)
@property (nonatomic, assign) int number;
@end
@implementation ClassA (Number)
- (int)number {
return [objc_getAssociatedObject(self,@selector(number)) intValue];
}
- (void)setNumber:(int)number {
objc_setAssociatedObject(self, @selector(number), @(number), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end我们可以通过objc_getAssociatedObject和objc_setAssociatedObject两个方法来为分类添加属性。
查看答案
我们知道`copy`关键词是复制一份内存地址,用新的指针指向。是属于深拷贝,而用`strong`关键字是通过一个指针指向对象的内存地址,通过内存地址访问对象,是属于浅拷贝。对于`strong`声明的字符串 数组字典其他地方可以通过地址直接修改对象。查看答案
通过`copy`修饰的NSMutableArry对象变成了NSArray,这样在编译类型是NSMutableArray,在运行时是NSArray,这样我们在编译时候调用NSMutableArray方法是不报错的,但是在运行时候类型不一样从而导致崩溃发生。查看答案
我们可以在.h文件声明属性用readonly关键词,在.m声明同样的属性用readwrite默认的关键词。- .h
@interface ClassA : NSObject
@property (nonatomic, assign, readonly) int number;
@end- .m
@interface ClassA ()
@property (nonatomic, assign) int number;
@end
@implementation ClassA
@end查看答案
block使用起来更加简单,比如访问作用域的变量还有代码逻辑的连贯性。代理更能表现是面向接口,能更好的描述功能和作用。如果是对外面向的接口推荐使用代理,日常开发页面之间的传值可以使用block.并且block和代理之间,block的执行会比较快一些。查看答案
通过block内部访问外部的变量,block会自动copy到自己的闭包空间。也就是复制了一份变量在自己的作用域内部,所以是无法修改外部的变量。通过__block关键词修饰之后,通过复制变量的引用地址来修改和访问外部变量的。查看答案
类别可以添加属性 添加的方法不实现会报警告。可以添加实例变量,但是只能自己类访问。扩展可以添加方法,不实现不会报警告。不能添加实例变量,属性可以通过关联对象实现。查看答案
分类可以将一个臃肿的类分散到多个文件中,可以将功能按照模块划分,多人多人开发一个类。分类可以给系统类添加方法,还可以拦截系统方法。继承拥有对类完全控制权,分类只对于引入文件有效。分类可以在不获取原来代码基础上增加方法,不可以删除方法,也不可以新增属性,分类具有更高的优先级。继承可以添加和删除方法,也可以新增属性。查看答案
分类只对于本次有效,推荐用分类。查看答案
这个其实是考察我们KVC的基本原理,都是可以的。查看答案
isMemberOfClass只能判断是否是当前的类,isKindOfClass可以用来判断是否是当前类或者子类。查看答案
isa其实是一个结构体 isa对象指向当前类 当前类指向父类 父类指向元类也就是NSObject,元类指向自己。作用是方便找到对应所在的方法。查看答案
这个第一次解除Block时候应该经常用到,在Block内部使用self调用属性或者方法。查看答案
我们知道创建对象 加载图片 加载文件 创建线程都需要消耗内存,特别是加载图片和加载文件会大量消耗内存。我们还知道在大量循环中创建临时变量就直线性的内存暴涨。通过上面我们可以才去进一步的优化内存,对于创建对象,因为单利会驻留在内存里面,不要创建消耗内存大的对象作为单利。加载图片imageName:会把图片驻留在内存里面做缓存提高下一次访问速度,我们可以把大图片用imageWithContentsOfFile的方法进行加载。尽可能减少多线程创建和数量,使用NSCache作为缓存机制替换我们常用的数组和字段作为缓存手段。在大量循环中用外部变量防止多次创建变量或者在循环内部使用runloop.如果是列表形式,做到数据重用。尽可能少的创建和消耗内存。查看答案
weak属性其实是系统维护的一个Hash表,系统会把weak声明的属性的内存地址作为key存放在hash表里面。当这个属性引用技术为0走dealloc方法时候,通过对应内存地址作为key再从hash表移出出去。查看答案
每个类里面都有一个method_list数组存储着这个类所有的方法和实现,通过selector对应的方法名称找到对应的方法和实现。查看答案
load放在会在第一次加载类方法会调用,调用顺序是先父类,然后子类,然后分类。initialize会在第一次调用类方法或者实例方法时候被调用。查看答案
可以通过KVC或者通过ivar进行访问。- 类实现
@interface ClassA : NSObject
@end
@implementation ClassA {
int _number;
}
- (instancetype)init {
if (self = [super init]) {
_number = 10;
}
return self;
}
@end- KVC
ClassA *a = [[ClassA alloc] init];
NSLog(@"number = %@",[a valueForKey:@"number"]);
[a setValue:@(5) forKey:@"number"];
NSLog(@"number = %@",[a valueForKey:@"number"]);- Ivar
ClassA *a = [[ClassA alloc] init];
Ivar var = class_getInstanceVariable([ClassA class], "_name");
NSLog(@"number = %@",object_getIvar(a, var));
object_setIvar(a, var, @"joser");
NSLog(@"number = %@",object_getIvar(a, var));Ivar对比KVC来说使用复杂,并且只支持对象类型。如果是基本类型直接在objc_getIvar方法崩溃。
查看答案
我们可以利用分类,通过交换方法实现。可以在方法之前也可以在方法后面。@interface ClassA : NSObject
@end
@implementation ClassA
- (void)printMyName {
NSLog(@"josercc");
}
@end
@interface ClassA (Print)
@end
@implementation ClassA (Print)
+ (void)load {
Method method1 = class_getClassMethod(self, @selector(printMyName));
Method method2 = class_getClassMethod(self, @selector(printMyName1));
method_exchangeImplementations(method1, method2);
}
- (void)printMyName1 {
NSLog(@"Hello");
[self printMyName1];
}
@end查看答案
- strong 会持有对象,会使对象引用计数加1
- weak 不会持有对象 只是存储了对象的内存地址
- assgin 用于声明基本类型 被assgin声明的会存放在栈区
- copy 会复制一份内存地址 一般用于NSString NSArray NSDictionary
查看答案
- __weak用来修饰实例变量
- __block用来修饰可以在block内部修改外部变量
查看答案
- atomatic 是相对线程安全的因为会自动在set和get方法进行加锁 但是会额外的消耗性能 默认
- nonatomic 是线程不安全的 性能好
- (void)viewDidLoad {
UILabel *alertLabel = [[UILabel alloc] initWithFrame:CGRectMake(100,100,100,100)];
alertLabel.text = @"Wait 4 seconds...";
[self.view addSubview:alertLabel];
NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
[backgroundQueue addOperationWithBlock:^{
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:4]];
alertLabel.text = @"Ready to go!”
}];
}查看答案
我们是在子线程操作的UI应该在主线程操作UI不会更新(在Xcode11测试依然会更新,只是会报不能在子线程更新UI的警告 -[UILabel setText:] must be used from main thread only)。
查看答案
OC是没有多继承的,可以通过协议进行代替。查看答案
OC并不存在严格来说的私有方法和私有变量。对于用@public修饰的属性和方法在.h都可以被外部调用。在.m实现的方法和实例变量只能本类调用就称作私有的。OC是一个有运行时的特性,所以我们可以获取方法列表找到私有方法通过消息转发调用。对于私有变量我们可以通过KVC,或者Ivar进行访问。所以OC不存在严格意义上面的私有方法和私有变量查看答案
对于const来说 声明在谁前面就意味着谁不能修改,如果const在第一位则向右移一位。比如int const a等效果于const int a都是让整形的a不可变。声明const可以让编译时候代码更紧凑,让系统修改变量时候随时产生报错,防止BUG的产生。
查看答案
UITableView有两个实例变量,visiableCells保存当前界面显示的Cell的数组,reusableTableCells保存在可以重用的Cell的字典。当第一次加载界面,reusableTableCells没有任何重用的Cell就会走UITableViewCell的初始化方法进行创建,之后添加到visiableCells数组里面。当界面滚动时候,原本展示的Cell消失在屏幕之后。对应的Cell对象就会添加到reusableTableCells里面,即而从visiableCells数组里面进行移出。当一个新的cell将展示时候从reusableTableCells查看是否存在重用的,如果存在就展示重用的,如果没有就重新走创建的流程。
查看答案
当手机内存运行不足时候会调用ViewController的didReceiveMemoryWarning方法,默认时尝试释放ViewController所拥有的View。我们也可以重写做其他释放内存的任务。
查看答案
delegate是一对一的关系,notification是一对多的关系。delegate通畅用于开放接口用于其他模块的调用,notification可以做到模块分离,通畅用于模块之间的通信。
查看答案
id代表在OC里面的对象的指针,nil代表是指针指向一个空的对象。
查看答案
NSTimer因为是添加到runloop中的走的是默认的mode,当滑动表格的时候就会切换对应mode停止timer。runloop因为优先在触发时候执行输入源,才会执行runloop中的任务,虽有会有50-100毫秒的误差。
我们想做一个精准的timer就需要创建一个新的runloop来不受其他输入源的影响。
- 添加
Timer到当前的runloop设置为NSRunLoopCommonModes
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerStart)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];- 在新线程添加
NSTimer
dispatch_async(dispatch_queue_create("", 0), ^{
@autoreleasepool {
self->_timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(timerStart)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
});- GCD
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"timer");
});
dispatch_resume(timer);对比还是第三种方案简单,第二种执行的任务会在子线程,容易写出BUG,第一种容易遗漏添加到runloop设置对应的mode。第三种有现成的代码块,而且还十分的精准。
查看答案
系统分配16个字节,但是真正占用只有8个字节。
查看答案
selector只是代表方法名称,而method包含了名称和实现。
查看答案
反射机制就是通过字符串反射为对应的类,协议,方法。或者将协议,方法或者类反射为字符串。通常用于做模块化跳转,或者用于做deeplink等运行时创建类等功能。
查看答案
协议默认为@require,使用时候要注意用weak声明代理,防止循环引用。
查看答案
通知底层核心是存在一个notication_map的字典,通知的名称作为Key,Value为数组结构,数组的每个元素包含了监听的对象,方法名称,还有传递的参数。当发送通知的时候,会在notication_map的字典当中找到对应key的数组,通过遍历数组,对数组里面所有的监听者发送通知。当发送参数和监听的参数不一样的时候,会被忽略,这就是为什么名字相同却收不到通知。
-
当接受通知的参数为空可以接受发送通知参数为空或者不为空
可以接受到通知
NotificationCenter.default.addObserver(self, selector: #selector(revicerNotication(_:)), name: NSNotification.Name(rawValue: "test notification"), object: nil) NotificationCenter.default.post(name: NSNotification.Name(rawValue: "test notification"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(revicerNotication(_:)), name: NSNotification.Name(rawValue: "test notification"), object: nil) NotificationCenter.default.post(name: NSNotification.Name(rawValue: "test notification"), object: "234")
NotificationCenter.default.addObserver(self, selector: #selector(revicerNotication(_:)), name: NSNotification.Name(rawValue: "test notification"), object: "234") NotificationCenter.default.post(name: NSNotification.Name(rawValue: "test notification"), object: "234")
不可以接受到通知
NotificationCenter.default.addObserver(self, selector: #selector(revicerNotication(_:)), name: NSNotification.Name(rawValue: "test notification"), object: "234") NotificationCenter.default.post(name: NSNotification.Name(rawValue: "test notification"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(revicerNotication(_:)), name: NSNotification.Name(rawValue: "test notification"), object: "234") NotificationCenter.default.post(name: NSNotification.Name(rawValue: "test notification"), object: "123")
Details
查看答案KVO系统通过isa混淆技术通过创建类的不可见子类,通过重写set和get方法来实现监听机制的。对于没有调用set和get方法是调用不了监听机制的,需要手动调用willChangeValueForKey和didChangeValueForKey触发。
查看答案
不能,因为编译之后类的内存大小已经固定不能再添加实例变量,但是可以动态的添加实例变量。