音频初见
音频初见
基本介绍
音频: 从形式上分为
短音频(音效播放):不需要进度|循环等控制 AudioToolbox.framework
(C语言框架
- 本质:将音频注册到system sound service(简单底层的声音播放服务))
特点: - 播放时间不超过30s
- 数据格式PCM/IMA4
- 音频文件必须打包成.caf,.aif,.wav,(.MP3也可以)
长音频(音乐播放)
- 较大的音频文件,需要进行精确的控制的场景
- AVFoundation.framework
- 支持的音频格式有:AAC,ALAC,HE-AAC,iLBC,IMA4,MP3等.
音效播放
步骤:
- 不要忘记导入头文件: #import \<AudioToolbox/AudioToolbox.h>
- 1.调用AudioServicesCreateSystemSoundID(CFURLRef inFileURL, SystemSoundID* outSystemSoundID)函数获得系统声音ID。
- 2.如果需要监听播放完成操作,则使用AudioServicesAddSystemSoundCompletion( SystemSoundID inSystemSoundID,CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, AudioServicesSystemSoundCompletionProc inCompletionRoutine, void* inClientData)方法注册回调函数。
- 3.调用AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID)
AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID) 方法播放音效(后者带有震动效果)
1 | - (void)audioBox{ |
音乐播放 AVAudioPlayer
AVAudioPlayer支持多种音频格式,而且能够对进度,音量,播放速度等控制
头文件 #import \<AVFoundation/AVFoundation.h>
步骤:
- 1.给定资源路径,或者二进制数据初始化播放器
- 2.设置相关属性
- 3.执行播放,更新相关UI(进度更新,歌词显示(CADislinkplay/NSTimer))
初始化方法
AVAudioPlayer可以接受本地资源路径(NSURL)/NSData类型数据(可以用来加载网络链接),但是实质上AVAudioPlayer不支持流式播放,只能播放完整的文件,使用后者加载网络链接,当播放的音频文件较大或者网络不好就需要过长的时间等待
每一个实例化方法返回一个AVAudioPlayer对象,每一个文件/Data对应一个AVAudioPlayer对象,传入一个NSError的指针,用于判断初始化是否成功.
1 | - (nullable instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError; |
播放控制相关API
1 | //准备播放,分配播放所需的资源,并将其加入内部播放队列,加载音频文件到缓冲区,注意:即使在播放之前音频文件没有加载到缓冲区程序也会隐式调用此方法。 |
属性相关
1 | //获取是否正在播放 |
代理方法
1 | <!--音频播放结束后调用的函数 被中断停止播放并不会被调用--> |
后台播放
1.需要添加info.plist
1 | <key>UIBackgroundModes</key> |
或者
2.添加代码支持1
2
3//设置锁屏仍能继续播放
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
监听耳机事件
1 | //添加通知,拔出耳机后暂停播放 |
后台播放信息显示
1.导入框架 #import \<MediaPlayer/MediaPlayer.h>
2.添加代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
//设置歌曲题目
[dict setObject:@"题目" forKey:MPMediaItemPropertyTitle];
//设置歌手名
[dict setObject:@"歌手" forKey:MPMediaItemPropertyArtist];
//设置专辑名
[dict setObject:@"专辑" forKey:MPMediaItemPropertyAlbumTitle];
//设置显示的图片
UIImage *newImage = [UIImage imageNamed:@"43.png"];
[dict setObject:[[MPMediaItemArtwork alloc] initWithImage:newImage]
forKey:MPMediaItemPropertyArtwork];
//设置歌曲时长
[dict setObject:[NSNumber numberWithDouble:300] forKey:MPMediaItemPropertyPlaybackDuration];
//设置已经播放时长
[dict setObject:[NSNumber numberWithDouble:150] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
//更新字典
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict];
注:实现后台播放显示歌词的方法实现思路:将歌词信息,绘制到专辑图片上进行显示
后台播放远程控制
1.注册接受远程控制1
2
3
4
5
6
7
8
9
10
11- (void)viewDidAppear:(BOOL)animated {
// 接受远程控制
[self becomeFirstResponder];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
- (void)viewDidDisappear:(BOOL)animated {
// 取消远程控制
[self resignFirstResponder];
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
2.实现监听方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
if (event.type == UIEventTypeRemoteControl) {
//判断是否为远程控制
switch (event.subtype) {
//播放
case UIEventSubtypeRemoteControlPlay:
if (![self.audioPlayer isPlaying]) {
[self.audioPlayer play];
}
break;
case UIEventSubtypeRemoteControlPause:
//暂停
if ([self.audioPlayer isPlaying]) {
[self.audioPlayer pause];
}
break;
case UIEventSubtypeRemoteControlNextTrack:
NSLog(@"下一首");
break;
case UIEventSubtypeRemoteControlPreviousTrack:
NSLog(@"上一首 ");
break;
default:
break;
}
}
}
注:
event是事件类型对象,type是事件类型枚举1
2
3
4
5
6typedef NS_ENUM(NSInteger, UIEventType) {
UIEventTypeTouches, //触摸事件
UIEventTypeMotion, //运动事件
UIEventTypeRemoteControl, // 远程控制事件
UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0),//物理按钮事件类型
};
subtype中的枚举便是点击这些控制键后传递给我们的消息,我们可以根据这些消息在app内做逻辑处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29typedef NS_ENUM(NSInteger, UIEventSubtype) {
// available in iPhone OS 3.0
UIEventSubtypeNone = 0,
// for UIEventTypeMotion, available in iPhone OS 3.0
//摇晃
UIEventSubtypeMotionShake = 1,
//UIEventTypeRemoteControl相关的枚举信息
// for UIEventTypeRemoteControl, available in iOS 4.0
//点击播放按钮或者耳机线控中间那个按钮
UIEventSubtypeRemoteControlPlay = 100,
//点击暂停按钮
UIEventSubtypeRemoteControlPause = 101,
//点击停止按钮
UIEventSubtypeRemoteControlStop = 102,
//点击播放与暂停开关按钮(iphone抽屉中使用这个)
UIEventSubtypeRemoteControlTogglePlayPause = 103,
//点击下一曲按钮或者耳机中间按钮两下
UIEventSubtypeRemoteControlNextTrack = 104,
//点击上一曲按钮或者耳机中间按钮三下
UIEventSubtypeRemoteControlPreviousTrack = 105,
//快退开始 点击耳机中间按钮三下不放开
UIEventSubtypeRemoteControlBeginSeekingBackward = 106,
//快退结束 耳机快退控制松开后
UIEventSubtypeRemoteControlEndSeekingBackward = 107,
//开始快进 耳机中间按钮两下不放开
UIEventSubtypeRemoteControlBeginSeekingForward = 108,
//快进结束 耳机快进操作松开后
UIEventSubtypeRemoteControlEndSeekingForward = 109,
};
拓展:后台播放远程控制的相关自定义
MPRemoteCommandCenter属于\<MediaPlayer/MediaPlayer.h>框架,是一个单例类,处理远程控制中心事件
可以自定义你的控制中心,锁屏时控制,siri语音控制.它管理一系列命令Playback Commands,Feedback Command,Previous/Next Track Commands),Skip Interval Commands,Seek Command等
MPRemoteCommand是这些所有命令的父类
下面简单介绍:1
2
3
4
5
6
7
8
9
10
11//是否开启命令
@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
//Target-action 添加事件处理
- (void)addTarget:(id)target action:(SEL)action;
//当属性enable = NO时,移除
- (void)removeTarget:(id)target action:(nullable SEL)action;
- (void)removeTarget:(nullable id)target;
//返回一个事件对象 MPRemoteCommandEvent是一个命令事件的父类,对应不同的命令进行了继承扩展,主要添加一些属性,比如拖拽音乐进度的positionTime
- (id)addTargetWithHandler:(MPRemoteCommandHandlerStatus(^)(MPRemoteCommandEvent *event))handler;
远程控制三部曲
1:监听远程控制事件1
- (void)beginReceivingRemoteControlEvents
2:捕获远程控制事件1
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
3.取消远程控制事件1
- (void)endReceivingRemoteControlEvents
网易云的锁屏实现
注:添加的RemoteComand必须设置Enable为YES,并添加Target才会显示,并且添加相同类型RemoteComand,有优先级的选择显示,更改原有默认显示的上一曲,播放/暂停,下一曲会失效,需要重新添加
1 | MPRemoteCommandCenter *rcc = [MPRemoteCommandCenter sharedCommandCenter]; |
效果图:
锁屏控制播放进度
主要使用:MPChangePlaybackPositionCommand1
2
3
4
5
6
7
8//改变音乐播放进度
MPChangePlaybackPositionCommand *changePlayBackPosition = [rcc changePlaybackPositionCommand];
[changePlayBackPosition setEnabled:YES];
[changePlayBackPosition addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
MPChangePlaybackPositionCommandEvent *events = (MPChangePlaybackPositionCommandEvent *)event;
self.audioPlayer.currentTime = events.positionTime;
return MPRemoteCommandHandlerStatusSuccess;
}];
流式音频播放(播放网络音频)
使用AudioToolbox框架中的音频队列服务Audio Queue Services。
第三方框架:FreeStreamer
#import “FSAudioStream.h”1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//初始化
FSAudioStream *player = [[FSAudioStream alloc]initWithUrl:[self getFileUrl]];
player.onFailure = ^(FSAudioStreamError error, NSString *errorDescription) {
NSLog(@"播放错误error:%@",errorDescription);
};
player.onCompletion = ^{
NSLog(@"播放完成");
};
//设置音量
[player setVolume:0.5];
//播放
[player play];
//添加引用
self.audioStream = player;
豆瓣:DOUAudioStreamer1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24#import "DOUAudioStreamer.h"
@interface AudioModel : NSObject<DOUAudioFile>
@property (nonatomic, strong) NSURL *audioFileURL;
@end
@implementation AudioModel
- (NSURL *)audioFileURL{
NSString *urlStr= @"http://sc1.111ttt.com/2017/1/03/07/296072048390.mp3";
NSURL *url=[NSURL URLWithString:urlStr];
self.audioFileURL = url;
return url;
}
@end
AudioModel *file = [[AudioModel alloc]init];
[file audioFileURL];
self.stream = [[DOUAudioStreamer alloc]initWithAudioFile:file];
[self.stream play];
参考:
https://developer.apple.com/library/content/samplecode/MPRemoteCommandSample/Introduction/Intro.html
音频初见
音频初见
基本介绍
音频: 从形式上分为
短音频(音效播放):不需要进度|循环等控制 AudioToolbox.framework
(C语言框架
- 本质:将音频注册到system sound service(简单底层的声音播放服务))
特点: - 播放时间不超过30s
- 数据格式PCM/IMA4
- 音频文件必须打包成.caf,.aif,.wav,(.MP3也可以)
长音频(音乐播放)
- 较大的音频文件,需要进行精确的控制的场景
- AVFoundation.framework
- 支持的音频格式有:AAC,ALAC,HE-AAC,iLBC,IMA4,MP3等.
音效播放
步骤:
- 不要忘记导入头文件: #import \<AudioToolbox/AudioToolbox.h>
- 1.调用AudioServicesCreateSystemSoundID(CFURLRef inFileURL, SystemSoundID* outSystemSoundID)函数获得系统声音ID。
- 2.如果需要监听播放完成操作,则使用AudioServicesAddSystemSoundCompletion( SystemSoundID inSystemSoundID,CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, AudioServicesSystemSoundCompletionProc inCompletionRoutine, void* inClientData)方法注册回调函数。
- 3.调用AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID)
AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID) 方法播放音效(后者带有震动效果)
1 | - (void)audioBox{ |
音乐播放 AVAudioPlayer
AVAudioPlayer支持多种音频格式,而且能够对进度,音量,播放速度等控制
头文件 #import \<AVFoundation/AVFoundation.h>
步骤:
- 1.给定资源路径,或者二进制数据初始化播放器
- 2.设置相关属性
- 3.执行播放,更新相关UI(进度更新,歌词显示(CADislinkplay/NSTimer))
初始化方法
AVAudioPlayer可以接受本地资源路径(NSURL)/NSData类型数据(可以用来加载网络链接),但是实质上AVAudioPlayer不支持流式播放,只能播放完整的文件,使用后者加载网络链接,当播放的音频文件较大或者网络不好就需要过长的时间等待
每一个实例化方法返回一个AVAudioPlayer对象,每一个文件/Data对应一个AVAudioPlayer对象,传入一个NSError的指针,用于判断初始化是否成功.
1 | - (nullable instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError; |
播放控制相关API
1 | //准备播放,分配播放所需的资源,并将其加入内部播放队列,加载音频文件到缓冲区,注意:即使在播放之前音频文件没有加载到缓冲区程序也会隐式调用此方法。 |
属性相关
1 | //获取是否正在播放 |
代理方法
1 | <!--音频播放结束后调用的函数 被中断停止播放并不会被调用--> |
后台播放
1.需要添加info.plist
1 | <key>UIBackgroundModes</key> |
或者
2.添加代码支持1
2
3//设置锁屏仍能继续播放
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
监听耳机事件
1 | //添加通知,拔出耳机后暂停播放 |
后台播放信息显示
1.导入框架 #import \<MediaPlayer/MediaPlayer.h>
2.添加代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
//设置歌曲题目
[dict setObject:@"题目" forKey:MPMediaItemPropertyTitle];
//设置歌手名
[dict setObject:@"歌手" forKey:MPMediaItemPropertyArtist];
//设置专辑名
[dict setObject:@"专辑" forKey:MPMediaItemPropertyAlbumTitle];
//设置显示的图片
UIImage *newImage = [UIImage imageNamed:@"43.png"];
[dict setObject:[[MPMediaItemArtwork alloc] initWithImage:newImage]
forKey:MPMediaItemPropertyArtwork];
//设置歌曲时长
[dict setObject:[NSNumber numberWithDouble:300] forKey:MPMediaItemPropertyPlaybackDuration];
//设置已经播放时长
[dict setObject:[NSNumber numberWithDouble:150] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
//更新字典
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict];
注:实现后台播放显示歌词的方法实现思路:将歌词信息,绘制到专辑图片上进行显示
后台播放远程控制
1.注册接受远程控制1
2
3
4
5
6
7
8
9
10
11- (void)viewDidAppear:(BOOL)animated {
// 接受远程控制
[self becomeFirstResponder];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
- (void)viewDidDisappear:(BOOL)animated {
// 取消远程控制
[self resignFirstResponder];
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
2.实现监听方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
if (event.type == UIEventTypeRemoteControl) {
//判断是否为远程控制
switch (event.subtype) {
//播放
case UIEventSubtypeRemoteControlPlay:
if (![self.audioPlayer isPlaying]) {
[self.audioPlayer play];
}
break;
case UIEventSubtypeRemoteControlPause:
//暂停
if ([self.audioPlayer isPlaying]) {
[self.audioPlayer pause];
}
break;
case UIEventSubtypeRemoteControlNextTrack:
NSLog(@"下一首");
break;
case UIEventSubtypeRemoteControlPreviousTrack:
NSLog(@"上一首 ");
break;
default:
break;
}
}
}
注:
event是事件类型对象,type是事件类型枚举1
2
3
4
5
6typedef NS_ENUM(NSInteger, UIEventType) {
UIEventTypeTouches, //触摸事件
UIEventTypeMotion, //运动事件
UIEventTypeRemoteControl, // 远程控制事件
UIEventTypePresses NS_ENUM_AVAILABLE_IOS(9_0),//物理按钮事件类型
};
subtype中的枚举便是点击这些控制键后传递给我们的消息,我们可以根据这些消息在app内做逻辑处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29typedef NS_ENUM(NSInteger, UIEventSubtype) {
// available in iPhone OS 3.0
UIEventSubtypeNone = 0,
// for UIEventTypeMotion, available in iPhone OS 3.0
//摇晃
UIEventSubtypeMotionShake = 1,
//UIEventTypeRemoteControl相关的枚举信息
// for UIEventTypeRemoteControl, available in iOS 4.0
//点击播放按钮或者耳机线控中间那个按钮
UIEventSubtypeRemoteControlPlay = 100,
//点击暂停按钮
UIEventSubtypeRemoteControlPause = 101,
//点击停止按钮
UIEventSubtypeRemoteControlStop = 102,
//点击播放与暂停开关按钮(iphone抽屉中使用这个)
UIEventSubtypeRemoteControlTogglePlayPause = 103,
//点击下一曲按钮或者耳机中间按钮两下
UIEventSubtypeRemoteControlNextTrack = 104,
//点击上一曲按钮或者耳机中间按钮三下
UIEventSubtypeRemoteControlPreviousTrack = 105,
//快退开始 点击耳机中间按钮三下不放开
UIEventSubtypeRemoteControlBeginSeekingBackward = 106,
//快退结束 耳机快退控制松开后
UIEventSubtypeRemoteControlEndSeekingBackward = 107,
//开始快进 耳机中间按钮两下不放开
UIEventSubtypeRemoteControlBeginSeekingForward = 108,
//快进结束 耳机快进操作松开后
UIEventSubtypeRemoteControlEndSeekingForward = 109,
};
拓展:后台播放远程控制的相关自定义
MPRemoteCommandCenter属于\<MediaPlayer/MediaPlayer.h>框架,是一个单例类,处理远程控制中心事件
可以自定义你的控制中心,锁屏时控制,siri语音控制.它管理一系列命令Playback Commands,Feedback Command,Previous/Next Track Commands),Skip Interval Commands,Seek Command等
MPRemoteCommand是这些所有命令的父类
下面简单介绍:1
2
3
4
5
6
7
8
9
10
11//是否开启命令
@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
//Target-action 添加事件处理
- (void)addTarget:(id)target action:(SEL)action;
//当属性enable = NO时,移除
- (void)removeTarget:(id)target action:(nullable SEL)action;
- (void)removeTarget:(nullable id)target;
//返回一个事件对象 MPRemoteCommandEvent是一个命令事件的父类,对应不同的命令进行了继承扩展,主要添加一些属性,比如拖拽音乐进度的positionTime
- (id)addTargetWithHandler:(MPRemoteCommandHandlerStatus(^)(MPRemoteCommandEvent *event))handler;
远程控制三部曲
1:监听远程控制事件1
- (void)beginReceivingRemoteControlEvents
2:捕获远程控制事件1
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
3.取消远程控制事件1
- (void)endReceivingRemoteControlEvents
网易云的锁屏实现
注:添加的RemoteComand必须设置Enable为YES,并添加Target才会显示,并且添加相同类型RemoteComand,有优先级的选择显示,更改原有默认显示的上一曲,播放/暂停,下一曲会失效,需要重新添加
1 | MPRemoteCommandCenter *rcc = [MPRemoteCommandCenter sharedCommandCenter]; |
效果图:
锁屏控制播放进度
主要使用:MPChangePlaybackPositionCommand1
2
3
4
5
6
7
8//改变音乐播放进度
MPChangePlaybackPositionCommand *changePlayBackPosition = [rcc changePlaybackPositionCommand];
[changePlayBackPosition setEnabled:YES];
[changePlayBackPosition addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
MPChangePlaybackPositionCommandEvent *events = (MPChangePlaybackPositionCommandEvent *)event;
self.audioPlayer.currentTime = events.positionTime;
return MPRemoteCommandHandlerStatusSuccess;
}];
流式音频播放(播放网络音频)
使用AudioToolbox框架中的音频队列服务Audio Queue Services。
第三方框架:FreeStreamer
#import “FSAudioStream.h”1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//初始化
FSAudioStream *player = [[FSAudioStream alloc]initWithUrl:[self getFileUrl]];
player.onFailure = ^(FSAudioStreamError error, NSString *errorDescription) {
NSLog(@"播放错误error:%@",errorDescription);
};
player.onCompletion = ^{
NSLog(@"播放完成");
};
//设置音量
[player setVolume:0.5];
//播放
[player play];
//添加引用
self.audioStream = player;
豆瓣:DOUAudioStreamer1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24#import "DOUAudioStreamer.h"
@interface AudioModel : NSObject<DOUAudioFile>
@property (nonatomic, strong) NSURL *audioFileURL;
@end
@implementation AudioModel
- (NSURL *)audioFileURL{
NSString *urlStr= @"http://sc1.111ttt.com/2017/1/03/07/296072048390.mp3";
NSURL *url=[NSURL URLWithString:urlStr];
self.audioFileURL = url;
return url;
}
@end
AudioModel *file = [[AudioModel alloc]init];
[file audioFileURL];
self.stream = [[DOUAudioStreamer alloc]initWithAudioFile:file];
[self.stream play];
参考:
https://developer.apple.com/library/content/samplecode/MPRemoteCommandSample/Introduction/Intro.html
Window 对象
Window
核心对象:它是客户端JavaScript程序的全局对象
定时器
延迟调用setTimeout() 间歇调用setInterval()
单词interval:间隔,间歇,空隙;差距,差别,隔阂
at invervals 每隔一定的时间或距离,不时;到处
in the intervals 不一会儿,不久
- setTimeout()指定时间(毫秒)之后单次调用传入的函数,setInterval()指定毫秒数的间隔里重复调用传入的函数
- 同为全局函数,window对象的方法
- 返回值可以传递给clearTimeout(),clearInterval()用于取消函数的执行
- 参数,第一个参数为调用的函数(也可以传入一个字符串,但是实质为求值,执行eval());第二个参数为毫秒数,表示时间(传入的方法执行需要的时间间隔)
1 | //延迟调用 |
JavaScript 对象
对象
对象是 JavaScript 的数据类型。它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值,因此我们可以把它看成是从字符串到值的映射。对象是动态的,可以随时新增和删除自有属性。对象除了可以保持自有的属性,还可以从一个称为原型的对象继承属性,这种「原型式继承(prototypal inheritance)」是 JavaScript 的核心特征。
对象最常见的用法是创建(create)、设置(set)、查找(query)、删除(delete)、检测(test)和枚举(enumerate)它的属性。
属性包括名字和值。属性名可以是包含空字符串在内的任意字符串,但对象中不能存在两个同名的属性。值可以是任意 JavaScript 值,或者在 ECMAScript 5 中可以是 getter
或 setter
函数。
除了名字和值之外,每个属性还有一些与之相关的值,称为「属性特性(property attribute)」:
- 可写(writable attribute),表明是否可以设置该属性的值。
- 可枚举(enumerable attribute),表明是否可以通过
for-in
循环返回该属性。 - 可配置(configurable attribute),表明是否可以删除或修改该属性。
在 ECMAScript 5 之前,通过代码给对象创建的所有属性都是可写的、可枚举的和可配置的。在 ECMAScript 5 中则可以对这些特性加以配置。
除了包含属性特性之外,每个对象还拥有三个相关的「对象特性(object attribute)」:
- 对象的类(class),是一个标识对象类型的字符串。
- 对象的原型(prototype),指向另外一个对象,本对象的属性继承自它的原型对象。
- 对象的扩展标记(extensible flag),指明了在 ECMAScript 5 中是否可以向该对象添加新属性。
最后,用下面术语来对 JavaScript 的「三类对象」和「两类属性」进行区分:
- 内置对象(native object),是由 JavaScript 规范定义的对象或类。例如,数组、函数、日期和正则表达式都是内置对象。
- 宿主对象(host object),是由 JavaScript 解释器所嵌入的宿主环境(比如 Web 浏览器)定义的。客户端 JavaScript 中表示网页结构的 HTMLElement 对象均是宿主对象。
- 自定义对象(user-defined object),是由运行中的 JavaScript 代码创建的对象。
- 自有属性(own property),是直接在对象中定义的属性。
- 继承属性(inherited property),是在对象的原型对象中定义的属性。
创建对象
可以使用对象字面量、new
关键字和 ECMAScript 5 中的 Object.create()
函数来创建对象。
使用对象字面量创建对象(推荐)
创建对象最简单的方式就是在 JavaScript 代码中使用对象字面量。对象字面量是由若干名值对组成的映射表,名值对中间用冒号分隔,名值对之间用逗号分隔,整个映射表用花括号括起来。属性名可以是 JavaScript 标识符也可以是字符串直接量(包括空字符串)。属性的值可以是任意类型的 JavaScript 表达式,表达式的值(可以是原始值也可以是对象值)就是这个属性的值。例如:
1 | // 推荐写法 |
使用 new
关键字创建对象
new 关键字创建并初始化一个新对象。关键字 new 后跟随一个函数调用。这里的函数称做构造函数(constructor),构造函数用以初始化一个新创建的对象。JavaScript 语言核心中的原始类型都包含内置构造函数。例如:
1 | var person = new Object(); |
其中 var person = new Object();
等价于 var person = {};
。
使用 Object.create()
函数创建对象
ECMAScript 5 定义了一个名为 Object.create()
的方法,它创建一个新对象,其中第一个参数是这个对象的原型。Object.create()
提供第二个可选参数,用以对对象的属性进行进一步描述。Object.create()
是一个静态函数,而不是提供给某个对象调用的方法。使用它的方法很简单,只须传入所需的原型对象即可。例如:
1 | var person = Object.create(Object.prototype); |
其中 var person = Object.create(Object.prototype);
也等价于 var person = {};
。
原型(prototype)
所有通过对象字面量创建的对象都具有同一个原型对象,并可以通过 JavaScript 代码 Object.prototype
获得对原型对象的引用。通过关键字 new
和构造函数调用创建的对象的原型就是构造函数的 prototype
属性的值。因此,同使用 {}
创建对象一样,通过 new Object()
创建的对象也继承自 Object.prototype
。同样,通过 new Array()
创建的对象的原型就是 Array.prototype
,通过 new Date()
创建的对象的原型就是 Date.prototype
。
没有原型的对象为数不多,Object.prototype
就是其中之一。它不继承任何属性。其他原型对象都是普通对象,普通对象都具有原型。所有的内置构造函数(以及大部分自定义的构造函数)都具有一个继承自 Object.prototype
的原型。例如,Date.prototype
的属性继承自 Object.prototype
,因此由 new Date()
创建的 Date
对象的属性同时继承自 Date.prototype
和 Object.prototype
。
这一系列链接的原型对象就是所谓的「原型链(prototype chain)」。
属性的查询和设置
前面有提到过,可以通过点 .
或方括号 []
运算符来获取属性的值。对于点 .
来说,左侧应当是一个对象,右侧必须是一个以属性名称命名的简单标识符。对于方括号来说 []
,方括号内必须是一个计算结果为字符串的表达式,这个字符串就是属性的名称。例如:
1 | // 推荐写法 |
和获取属性的值写法一样,通过点和方括号也可以创建属性或给属性赋值,但需要将它们放在赋值表达式的左侧。例如:
1 | // 推荐写法 |
当使用方括号时,方括号内的表达式必须返回字符串。更严格地讲,表达式必须返回字符串或返回一个可以转换为字符串的值。
属性的访问错误
查询一个不存在的属性并不会报错,如果在对象 o
自身的属性或继承的属性中均未找到属性 x
,属性访问表达式 o.x
返回 undefined
。例如:
1 | var person = {}; |
但是,如果对象不存在,那么试图查询这个不存在的对象的属性就会报错。null
和 undefined
值都没有属性,因此查询这些值的属性会报错。例如:
1 | var person = {}; |
除非确定 person
和 person.wife
都是对象,否则不能这样写表达式 person.wife.name
,因为会报「未捕获的错误类型」,下面提供了两种避免出错的方法:
1 | // 冗余但易懂的写法 |
删除属性
delete
运算符用来删除对象属性,事实上 delete
只是断开属性和宿主对象的联系,并没有真正的删除它。delete
运算符只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上删除它,而且这会影响到所有继承自这个原型的对象)。
检测属性
JavaScript 对象可以看做属性的集合,我们经常会检测集合中成员的所属关系(判断某个属性是否存在于某个对象中)。可以通过 in
运算符、hasOwnPreperty()
和 propertyIsEnumerable()
来完成这个工作,甚至仅通过属性查询也可以做到这一点。
in
运算符的左侧是属性名(字符串),右侧是对象。如果对象的自有属性或继承属性中包含这个属性则返回 true
。例如:
1 | var o = { x: 1 } |
对象的 hasOwnProperty()
方法用来检测给定的名字是否是对象的自有属性。对于继承属性它将返回 false
。例如:
1 | var o = { x: 1 } |
propertyIsEnumerable()
是 hasOwnProperty()
的增强版,只有检测到是自有属性且这个属性的可枚举性(enumerable attribute)为 true
时它才返回 true
。某些内置属性是不可枚举的。通常由 JavaScript 代码创建的属性都是可枚举的,除非在 ECMAScript 5 中使用一个特殊的方法来改变属性的可枚举性。例如:
1 | var o = inherit({ y: 2 }); |
除了使用 in
运算符之外,另一种更简便的方法是使用 !==
判断一个属性是否是 undefined
。例如:
1 | var o = { x: 1 } |
然而有一种场景只能使用 in
运算符而不能使用上述属性访问的方式。in
可以区分不存在的属性和存在但值为 undefined
的属性。例如:
1 | var o = { x: undefined } // 属性被显式赋值为undefined |
枚举属性
除了检测对象的属性是否存在,我们还会经常遍历对象的属性。通常使用 for-in
循环遍历,ECMAScript 5 提供了两个更好用的替代方案。
for-in
循环可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承的属性),把属性名称赋值给循环变量。对象继承的内置方法不可枚举的,但在代码中给对象添加的属性都是可枚举的。例如:
1 | var o = {x:1, y:2, z:3}; // 三个可枚举的自有属性 |
有许多实用工具库给 Object.prototype
添加了新的方法或属性,这些方法和属性可以被所有对象继承并使用。然而在 ECMAScript 5 标准之前,这些新添加的方法是不能定义为不可枚举的,因此它们都可以在 for-in
循环中枚举出来。为了避免这种情况,需要过滤 for-in
循环返回的属性,下面两种方式是最常见的:
1 | for(p in o) { |
除了 for-in
循环之外,ECMAScript 5 定义了两个用以枚举属性名称的函数。第一个是 Object.keys()
,它返回一个数组,这个数组由对象中可枚举的自有属性的名称组成。第二个是 Object.getOwnPropertyNames()
,它和 Ojbect.keys()
类似,只是它返回对象的所有自有属性的名称,而不仅仅是可枚举的属性。在 ECMAScript 3 中是无法实现的类似的函数的,因为 ECMAScript 3 中没有提供任何方法来获取对象不可枚举的属性。
属性的 getter
和 setter
我们知道,对象属性是由名字、值和一组特性(attribute)构成的。在 ECMAScript 5 中,属性值可以用一个或两个方法替代,这两个方法就是 getter
和 setter
。由 getter
和 setter
定义的属性称做「存取器属性(accessor property)」,它不同于「数据属性(data property)」,数据属性只有一个简单的值。
当程序查询存取器属性的值时,JavaScript 调用 getter
方法。这个方法的返回值就是属性存取表达式的值。当程序设置一个存取器属性的值时,JavaScript 调用 setter
方法,将赋值表达式右侧的值当做参数传入 setter
。从某种意义上讲,这个方法负责「设置」属性值。可以忽略 setter
方法的返回值。
和数据属性不同,存取器属性不具有可写性(writable attribute)。如果属性同时具有 getter
和 setter
方法,那么它是一个读/写属性。如果它只有 getter
方法,那么它是一个只读属性。如果它只有 setter
方法,那么它是一个只写属性,读取只写属性总是返回 undefined
。定义存取器属性最简单的方法是使用对象直接量语法的一种扩展写法。例如:
1 | var o = { |
存取器属性定义为一个或两个和属性同名的函数,这个函数定义没有使用 function
关键字,而是使用 get
或 set
。注意,这里没有使用冒号将属性名和函数体分隔开,但在函数体的结束和下一个方法或数据属性之间有逗号分隔。
序列化对象(JSON)
对象序列化(serialization)是指将对象的状态转换为字符串,也可将字符串还原为对象。ECMAScript 5 提供了内置函数 JSON.stringify()
和 JSON.parse()
用来序列化和还原 JavaScript 对象。这些方法都使用 JSON 作为数据交换格式,JSON 的全称是「JavaScript 对象表示法(JavaScript Object Notation)」,它的语法和 JavaScript 对象与数组直接量的语法非常相近。例如:
1 | o = {x:1, y:{z:[false,null,""]}}; // 定义一个对象 |
JSON 的语法是 JavaScript 语法的子集,它并不能表示 JavaScript 里的所有值。它支持对象、数组、字符串、无穷大数字、true
、false
和 null
,可以序列化和还原它们。NaN
、Infinity
和 -Infinity
序列化的结果是 null
,日期对象序列化的结果是 ISO 格式的日期字符串(参照 Date.toJSON()
函数),但 JSON.parse()
依然保留它们的字符串形态,而不会将它们还原为原始日期对象。函数、RegExp
、Error
对象和 undefined
值不能序列化和还原。JSON.stringify()
只能序列化对象可枚举的自有属性。对于一个不能序列化的属性来说,在序列化后的输出字符串中会将这个属性省略掉。JSON.stringify()
和 JSON.parse()
都可以接收第二个可选参数,通过传入需要序列化或还原的属性列表来定制自定义的序列化或还原操作。
Swift 2.2 新特性NOTE
Swift 2.2 新特性NOTE
慕课网:http://www.imooc.com/learn/750
1. 弃用 ++,–运算符
var a = 3
//a++
a += 1
//a--
a -= 1
为什么取消
- –a ; a– 容易让人迷惑
- += 更符合语意
2. Deprecate-C-Style-For-Loop
// 使用区间运算符
for i in 1...10{
print(i)
}
for i in 1..<10{
print(i)
}
// 反向遍历可以使用reversed
for i in (1...10).reversed(){
print(i)
}
for i in (1..<10).reversed(){
print(i)
}
// 使用stride to表示不包括类似于区间 ..<
for i in stride(from:0, to:10, by:2){
print(i)
}
//through表示包含 类似于区间 ...
for i in stride(from:0, through:10, by:2){
print(i)
}
//递减 步长为-2
for i in stride(from:10, to:0, by:-2){
print(i)
}
//使用浮点数
for i in stride(from:0.5, to:1.5, by:0.1){
print(i)
}
3.Tuple的比较, Deprecate-Tuple-Splat(没用过)
// tuple 的比较
let score1 = (chinese:90, math:95)
let score2 = (chinese:90, math:100)
let score3 = (chinese:100, math:90)
score1 < score2 //ture
score3 < score2 //false
4. Selectors-Can-Not-Be-a-String
//以前使用字符串,容易出现拼写错误
//button.addTarget(self, action: "click", for: .touchUpInside)
button.addTarget(self, action: #selector(click), for: .touchUpInside)
Block
Category
objc2.0,作用:为已经存在的类添加方法
- 声明私有方法
- 模拟多继承
- 不同的功能组织到不同的category里,减少单个文件的体积,由多个开发者共同完成一个类
- 按需加载想要的category
###Category与Extesion
Extesion是一个匿名类别,在编译期间决定,作为类的组成部分,在编译期间与头文件里的@interface和实现文件里面的@implement组成一个完成的类,与类共存亡.
Extesion一般用来隐藏类的私有信息,只有拥有一个类的源码才能添加,不能为系统类添加Extesion.
Category在运行期决议的,所以无法添加实例变量(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内存布局,这对编译型语言是灾难性的)
###方法覆盖
1)、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA
2)、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休^_^,殊不知后面可能还有一样名字的方法。
###Category的Load
1)、在类的+load方法调用的时候,我们可以调用category中声明的方法么?
2)、这么些个+load方法,调用顺序是咋样的呢?
1)、可以调用,因为附加category到类的工作会先于+load方法的执行
2)、+load的执行顺序是先类,后category,而category的+load执行顺序是根据编译顺序决定的。
###关联对象
category里面是无法为category添加实例变量的。但是我们很多时候需要在category中添加和对象关联的值,这个时候可以求助关联对象来实现。
Block
Block的理解
####什么是Block
带有自动变量的匿名函数
- 自动变量
- 匿名函数
####Block的使用
- 作为变量
1 | //定义 |
- 作为属性
1 | @property (nonatomic,copy)returnType (^blockName)(Parameters); |
- 作为函数参数
1 | - (void)testMethodWithHandler:(returnType (^blockName)(Parameters))block; |
- 使用typedef
1 | typedef returntype (^BlockName)(Parameters); |
####Block的本质
1 | #define BLOCK_DESCRIPTOR_1 1 |
objc中对象的定义:首地址是isa的结构体指针,所以block是对象类型
block的class类型:
- _NSConcreteStackBlock 栈上创建的block
- _NSConcreteMallocBlock 堆上创建的block
- _NSConcreteGlobalBlock 作为全局变量的block
- _NSConcreteWeakBlockVariable
- _NSConcreteAutoBlock
- _NSConcreteFinalizingBlock
#####_NSConcreteGlobalBlock
创建block时,就是声明struct,初始化struct的成员;执行block,就是调用struct的函数指针,编译完成之后,block中的代码会被提取出来,成为单独的C函数,调用这个C函数,并把struct指针传递过去.block的实际作用就相当于C语言的匿名函数.
全局block:block内部没有捕获任何外部变量就是一个全局block类型,调用block(),和C语言的函数无异,不需要考虑其生命周期
#####_NSConcreteStackBlock
当block内部引用了外部变量,当struct被创建时,它是存在于函数的栈帧上的,其class是固定的_NSConcreteStackBlock类型.其捕获的变量是会赋值到结构体的成员上,所以当block初始化完成之后,捕获的变量不能更改.
当函数返回时,函数的栈帧被销毁,这个block的内存也会被清除.所以当函数结束后仍然需要这个block时,就必须用Block_copy()方法将它拷贝到堆上,核心动作即(申请内存,将栈数据复制过去,将class改成_NSConcreteMallocBlock,向捕获的对象发送retain,增加block的引用计数)
block就是个匿名函数,只不过我们给了一个变量来引用这个匿名函数,在需要的时候调用。但是,栈block会随着函数栈帧的销毁而销毁,这样一来,我们用之前做引用的变量再去调用这么一块被销毁的内存,就会出现内存崩溃。
KVC与KVO
##KVC与KVO
###KVC(key-value-coding)键值编码
用来给一个对象的属性进行赋值或者访问.
通过类别的方式添加
@interface NSObject(NSKeyValueCoding)
使用:
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
第二个方法通过路径的方式的访问,即可以访问成员变量是对象的属性 “Dog.age”
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
应用:
- 系统Storyboard控件拖线
- 访问私有成员变量
- 字典转模型
字典转模型(属性名相同,不能是关键字,嵌套对象模型时还需另做处理,一般使用第三方)
- (void)setValuesForKeysWithDictionary:(NSDictionary
*)keyedValues;
遇到不能识别的key执行的方法 - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
原理:
###KVO(key-value-observing)键值监听
利用一个key来找到某个属性并监听其值的改变。典型的观察者模式
类别:@interface NSObject(NSKeyValueObserving)
使用:
- 添加监听
- 实现监听方法,observeValueForKeyPath: ofObject: change: context:
- 移除监听
相关API
- (void)addObserver:(NSObject )observer forKeyPath:(NSString )keyPath options:(NSKeyValueObservingOptions)options context:(nullable 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));//删除监听
- (void)removeObserver:(NSObject )observer forKeyPath:(NSString )keyPath;//删除监听
- (void)observeValueForKeyPath:(nullable NSString )keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> )change context:(nullable void *)context;//监听实现方法
####手动实现KVO
重写该方法,对应的key但会NO
- (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;
调用方法完成当值改变时发出通知
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
###KVO的实现原理
当一个类的属性被观察的时候,系统会通过runtime动态的创建一个该类的派生类,并且会在这个类中重写基类被观察的属性的setter方法,而且系统将这个类的isa指针指向了派生类,从而实现了给监听的属性赋值时调用的是派生类的setter方法。重写的setter方法会在调用原setter方法前后,通知观察对象值得改变。