两种输出Hello world 的形式:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
@interface 创建接口
(void) 声明方法
@implementation 实现接口
```
@interface SampleClass : NSObject
(void)sampleMethod;
@end
@implementation SampleClass
(void)sampleMethod{
NSLog(@"Hello world!");
}
@end
int main(){
SampleClass * sampleClass = [[SampleClass alloc] init];
[sampleClass sampleMethod];
return 0;
}
生命周期
APP生命周期
UIViewController生命周期
方法调用
求两数之间的最大值
@interface SampleClass : NSObject
(int) max:(int)num1 secondNumber:(int)num2;
@end
@implementation SampleClass
(int) max:(int)num1 secondNumber:(int)num2{
int num;
if(num1 > num2){
num = num1;
}else {
num = num2;
}
return num;
}
@end
int main(){
int a = 10;
int b = 20;
int res;
SampleClass *sampleClass = [[SampleClass alloc]init];
res = [sampleClass max:a secondNumber:b];
NSLog(@"res is", res);
return 0;
}
属性
读写修饰符
(1)readwrite
表明这个属性是可读可写,默认属性,系统创建setter和getter方法。
(2)readonly
表明这个属性只读不写,系统只创建getter方法,不创建setter方法。
原子性修饰符
(1)atomic
原子属性,线程安全但可能降低性能。
(2)nonatomic
非原子属性,提高性能但非线程安全。
内存管理语义: assign、week、strong、copy、retain、unsafe_unretained
assgin和weak
weak与assgin都表示了一种非持有关系,也不被称为弱引用,在调用时,不会增加引用对象的引用计数。weak在引用对象被销毁时,会被指向nil;而assgin不会被置为nil。
(1)assign
如果用assgin修饰对象,当assgin指向的对象被销毁时,assgin就会指向一块无效内存,这时给assgin发送消息,程序一不定会发生崩溃,这个取决于你发送消息时,那块内存还是否存在。
assgin一般修饰的基础数据类型(NSInteger,int,float,double,char,bool等),因为基础数据类型是被分配到栈上,栈的内存会由系统自动处理,所以不会造成悬空指针。
(2)weak
只用于修饰对象,修饰的对象会通过一个hash表保存。
strong和copy
使用strong和copy是都会使引用对象引用计数+1。但是使用copy修饰的属性在某些情况下赋值的时候会创建对象的副本,就是深拷贝。strong是两个指针指向同一个内存地址,copy是在内存中拷贝一份对象,两个指针指向不同的内存地址。
(1)copy
copy修饰NSString、NSArray、block属性。系统在当源字符串为不可变类型时,你的属性copy其实就是进行浅拷贝,当源字符串为可变类型时,才进行深拷贝。但是我们建议在使用NSString属性的时候用copy,避免可变字符额修改导致一些非预期问题。与strong类似,设置方法会拷贝一份副本。一般用于修饰字符串和集合类的不可变版, block用copy修饰。
(2)strong
表示指向并拥有该对象。其修饰的对象引用计数会 +1 ,该对象只要引用计数不为 0 就不会销毁,强行置空可以销毁它。一般用于修饰对象类型、字符串和集合类的可变版本。只能用于ARC环境,strong修饰NSString、block以外的OC对象。
retain
retain修饰NSArray,NSDate,对应的setter方法。
修饰对象类型,强引用对象,并使对象引用计数加1,可用于MRC环境中。
与assign相对,我们要解决对象被其他对象引用后释放造成的问题,就要用retain来声明。retain声明后的对象每次被引用,引用计数会+1,释放后会-1,即使这个对象本身释放了,只要还有对象在引用它,就会持有,只有当引用计数为0时,就被dealloc析构函数回收内存了。
unsafe_unretained
与weak类似,但是销毁时不自动清空,容易形成野指针。
Block
块是C,Objective-C和C++等编程语言中的高级功能,它允许创建不同的代码段,这些代码段可以传递给方法或函数,就像它们是值一样。 块是Objective-C对象,因此它们可以添加到NSArray或NSDictionary等集合中。 它们还能够从封闭范围中捕获值,使其类似于其他编程语言中的闭包或lambda。
block本质上也是一个OC对象,内部也有个isa指针,并且内部封装了函数调用。
BLock的实现
1、无参数无返回值
void(^ZSBlockOne)(void) = ^(void){
NSLog(@"无参数,无返回值")
};
ZSBlockOne();
2、有参数无返回值
void(^ZSBlockTwo)(int value) = ^(int value){
NSLog(@"有参数,无返回值的Block:%d",value);
};
ZSBlockTwo()
3、有参数有返回值
int(^ZSBlockThree)(int value) = ^(int value){
NSLog(@"有参数,有返回值的block:%d",value);
return value + 10;
};
int a = ZSBlockThree(5);
4、无参数有返回值
int(^ZSBlockFour)(void) = ^{
NSLog(@"无参数,有返回值的block");
return 100;
};
int a = ZSBlockFour();
5、开发中常用typedef定义block
typedef void (^ZSBlock)(int value);
// ZSBlock是一个种block类型,可以直接创建属性
@property(nonatomic, copy) ZSBlock myBlock;
self.myBlock = ^(int value){
NSLog(@"调用了block,传了一个值为:%d",value);
};
self.myBlock(10);
例如:
typedef void (^CompletionBlock)();
@interface SampleClass : NSObject
-(vid)performActionWithCompletion:(CompletionBlock)completionBlock;
@end
@implementation SampleClass
-(vid)performActionWithCompletion:(CompletionBlock)completionBlock{
NSLog(@"Action perform:");
completionBlock();
}
@end
int main(){
SampleClass *sampleClass = [[SampleClass alloc]init];
[sampleClass performActionWithCompletion:^{
NSLog(@"is performed");
}];
return 0;
}
Block的类型
协议
关键字@required下的方法必须在符合协议的类中实现,并且@optional关键字下的方法是可选的。
@protocol PrintProtocolDelegate
-(void)processCompleted;
@end
@interface PrintClass : NSObject{
id delegate;
}
-(void)printDetails;
-(void)setDelegate:(id)newDelegate;
@end
@implementation PrintClass
-(void)printDetails{
NSLog(@"Print details");
[delegate processCompleted];
}
-(void)setDelegate:(id)newDelegate{
delegate = newDelegate;
}
@end
@interface SampleClass : NSObject<PrintProtocolDelegate>
-(void)startAction;
@end
@implementation SampleClass
-(void)startAction{
PrintClass *printClass = [[PrintClass alloc]init];
[printClass setDelegate:self];
[printClass printDetails];
}
-(void)processCompleted{
NSLog(@"print process completed");
}
@end
int main(int args, const char * argv[]){
SampleClass *sampleClass = [[SampleClass alloc]init];
[sampleClass startAction];
return 0;
}
快速枚举
集合的主要目的是提供一种有效存储和检索对象的通用方法。
有几种不同类型的集合。 虽然它们都能实现能够容纳其他对象的相同目的,但它们的主要区别在于检索对象的方式。
int main(){
NSArray * array = [[NSArray alloc] initWithObjects:@"1", @"2", @"3", nil];
//1 2 3
for(NSString * aString in array){
NSLog(@"VAlue: %@", aString);
}
//3 2 1
for(NSString * bString in [array reverseObjectEnumerator]){
NSLog(@"Value: %@", bString);
}
return 0;
}
内存管理
Objective-C内存管理技术可分为两类:“手动保留或释放”或“自动参考计数”。
在MRR中,通过跟踪自己的对象来明确管理内存。这是使用一个称为引用计数的模型实现的,Foundation类NSObject与运行时环境一起提供。
MRR和ARC之间的唯一区别是保留和释放,前者是手动处理,而后者则自动处理。
手动保留释放
@interface SampleClass:NSObject
- (id)sampleMethod;
@end
@implementation SampleClass
- (id)sampleMethod {
NSLog(@"Hello, World! \n");
}
- (id)dealloc {
NSLog(@"Object deallocated");
[super dealloc];
}
@end
int main() {
SampleClass *sampleClass = [[SampleClass alloc]init];
[sampleClass sampleMethod];
NSLog(@"Retain Count after initial allocation: %d",
[sampleClass retainCount]);
[sampleClass retain];
NSLog(@"Retain Count after retain: %d", [sampleClass retainCount]);
[sampleClass release];
NSLog(@"Retain Count after release: %d", [sampleClass retainCount]);
[sampleClass release];
NSLog(@"SampleClass dealloc will be called before this");
sampleClass = nil;
return 0;
}
自动参考计数
@interface SampleClass:NSObject
- (id)sampleMethod;
@end
@implementation SampleClass
- (id)sampleMethod {
NSLog(@"Hello, World! \n");
}
- (id)dealloc {
NSLog(@"Object deallocated");
}
@end
int main() {
@autoreleasepool {
SampleClass *sampleClass = [[SampleClass alloc]init];
[sampleClass sampleMethod];
sampleClass = nil;
}
return 0;
}
Runtime
消息发送以及转发机制
调用[receiver selector];后,进行的流程:
1. 编译阶段:[receiver selector];方法被编译器转换为:
Objc_msgSend(receiver, selector) --- 不带参数
Objc_msdSend(receiver, selector, org1, org2, ...) --- 带参数
2. 运行时阶段:消息接受者receiver寻找对应的selector
通过receiver的isa 指针找到receiver的Class (类);
在Class (类)的cache (方法缓存)的散列表中寻找对应的IMP (方法实现);
如果在cache (方法缓存)中没有找到对应的IMP (方法实现)的话,就继续在Class (类)method list (方法列表)中找对应的selector,如果找到,填充到cache (方法缓存)中,并返回selector;
如果在class (类)中没有找到这个selector,就继续在它的superclass (父类)中寻找;
一旦找到对应的selector,直接执行receiver对应的selector方法实现的IMP (方法实现)。
若找不到对应的selector,Runtime系统进入消息转发机制。
3. 运行时消息转发阶段:
动态解析:
通过重写+resolveInstanceMethod: 或者 +resolveClassMethod:方法,利用class_addMethod方法添加其他函数实现;
消息接受者重定向:
如果上一步没有添加其他函数实现,可在当前对象中利用forwardingTargetForSelector:方法将消息的接受者转发给其他对象;
消息重定向:
如果上一步返回值是nil,则利用methodSignatureForSelector:方法获取函数的参数和返回值类型。如果methodSignatureForSelector:返回了一个NSMethodSignature对象(函数签名),Runtime系统就会创建一个NSInvocation对象,并通过forwardInvocation:消息通知当前对象,给予此次消息发送最后一次寻找IMP的机会。
如果methodSignatureForSelector:返回nil。则Runtime系统会发出doesNotRecognizeSelector:消息,程序也就崩溃了。
Method Swizzling黑魔法
Method Swizzling修改了method list (方法列表),使得不同Method (方法)中的键值对发生了交换。比如交换前两个键值分别为:SEL A : IMP A、SEL B : IMP B,交换之后变成了SEL A : IMP B、SEL B : IMP A。
需注意:
1、应该只在+load中执行 ·Method Swizzling·
程序在启动的时候,会先加载所有的类,这时会调用每个类的+load方法。而且在整个程序运行周期中只会调用一次(不包括外部显示调用)。所以在+load方法中进行是最好的选择
2、Method Swizzling 在+load中执行时,不要调用[super load];。
程序在启动的时候,会先加载所有类。如果在+(void)load方法中调用[super load]方法,就会导致父类的Method Swizzling被重复执行两次,而方法交换也被执行了两次,相当于互转了一次方法后,第二次又换回来了,从而是的父类的Method Swizzling失效。
3、Method Swizzling应该总在dispatch_once中执行。
Method Swizzling不是原子操作,dispatch_once可以保证即使在不同的线程中也能确保代码只执行一次。所以,我们应该总在dispatch_once中执行 ·Method Swizzling· 操作,保证方法替换只被执行一次。
4、使用 Method Swizzling 后要记得调用原生方法的实现。
在交换方法实现后记得要调用原生方法的实现。APIs 提供了输入输出的规则,而在输入输出中间的方法实现就是一个看不见的黑盒。交换了方法实现并且一些回调方法不会调用原生方法的实现,这可能造成底层实现的崩溃。
5、避免命名冲突和参数_cmd被篡改。
6、谨慎对待 Method Swizzling
使用 Method Swizzling,会改变非自己拥有的代码。我么使用 Method Swizzling通常会更改一些系统框架的对象方法,或是类方法。我们改变的不只是一个对象实例,而是改变了项目中所有的该类的对象实例,以及所有子类的对象实例。所以,在使用 ·Method Swizzling· 的时候,应该保持足够的谨慎。
7、对于 Method Swizzling 来说,调用顺序很重要
+load方法的调用规则为:
1. 先调用主类,按照编译顺序,顺序的根据继承关系由父类向子类调用;
2. 在调用分类,按照编译顺序,依次调用;
3. +load方法除非主动调用,否则只会调用一次。
这样的调用规则导致了+load方法调用顺序不一定正确。一个顺序可能是:父类 -> 子类 -> 父类类别 -> 子类类别,也可能是父类 -> 子类 -> 子类类别 -> 父类类别。所以 ·Method Swizzling· 的顺序不能保证,那么就不能保证 ·Method Swizzling· 后方法的调用顺序是正确的。
所以被用于 ·Method Swizzling· 的方法必须是当前类自身的方法,如果把继承自父类的IMP复制到自身上面,可能存在问题。如果+load方法调用顺序为:父类 -> 子类 -> 父类类别 -> 子类类别,那么造成的影响就是·调用子类的替换方法并不能正确调起父类分类的替换方法。
Category分类
主要作用是为已经存在的类添加方法。category可以做到在既不子类化,又不侵入一个类的源码,为原有的类添加新的方法,从而实现扩展一个类或者分离一个类的目的。
category分类和extension扩展
看起来两者有些相似,但实质上是不同的东西。Extension是在编译阶段与该类同时编译,是类的一部分;且Extension中声明的方法只能在该类的@implementation中实现,也就是说,对于系统类(如NSArray类)是无法使用扩展的。
Extension不仅可以声明方法,还可以声明成员变量,是Category做不到的。Category的本质是_category_t结构体,可以为类添加对象方法、类方法、协议、属性。
Category的特性是可以在运行时阶段动态的为已有类添加新行为,成员变量的内存布局在编译阶段就确定分配好了,添加成员变量,会破坏原有类的内存布局。
Category中方法、协议、属性附加到类上的操作,是在+load方法执行前进行的。Category分类和class类的+load方法调用顺序为:
1. 先调用主类,按照编译顺序,依次根据继承关系,由父类向子类调用
2. 调用分类,按照编译顺序,依次调用
3. +load方法除了主动调用,否则只会调用一次
Core Animation(核心动画)
在创建UIView对象时,UIView内部会自动创建一个图层(即CALayer对象),通过UIView的layer属性可以访问这个层。
@property(nonatomic,readonly,retain) CALayer *layer;
当UIView需要显示到屏幕上时,会调用drawRect:方法进行绘图,并且会将所有内容绘制在自己的图层上,绘图完毕后,系统会将图层拷贝到屏幕上,于是就完成了UIView的显示。
换句话说,UIView本身不具备显示的功能,是它内部的层才有显示功能。
CALayer的基本属性:
//宽度和高度:
@property CGRect bounds;
//位置(默认指中点,具体由anchorPoint决定):
@property CGPoint position;
//锚点(x,y的范围都是0-1),决定了position的含义:
@property CGPoint anchorPoint;
//背景颜色(CGColorRef类型):
@property CGColorRef backgroundColor;
//形变属性:
@property CATransform3D transform;
CGPoint position 用来设置CALayer在父层的位置,以父层的左上角为原点
CGPoint anchorPoint 称为定位点、锚点,决定着CALayer身上的某点会在position所指的位置,以自己的左上角为原点,默认中心点为(0.5,0.5)。
隐式动画
根层与非根层:
● 每一个UIView内部都默认关联着一个CALayer,我们可用称这个Layer为Root Layer
● 所有的非Root Layer,也就是手动创建的CALayer对象,都存在着隐式动画
当对非Root Layer的部分属性进行修改时,默认会自动产生一些动画效果,而这些属性称为Animatable Properties(可动画属性)。
常见的几个可动画属性:
● bounds:用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画
● backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画
● position:用于设置CALayer的位置。修改这个属性会产生平移动画
可以通过事务关闭隐式动画:
[CATransaction begin];
// 关闭隐式动画
[CATransaction setDisableActions:YES];
self.myview.layer.position = CGPointMake(10, 10);
[CATransaction commit];
CALayer和UIView都可以实现相同的效果,但UIView多一个事件处理功能,UIView可以处理用户的触摸事件。CALayer不可以直接使用UIColor、UIImage。
layer.backgroundColor = [UIColor redColor].CGColor;
CAAnimation
是所有动画对象的父类,负责控制动画的持续时间和速度,是个抽象类,不能直接使用
属性 | 说明 |
duration | 动画的持续时间 |
repeatCount | 重复次数,无限循环(HUGE_VALF或MAXFLOAT) |
repeatDuration | 重复时间 |
removedOnCompletion | 默认YES,动画执行完就从图层上移除,图形恢复到执行前的状态。保持显示动画执行后的状态,就设为NO |
fillMode | 决定当前对象在非action时间段的行为 |
beginTime | 可以设置延迟执行时间,CACurrentMediaTime()为图层的当前时间 |
timingFunction | 速度控制函数 |
delagate | 动画代理 |
CAPropertyAnimation
CAAnimation的子类,也是个抽象类,要想创建动画对象,应该使用它的两个子类:CABasicAnimation和CAKeyframeAnimation。
属性说明,keyPath:通过指定CALayer的一个属性名称为keyPath(NSString类型),并且对CALayer的这个属性的值进行修改,达到相应的动画效果。比如,指定@“position”为keyPath,就修改CALayer的position属性的值,以达到平移的动画效果
CABasicAnimation——基本动画
属性 | 说明 |
fromValue | keyPath相应属性的初始值 |
toValue | keyPath相应属性的结束值 |
CAKeyframeAnimation——关键帧动画
CAPropertyAnimation的子类,与CABasicAnimation的区别是:
CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue),而CAKeyframeAnimation会使用一个NSArray保存这些数值;CABasicAnimation可看做是只有2个关键帧的CAKeyframeAnimation
属性 | 说明 |
values | NSArray对象。里面的元素称为“关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧 |
path | 可以设置一个CGPathRef、CGMutablePathRef,让图层按照路径轨迹移动。path只对CALayer的anchorPoint和position起作用。如果设置了path,那么values将被忽略 |
keyTimes | 可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧。如果没有设置keyTimes,各个关键帧的时间是平分的 |
CAAnimationGroup——动画组
CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行。默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间。
属性 | 说明 |
animations | 用来保存一组动画对象的NSArray |
CATransition——转场动画
CATransition是CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点。
UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果。
属性 | 说明 |
type | 动画过渡类型 |
subtype | 动画过度方向 |
startProgress | 动画起点(在整体动画的百分比) |
endProgress | 动画终点(在整体动画的百分比) |
CADisplayLink
是一种以屏幕刷新频率触发的时钟机制,每秒执行大概60次,可以是绘图与视图的刷新频率保持同步,NSTimer无法确保计时器实际被触发的时间。
使用方法:
// 定义
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(rotationChange)];
// 添加到主循环队列
[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
//暂停
link.paused = YES;
//开始
link.paused = NO;
多线程
NSThread
两种创建方式:
通过NSThread的对象方法(动态创建)
通过NSThread的类方法(静态创建)
线程创建->线程开启->线程取消->线程关闭->线程暂停->设置线程优先级
NSOperation
NSOperation是对GCD的封装,其中有两个核心概念【队列+操作】
1.NSOperation本身是抽象类,只能有它的子类。
2.两大子类分别是:NSBlockOperation、NSInvocationOperation,当然你也可以自定义继承自NSOperation的类。
基本使用
实例化NSOperation的子类,绑定执行的操作。
创建NSOperationQueue队列,将NSOperation实例添加进来。
系统会自动将NSOperationQueue队列中检测取出和执行NSOperation的操作。
GCD
GCD是苹果公司为多核的并行运算提出的解决方案,会自动利用更多的CPU内核(比如双核、四核),会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
使用步骤
指定任务:确定想要做的事
将任务添加到队列中:GCD会自动将队列中的任务取出,放到对应的线程中执行,任务的取出遵循队列的FIFO原则:先进先出,后进后出。
队列
可以分为2大类型:1.并发队列 2.串行队列
使用dispatch_queue_create函数创建队列
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
//const char *label 列队列的名称
//dispatch_queue_attr_t attr 队列的类型
//创建一个并发队列
dispatch_queue_t queue1 = dispatch_queue_create("myQueue1", DISPATCH_QUEUE_CONCURRENT);
//创建一个串行队列
dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_SERIAL);
//获得全局的并发队列
dispatch_queue_t queue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 其中:第一个参数代表全局并发队列的优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 --》 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 --》 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) --》 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN --》 后台
GCD获得串行有2种途径
1. 使用dispatch_queue_create 创建串行队列,创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue4 = dispatch_queue_create("com.520.queue", NULL);
2. 使用主队列 (跟主线程相关联的队列),主队列时GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放在主线程中执行,dispatch_get_main_queue()获得主队列
dispatch_queue_t queue5 = dispatch_get_main_queue();