-
1.NSAssert
NSAssert()是一個宏,用于開發階段調試程序中的Bug,通過為NSAssert()傳遞條件表達式來斷定是否屬于Bug,滿足條件返回真值,程序繼續運行,如果返回假值,則拋出異常,并且可以自定義異常描述。
NSAssert()的源碼定義
#define NSAssert(condition, desc, ...) \
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (!(condition)) { \
NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
__assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
[[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
object:self file:__assert_file__ \
lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
} \
__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
} while(0)
#endif
第一個參數condition是條件表達式,值為YES或NO;第二個參數desc為異常描述,通常為NSString。當conditon為YES時程序繼續運行,為NO時,則拋出帶有desc描述的異常信息。NSAssert()可以出現在程序的任何一個位置。
NSAssert和assert 區別
NSAssert和assert都是斷言,主要的差別是assert在斷言失敗的時候只是簡單的終止程序,而NSAssert會報告出錯誤信息并且打印出來.所以只使用NSAssert就好,可以不去使用assert。-
2.NS_ASSUME_NONNULL_BEGIN & NS_ASSUME_NONNULL_END宏
在這兩個宏之間的代碼,所有簡單指針對象都被假定為nonnull,因此我們只需要去指定那些nullable的指針。
__nullable 和 __nonnull 。從字面上我們可以猜到,__nullable表示對象可以是NULL或nil,而__nonnull表示對象不應該為空。當我們不遵循這一規則時,編譯器就會給出警告。
- 注意:
- typedef,定義的類型的nullability,特性通常依賴于上下文,即使是在Audited Regions中,也不能假定它為nonnull。
- 復雜的指針類型(如id*)必須顯示去指定是non null還是nullable。例如,指定一個指向nullable對象的nonnulla指針,可以使用”__nullable id * __nonnull”。
我們經常使用的NSError **通常是被假定為一個指向nullable NSError對象的nullable指針。
-
3.合理使用NS_DESIGNATED_INITIALIZER和NS_UNAVAILABLE這兩個宏
Objective-C 中主要通過NS_DESIGNATED_INITIALIZER宏來實現指定構造器的。這里之所以要用這個宏,往往是想告訴調用者要用這個方法去初始化(構造)類對象。
通過NS_UNAVAILABLE,可以在編譯期禁用父類的方法,算是不完美中的完全吧,我們可以禁用掉一些不合理的類成員,來達到一個比較好的封裝效果。
怎樣避免使用NS_DESIGNATED_INITIALIZER產生的警告
如果子類指定了新的初始化器,那么在這個初始化器內部必須調用父類的Designated Initializer。并且需要重寫父類的Designated Initializer,將其指向子類新的初始化器。
更好的做法
如果指定一個新的方法為初始化器NS_DESIGNATED_INITIALIZER,大多是不想讓調用者調用父類的初始化函數,只希望通過該類指定的初始化進行初始化,這時候就可以用NS_UNAVAILABLE宏指定系統的初始化器為不可用即可,那么外界在要給我這個類對象進行初始化時使用指定的方法即可。
比如下面代碼:
#import <UIKit/UIKit.h>
@interface XYView : UIView
NS_ASSUME_NONNULL_BEGIN
// .h中讓系統默認的初始化器不可用(NS_UNAVAILABLE)
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
// 指定這個方法為初始化構造器,外界調用這個方法初始化對象即可
- (instancetype)initWithFrame:(CGRect)frame color:(UIColor *)color right:(BOOL)flag NS_DESIGNATED_INITIALIZER;
NS_ASSUME_NONNULL_END
@end
// .m實現
- (instancetype)initWithFrame:(CGRect)frame {
NSAssert(NO, nil);
@throw nil;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
NSAssert(NO, nil);
@throw nil;
}
// .m實現指定的初始化器方法中別忘記讓當前類調用父類的初始化器方法
- (instancetype)initWithFrame:(CGRect)frame color:(UIColor *)color right:(BOOL)flag {
if (self = [super initWithFrame:frame]) {
}
return self;
}
此時外界給我這個對象進行初始化時,系統就不會提示那兩個聲明NS_UNAVAILABLE這個宏的初始化方法了,可以使用我指定的方法進行初始化了,當然你也可以把init方法給設為不可用,如下圖
4.iOS 攝像頭使用-UIImagePickerController
1.Source type: 這個參數是用來確定是調用攝像頭還是調用圖片庫.如果是 UIImagePickerControllerSourceTypeCamera 就是調用攝像頭,如果是UIImagePickerControllerSourceTypePhotoLibrary 就是調用圖片庫,如果是UIImagePickerControllerSourceTypeSavedPhotosAlbum 則調用iOS設備中的膠卷相機的圖片.
2.Media types:在拍照時,用來指定是拍靜態的圖片還是錄像.kUTTypeImage 表示靜態圖片, kUTTypeMovie表示錄像.
注意:KUTTYpeImage和kUTTypeMovie常量是MobileCoreServices框架中的CFStringRef類型常量,需要導入#import <MobileCoreServices/MobileCoreServices.h>頭文件,要轉換下格式為NSString
3.Editing controls :用來指定是否可編輯.將allowsEditing 屬性設置為YES表示可編輯,NO表示不可編輯,設置當拍照完或在相冊選完照片后,是否跳到編輯模式進行圖片剪裁。只有當showsCameraControls屬性為YES時才有效果 imagepicker.allowsEditing = YES;
4.代理方法:- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info;成功獲取視頻或相冊后的回調
如以下代碼
UIImagePickerController *myImagePickerController = [[UIImagePickerController alloc] init];
myImagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
// 讓攝像頭只能攝影而不能拍照,設置mediaTypes為kUTTypeMovie
myImagePickerController.mediaTypes =
[[NSArray alloc] initWithObjects: (NSString *) kUTTypeMovie, nil];
myImagePickerController.delegate = self;
myImagePickerController.editing = NO;
[self presentViewController:myImagePickerController animated:YES completion:nil];
-
4. AVAsset
AVFoundation給我們提供了一個非常大的可擴展的框架,我們可以通過這個框架,對媒體資源進行捕捉,組合,播放,處理等功能, AVAsset則是AVFoundation框架中,一個非常重要的類
- 1.何為AVAsset
AVAsset是一個抽象類和不可變類,定義了媒體資源混合呈現的方式.可以讓我們開發者在處理時基媒體提供了一種簡單統一的方式,它并不是媒體資源,但是它可以作為時基媒體的容器.
- 2.創建方法
當我們想為一個媒體資源創建AVAsset對象時,可以通過URL對它進行初始化,URL可以是本地資源也可以是一個網絡資源
AVAsset *asset = [AVAsset asetWithURL:assetUrl];```
當通過asetWithURL方法進行創建時,實際上是創建了AVAsset子類AVUrlAsset的一個實例,而AVAsset是一個抽象類,不能直接被實例化.
通過AVUrlAsset我們可以創建一個帶選項(optional)的asset,以提供更精確的時長和計時信息
- ###5.AVPlayer播放視頻
>在iOS開發中,播放視頻通常有兩種方式:一種是使用MPMoviePlayerController(需要導入MediaPlayer.Framework),還有一種是使用AVPlayer。
這兩個類的區別簡而言之就是MPMoviePlayerController使用更簡單,功能不如AVPlayer強大,而AVPlayer使用稍微麻煩點,不過功能更加強大。
單純使用AVPlayer類是無法顯示視頻的,要將視頻層添加至AVPlayerLayer中,這樣才能將視頻顯示出來。
- 播放Assets
1.一個播放器就是控制asset播放的對象,比如開始和結束,seek到指定的時間。可以使用AVPlayer來播放單個asset,用AVQueuePlayer來播放多個連續的asset。
2.一個player向你提供播放的信息,如果需要,你通過player的狀態同步顯示到界面上。你也可以直接把player的輸出顯示到指定的動畫層(AVPlayerLayer或者AVSynchronizedLayer)```多個layer的情況:你可以創建多個AVPlayerLayer對象,但是只有最近創建的layer才會顯示視頻畫面。```
3.雖然是播放asset,但是不能直接把asset傳給AVPlayer對象,你應該提供AVPlayerItem對象給AVPlayer。一個player item管理著和它相關的asset。一個player item包括player item tracks-(AVPlayerItemTrack對象,表示asset中的tracks)
- 用AVPlayerLayer播放一個video文件,步驟如下:
1、用AVPlayerLayer配置一個view
2、創建一個AVPlayer
3、用video文件創建一個AVPlayerItem對象,并且用kvo觀察他的status
4、當收到item的狀態變成可播放的時候,播放按鈕啟用
5、播放,結束之后把播放頭設置到起始位置
- ###6. CALayer - contentsGravity屬性
>當我們使用Cocoa的視圖的時候,我們必須繼承NSView或者UIView并且重載函數drawRect:來顯示任何內容。但是CALayer實例可以直接使用,而無需繼承子類。因為CALayer是一個鍵-值編碼兼容的容器類,你可以在實例里面存儲任意值,所以子類實例化完全可以避免
- 給CALayer提供內容
>你可以通過以下任何一種方法指定CALayer實例的內容:
1.使用包含圖片內容的CGImageRef來顯式的設置圖層的contents的屬性。
2.指定一個委托,它提供或者重繪內容。
3.繼承CALayer類重載顯示的函數。
- 1設置contents屬性
圖層的圖片內容可以通過指定contents屬性的值為CGImageRef。當圖層被創建的時候或者在任何其他時候,這個操作可以在其他實體上面完成,比如
CALayer *layer = [CALayer layer];
layer.position = CGPointMake(50.0, 50.0);
layer.bounds = CGRectMake(0, 0, 200, 200);
layer.contents = (__bridge id _Nullable)([[UIImage imageNamed:@"image"] CGImage]);```
-
修改圖層內容的位置
CALayer的屬性contentsGravity允許你在圖層的邊界內容修改圖層的contents圖片的位置或者伸縮值。默認情況下,內容的圖像完全填充層的邊界,忽視自然的圖像寬高比。
使用contentsGravity位置常量,你可以指定圖片位于圖層任何一個邊界,比如位于圖層的角落,或者圖層邊界的中心。然而當你使用位置常量的時候,contentsCenter屬性會被忽略。
“圖層的坐標系”標識了所支持的內容位置和他們相應的常量。
layer_contentsgravity1.jpg
通過設置contentsGravity屬性為其他一個常量。圖層的內容圖片可以被向上或者向下拉伸, 僅當使用其他任何一個調整大小的常量的時候,contentsCenter屬性才會對內容圖片起作用。
-
-
7.AVPlayer視頻播放相關
1.Video Gravity 視頻播放時的拉伸方式
Apple定義的視頻播放時拉伸方式的三種常量
AVF_EXPORT NSString *const AVLayerVideoGravityResizeAspect NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVLayerVideoGravityResizeAspectFill NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVLayerVideoGravityResize NS_AVAILABLE(10_7, 4_0);
AVLayerVideoGravityResize, // 非均勻模式。兩個維度完全填充至整個視圖區域
AVLayerVideoGravityResizeAspect, // 等比例填充,直到一個維度到達區域邊界
AVLayerVideoGravityResizeAspectFill, // 等比例填充,直到填充滿整個視圖區域,其中一個維度的部分區域會被裁剪
比如以下設置playerLayer
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.contentsGravity = AVLayerVideoGravityResizeAspect;
2.actionAtItemEnd
@property (nonatomic) AVPlayerActionAtItemEnd actionAtItemEnd;
這是一個AVPlayerActionAtItemEnd的枚舉類型,表示當項目的播放到達其結束時間時播放器應當執行的動作
定義如下
typedef NS_ENUM(NSInteger, AVPlayerActionAtItemEnd)
{
AVPlayerActionAtItemEndAdvance = 0, // 結束前進
AVPlayerActionAtItemEndPause = 1, //結束暫停
AVPlayerActionAtItemEndNone = 2,// 不結束
};
3.控制快進,后退的方法
- (void)seekToTime:(CMTime)time
toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter;
使用此方法可以尋找當前播放器項目的指定時間。求得的時間將在[時間容差前,時間+容差后]范圍內,并且可能與指定的時間有所不同,以提高效率。對于toleranceBefore和toleranceAfter,請求kCMTimeZero以請求樣本精確尋找,這可能招致額外的解碼延遲。使用beforeTolerance消息傳遞此方法:kCMTimePositiveInfinity和afterTolerance:kCMTimePositiveInfinity與消息seekToTime:直接相同
比如:[self.player seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
-
8.CMTimeMake 和 CMTimeMakeWithSeconds
CMTime是專門用于標識電影時間的結構體,通常用如下兩個函數來創建CMTime
Apple定義CMTime的源碼:
typedef struct
{
CMTimeValue value; // CMTime的值: value / timescale = seconds
CMTimeScale timescale; // 每秒的幀數
CMTimeFlags flags; /*! @field flags The flags, eg. kCMTimeFlags_Valid, kCMTimeFlags_PositiveInfinity, etc. */
CMTimeEpoch epoch; /*時間區分實際上不同的相等時間戳,因為循環,多項目排序等。將在比較期間使用:更大的時期發生在較小的時期之后。加法/減法只能在單個時期內進行,然而,因為時代長度可能是未知/可變的*/
} CMTime;
第一方式創建CTTime
CMTime CMTimeMake (
int64_t value, //表示 當前視頻播放到的第幾楨數
int32_t timescale //每秒的幀數
);```
注意:```CMTimeMake(time, timeScale)```,第一個參數time指的是時間(不是秒),而時間time要轉換為秒就要看第二個參數timeScale了,timeScale指的是1秒需要由幾個frame構造(可以視為fps每秒的幀數),因為真正要表達的時間會是time/timeScale才是秒
>經常看到別的開源項目中這樣定義CMTime
```CMTime firstframe = CMTimeMake(1,10);
CMTime lastframe = CMTimeMake(10, 10);```
上面的代碼可以這么理解,視頻的fps(幀率)是10,firstframe是第一幀的視頻時間為0.1秒,lastframe是第10幀視頻時間為1秒。
或者換種寫法 CMTime curFrame = CMTimeMake(第幾幀, 幀率)
>第二種方式創建CMTime
CMTime CMTimeMakeWithSeconds(
Float64 seconds, //第幾秒的截圖,是當前視頻播放到的幀數的具體時間
int32_t preferredTimeScale //首選的時間尺度 "每秒的幀數"
);
- ###9.AVAsset、AVMutableComposition視頻裁剪示例
>與之相關的這些類有些抽象,用代碼將視頻合成其實與繪聲繪影/vegas/Final Cut Pro等軟件將視頻合成的過程類似,首先了解下這類軟件一些相關知識:一個工程文件中有很多軌道,如音頻軌道1,音頻軌道2,音頻軌道3,視頻軌道1,視頻軌道2等等,每個軌道里有許多素材,對于每個視頻素材,它可以進行縮放、旋轉等操作,素材庫中的視頻拖到軌道中會分為視頻軌和音頻軌兩個軌道。
- 這里用這些軟件里的一些術語類來比這些類:
AVAsset:素材庫里的素材;
AVAssetTrack:素材的軌道;
AVMutableComposition :一個用來合成視頻的工程文件;
AVMutableCompositionTrack :工程文件中的軌道,有音頻軌、視頻軌等,里面可以插入各種對應的素材;
AVMutableVideoCompositionLayerInstruction:視頻軌道中的一個視頻,可以縮放、旋轉等;
AVMutableVideoCompositionInstruction:一個視頻軌道,包含了這個軌道上的所有視頻素材;
AVMutableVideoComposition:管理所有視頻軌道,可以決定最終視頻的尺寸,裁剪需要在這里進行;
AVAssetExportSession:配置渲染參數并渲染。```
接下來就用這種類比的方式裁剪一個視頻:
1.將素材拖入到素材庫中
AVAsset *asset = [AVAsset assetWithURL:outputFileURL];
AVAssetTrack *videoAssetTrack = [[asset tracksWithMediaType:AVMediaTypeVideo]objectAtIndex:0];//素材的視頻軌
AVAssetTrack *audioAssertTrack = [[asset tracksWithMediaType:AVMediaTypeAudio]objectAtIndex:0];//素材的音頻軌
2.將素材的視頻插入視頻軌,音頻插入音頻軌
AVMutableComposition *composition = [AVMutableComposition composition];//這是工程文件
AVMutableCompositionTrack *videoCompositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];//視頻軌道
[videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAssetTrack.timeRange.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero error:nil];//在視頻軌道插入一個時間段的視頻
AVMutableCompositionTrack *audioCompositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];//音頻軌道
[audioCompositionTrack insertTimeRange: CMTimeRangeMake(kCMTimeZero, videoAssetTrack.timeRange.duration) ofTrack:audioAssertTrack atTime:kCMTimeZero error:nil];//插入音頻數據,否則沒有聲音
3.裁剪視頻,就是要將所有視頻軌進行裁剪,就需要得到所有的視頻軌,而得到一個視頻軌就需要得到它上面所有的視頻素材
AVMutableVideoCompositionLayerInstruction *videoCompositionLayerIns = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoAssetTrack];
[videoCompositionLayerIns setTransform:videoAssetTrack.preferredTransform atTime:kCMTimeZero];//得到視頻素材(這個例子中只有一個視頻)
AVMutableVideoCompositionInstruction *videoCompositionIns = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
[videoCompositionIns setTimeRange:CMTimeRangeMake(kCMTimeZero, videoAssetTrack.timeRange.duration)];//得到視頻軌道(這個例子中只有一個軌道)
AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
videoComposition.instructions = @[videoCompositionIns];
videoComposition.renderSize = CGSizeMake(...);//裁剪出對應的大小
videoComposition.frameDuration = CMTimeMake(1, 30);
4.導出
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetMediumQuality];
exporter.videoComposition = videoComposition;
// 視頻輸出的地址
exporter.outputURL = [NSURL fileURLWithPath:_outputFilePath isDirectory:YES];
// 輸出文件的類型
exporter.outputFileType = AVFileTypeMPEG4;
exporter.shouldOptimizeForNetworkUse = YES;
// 剪輯視頻:注意它是一個異步操作,outputFileType屬性的設置
[exporter exportAsynchronouslyWithCompletionHandler:^{
if (exporter.error) {
//...
}else{
//...
}
}];```
- ###10.視頻導出選項
AVF_EXPORT NSString *const AVAssetExportPresetLowQuality NS_AVAILABLE(10_11, 4_0);
AVF_EXPORT NSString *const AVAssetExportPresetMediumQuality NS_AVAILABLE(10_11, 4_0);
AVF_EXPORT NSString *const AVAssetExportPresetHighestQuality NS_AVAILABLE(10_11, 4_0);```
這些導出選項可用于生成具有適合于設備的視頻大小的電影文件。導出不會將視頻從較小的大小縮放。視頻將使用壓縮H.264和音頻將使用AAC進行壓縮。
+ (NSArray<NSString *> *)exportPresetsCompatibleWithAsset:(AVAsset *)asset;
AVAssetExportSession導出視頻的狀態枚舉類型AVAssetExportSessionStatus
typedef NS_ENUM(NSInteger, AVAssetExportSessionStatus) {
AVAssetExportSessionStatusUnknown, // 導出狀態未知
AVAssetExportSessionStatusWaiting, // 導出等待狀態
AVAssetExportSessionStatusExporting, // 正在導出狀態
AVAssetExportSessionStatusCompleted, // 已導出完成狀態
AVAssetExportSessionStatusFailed, // 導出失敗狀態
AVAssetExportSessionStatusCancelled // 已取消導出狀態
};
注意: 導出視頻的方法是異步執行的,在導出完成后回到主線程保存視頻到相冊中,如下
// 導出視頻
[self.exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([self.exportSession status]) {
case AVAssetExportSessionStatusFailed
NSLog(@"Export failed: %@", [[self.exportSession error] localizedDescription]);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"Export canceled");
break;
default:
NSLog(@"NONE");
dispatch_async(dispatch_get_main_queue(), ^{
NSURL *movieUrl = [NSURL fileURLWithPath:self.tempVideoPath];
UISaveVideoAtPathToSavedPhotosAlbum([movieUrl relativePath], self,@selector(video:didFinishSavingWithError:contextInfo:), nil);
});
break;
}
}];
-
11. AVAssetImageGenerator
AVFoundation生成視頻縮略圖主要靠如下兩個類
- 1.AVURLAsset
該類是AVAsset的子類,AVAsset類專門用于獲取多媒體的相關信息,包括獲取多媒體的畫面、聲音等信息。而AVURLAsset子類的作用則是根據NSURL來初始化AVAsset對象。
- 2.AVAssetImageGenerator
該類專門用于截取視頻指定幀的畫面。
使用AVFoundation生成視頻縮略圖的步驟如下:
- 1.根據視頻的NSURL創建AVURLAsset對象
- 2.根據AVURLAsset對象創建AVAssetImageGenerator對象
- 3.調用AVAssetImageGenerator對象的
copyCGImageAtTime:actualTime:error:
方法來獲取該視頻指定時間點的視頻截圖。
該方法的第一個CMTime參數用于指定獲取哪個時間點的視頻截圖,第2個CMTime參數用于獲取實際截圖 位于哪個時間點.
其中CMTime是專門用于標識電影時間的結構體,通常用如下兩個函數來創建CMTime.
CMTimeMake(int64_t value, int_32 timescale):
第1個參數代表獲取第幾幀的截圖,第2個參數代表每秒的幀數.因此實際截取的時間點是value/timescale.
CMTimeMakeWithSeconds(Float64 seconds, int32_t preferredTimeScale):
第1個參數代表獲取第幾秒的截圖,第2個參數則代表每秒的幀數.