Q1: UIView常用的一些方法小记之setNeedsDisplay和setNeedsLayout
首先两个方法都是异步执行的。
setNeedsDisplay会自动调用drawRect方法,这样可以拿到 UIGraphicsGetCurrentContext
,可以实现绘图;
setNeedsLayout会默认调用layoutSubViews,就可以处理子视图中的一些数据。
layoutSubviews在以下情况下会被调用:
1、init初始化不会触发layoutSubviews。
2、addSubview会触发layoutSubviews。
3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。
4、滚动一个UIScrollView会触发layoutSubviews。
5、旋转Screen会触发父UIView上的layoutSubviews事件。
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。
7、直接调用setLayoutSubviews。
drawRect在以下情况下会被调用:
1、如果在UIView初始化时没有设置rect大小,将直接导致drawRect不被自动调用。drawRect调用是在Controller->loadView, Controller->viewDidLoad 两方法之后调用的,所以不用担心在控制器中,这些View的drawRect就开始画了,这样可以在控制器中设置一些值给View。
2、该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size,然后系统自动调用drawRect:方法。
3、通过设置contentMode属性值为UIViewContentModeRedraw,将在每次设置或更改frame的时候自动调用drawRect:。
4、直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect不能为0。
以上1,2推荐;而3,4不提倡
drawRect方法使用注意点:
1、若使用UIView绘图,只能在drawRect:方法中获取相应的contextRef并绘图。如果在其他方法中获取将获取到一个invalidate的ref并且不能用于画图。
2、drawRect:方法不能手动显示调用,必须通过调用setNeedsDisplay 或者 setNeedsDisplayInRect,让系统自动调该方法。
3、若使用CALayer绘图,只能在drawInContext: 中(类似于drawRect)绘制,或者在delegate中的相应方法绘制,同样也是调用setNeedDisplay等间接调用以上方法。
4、若要实时画图,不能使用GestureRecognizer,只能使用touchbegan等方法来调用setNeedsDisplay实时刷新屏幕。
Q2:RunLoop在什么情况下会用到?
- 定义一个NSTimer来隔一会调用某个方法 ,但这时你在拖动TextVIew不放手 ,主线程就被占用了,timer的监听方法就不调用 直到你松手,这时把NSTimer加到RunLoop里 ,就相当于告诉主循环腾出点时间来给timer ,再拖动TextView就不会因主线程被占用而不调用了。
- 例如AFNetWorking和SDWebImage的源码中都是子类化NSOperation,在子线程中发起NSURLConnetion,connetion的代理就是这个operation,然后CFRunloopRun(),开启Runloop,Fail或者Finish的时候CFRunloopStop(CFRunloopGetCurrent())关闭Runloop。如果不写CFRunloopRun(),根本不会执行NSURLConnection的代理方法的,因为该线程没开启Runloop,马上就完了。RunLoop相当于子线程的循环,可以灵活控制子线程的生命周期。
Q3:Runtime在什么情况下会用到?
Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 OC 代码,底层都是基于它来实现的。
我们写的代码在程序运行过程中都会被转化成runtime的C代码执行,例如[target doSomething];
会被转化成objc_msgSend(target, @selector(doSomething));
- 获取列表
我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)
unsigned int count;
//获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i%@", [NSString stringWithUTF8String:propertyName]);
}
//获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i%@", NSStringFromSelector(method_getName(method)));
}
//获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i%@", [NSString stringWithUTF8String:ivarName]);
}
//获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i%@", [NSString stringWithUTF8String:protocolName]);
}
-
方法调用
- 重写父类的方法,并没有覆盖掉父类的方法,只是在当前类对象中找到了这个方法后就不会再去父类中找了;
- 如果想调用已经重写过的方法的父类的实现,只需使用
super
这个编译器标识,它会在运行时跳过在当前的类对象中寻找方法的过程。
动态添加方法
关联对象
现在你准备用一个系统的类,但是系统的类并不能满足你的需求,你需要额外添加一个属性。 这种情况的一般解决办法就是继承。 但是,只增加一个属性,就去继承一个类,总是觉得太麻烦。
//首先定义一个全局变量,用它的地址作为关联对象的key
static char associatedObjectKey;
//设置关联对象
objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串属性", OBJC_ASSOCIATION_RETAIN_NONATOMIC); //获取关联对象
NSString *string = objc_getAssociatedObject(target, &associatedObjectKey);
NSLog(@"AssociatedObject = %@", string);
objc_setAssociatedObject
的四个参数:
id object
给谁设置关联对象。
const void *key
关联对象唯一的key
,获取时会用到。
id value
关联对象。
objc_AssociationPolicy
关联策略。objc_getAssociatedObject
的两个参数。
id object
获取谁的关联对象。
const void *key
根据这个唯一的key
获取关联对象。方法交换
#import "UIViewController+swizzling.h"
#import
@implementation UIViewController (swizzling)
//load方法会在类第一次加载的时候被调用
//调用的时间比较靠前,适合在这个方法里做方法交换
+ (void)load{
//方法交换应该被保证,在程序中只会执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//获得viewController的生命周期方法的selector
SEL systemSel = @selector(viewWillAppear:);
//自己实现的将要被交换的方法的selector
SEL swizzSel = @selector(swiz_viewWillAppear:);
//两个方法的Method
Method systemMethod = class_getInstanceMethod([self class], systemSel);
Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
//首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
if (isAdd) {
//如果成功,说明类中不存在这个方法的实现
//将被交换方法的实现替换到这个并不存在的实现
class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
}else{
//否则,交换两个方法的实现
method_exchangeImplementations(systemMethod, swizzMethod);
}
});
}
- (void)swiz_viewWillAppear:(BOOL)animated{
//这时候调用自己,看起来像是死循环
//但是其实自己的实现已经被替换了
[self swiz_viewWillAppear:animated];
NSLog(@"swizzle");
}
@end
在一个自己定义的viewController中重写viewWillAppear:
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(@"viewWillAppear");
}
Q4: 线程创建的几种方式
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
//创建线程的第一种方式
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"universe"];
[thread start];
[thread release];
//创建线程的第二种方式,NSThread类方法
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"yuzhou"];
//创建线程的第三种方法 NSObject方法
[self performSelectorInBackground:@selector(run:) withObject:@"nsobject thread"];
//创建线程的第四种方式
NSOperationQueue *oprationQueue = [[NSOperationQueue alloc] init];
[oprationQueue addOperationWithBlock:^{
//这个block语句块在子线程中执行
NSLog(@"oprationQueue");
}];
[oprationQueue release];
//第五种创建线程的方式
NSOperationQueue *oprationQueue1 = [[NSOperationQueue alloc] init];
oprationQueue1.maxConcurrentOperationCount = 1;//指定池子的并发数
//NSOperation 相当于java中的runnable接口,继承自它的就相当一个任务
NSInvocationOperation *invation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"invation"];
[oprationQueue1 addOperation:invation];//将任务添加到池子里面,可以给池子添加多个任务,并且指定它的并发数
[invation release];
//第二个任务
NSInvocationOperation *invation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2:) object:@"invocation2"];
invation2.queuePriority = NSOperationQueuePriorityHigh;//设置线程优先级
[oprationQueue1 addOperation:invation2];
[invation2 release];
[oprationQueue1 release];
//调用主线程,用来子线程和主线程交互,最后面的那个boolean参数,如果为yes就是等这个方法执行完了在执行后面的代码;如果为no的话,就是不管这个方法执行完了没有,接着往下走
[self performSelectorOnMainThread:@selector(onMain) withObject:self waitUntilDone:YES];
//---------------------GCD----------------------支持多核,高效率的多线程技术
//创建多线程第六种方式
dispatch_queue_t queue = dispatch_queue_create("name", NULL);
//创建一个子线程
dispatch_async(queue, ^{
// 子线程code... ..
NSLog(@"GCD多线程");
//回到主线程
dispatch_sync(dispatch_get_main_queue(), ^{//其实这个也是在子线程中执行的,只是把它放到了主线程的队列中
Boolean isMain = [NSThread isMainThread];
if (isMain) {
NSLog(@"GCD主线程");
}
});
});
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
- (void)onMain
{
Boolean b = [NSThread isMainThread];
if (b) {
NSLog(@"onMain;;%d",b);
}
}
- (void) run:(NSString*)str
{
NSLog(@"多线程运行:::%@",str);
}
- (void) run2:(NSString*)str
{
NSLog(@"多线程运行:::%@",str);
}
Q5: ____block 与 __ __weak的区别理解
- __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
- __weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
- ____block对象可以在block中被重新赋值,__ __weak不可以。
PS:____unsafe_unretained修饰符可以被视为iOS SDK 4.3以前版本的__weak的替代品,不过不会被自动置空为nil,所以尽可能不要使用这个修饰符。
Q6: KVO,NSNotification,delegate及block区别
解释:
KVO就是cocoa框架实现的观察者模式,一般同KVC搭配使用,通过KVO可以监测一个值的变化,比如View的高度变化。是一对多的关系,一个值的变化会通知所有的观察者。
NSNotification是通知,也是一对多的使用场景。在某些情况下,KVO和NSNotification是一样的,都是状态变化之后告知对方。NSNotification的特点,就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比KVO多了发送通知的一步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。
delegate 是代理,就是我不想做的事情交给别人做。比如狗需要吃饭,就通过delegate通知主人,主人就会给他做饭、盛饭、倒水,这些操作,这些狗都不需要关心,只需要调用delegate(代理人)就可以了,由其他类完成所需要的操作。所以delegate是一对一关系。
block是delegate的另一种形式,是函数式编程的一种形式。使用场景跟delegate一样,相比delegate更灵活,而且代理的实现更直观。
区别:
- KVO一般的使用场景是数据,需求是数据变化,比如股票价格变化,我们一般使用KVO(观察者模式)。
- delegate一般的使用场景是行为,需求是需要别人帮我做一件事情,比如买卖股票,我们一般使用delegate。
- Notification一般是进行全局通知,比如利好消息一出,通知大家去买入。
- delegate是强关联,就是委托和代理双方互相知道,你委托别人买股票你就需要知道经纪人,经纪人也需要知道自己的顾客。Notification是弱关联,利好消息发出,你不需要知道是谁发的也可以做出相应的反应,同理发消息的人也不需要知道接收的人也可以正常发出消息。
Q6: iOS是否有私有方法和私有变量?
私有方法
- 如果一个方法不在头文件中声明,那么这个方法在编译期,通过[receiver MethodName]的形式向对象发送消息,编译器会有警告,告诉你未找到该方法,但是实际运行时,依然可以正常运行;
- 因为在编译时,即使这个方法不在头文件中声明,编译器仍然会将方法的签名编译进类的方法列表中, 发送消息时,会自动查找消息列表,如果找到同名消息,则会被触发。
@interface ABC : NSObject
@end
@implementation ABC
- (void)abc {
NSLog(@"abc");
}
@end
ABC *abc = [ABC new];
[abc performSelector:@selector(abc)];
私有变量
- OC中所有的方法调用都是通过消息传递,即使你使用了obj.prop 这种点语法去为对象属性赋值,其编译后的代码仍然是转换为消息的调用。obj.prop 其实是向对象发送了一个setProp方法,等价于[obj setProp] 。 如果你用[obj setProp] 去向这个所谓的"私有属性"赋值,仍然可以赋值成功,并且可以正常使用。
结论
- OC中其实并无真正意义上的的私有方法和私有属性。但是在实际使用中,我们应遵守规则,不调用不能调用的方法。
Q67 iOS中id和instancetype的区别?
-
相同点
都可以作为方法的返回类型
-
不同点
- instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象;
- instancetype只能作为返回值,不能像id那样作为参数。
//err,expected a type
- (void)setValue:(instancetype)value {
//do something
}
应该写成如下形式:
- (void)setValue:(id)value {
//do something
}