#include <stdio.h>
#include <string.h> //문자열 길이를 구하는 strlen 함수를 사용하기 위해서
#include <stdlib.h> //  atoi 함수를 사용하기 위해



int main(int argc, const char * argv[]) {
    //배열포인터
    
    //data 배열은 1 - 5 까지의 값을 저장하고 있다.
    char data[5] = {1,2,3,4,5};
    
    //합산에 사용할 result 변수는 0으로 초기화 한다.
    int result = 0, i;
    
    char *p = data; // data배열의 시작위치를 포인터 변수 p에 저장한다.
    
    //5번 반복하면서 포인터 p를 사용하여 배열의 각 항목을 result 변수에 합산
    
    for (i = 0; i < 5; i++) {
        result = result + *p;
        p++;   //data 배열의 다음 항목으로 주소를 이동 data[0] -> data[1] -> ...
    }
    
    //합산한 결과를 출력한다.
    printf("data 배열의 각 요소의 합은 %d 입니다. \n" , result);
    return 0;
}

do it c 언어 입문 참고

'ios 뽀개기 > C언어' 카테고리의 다른 글

c언어 기초4  (1) 2019.05.22
c언어 기초 3  (0) 2019.05.21
c언어 기초2  (0) 2019.05.17
c언어 기초1  (0) 2019.05.16
  1. 2019.07.26 12:27

    비밀댓글입니다

#include <stdio.h>
#include <string.h> //문자열 길이를 구하는 strlen 함수를 사용하기 위해서
#include <stdlib.h>   //  atoi 함수를 사용하기 위해

 //14 - 10 문자를 정수로 변환
int ArrayToInteger(char string[]){
    
    int count = 0, num = 0;
    //문자열이 끝날때 까지 반복
    while (string[count] != 0) {
        //반복할 때마다 이전 값에 10을 곱해서 자릿수를 증가시킴
        num = num * 10 + string[count] - '0';
        count++;
        
    }
    return num;
}


/*
 buffer : 사용자가 입력한 문자열을 저장할 배열
 limit : 최대 입력 가능한 문자개수
 1반환 : 정상입력
 0반환 : 제한된 개수만 받았다
 */
int GetMyString(char buffer[] , int limit ){
    
    int i;
    for (i = 0; i <= limit; i++) {   //최대 개수만큼만 반복
        buffer[i] = getchar();
        if (buffer[i] == '\n') {    //엔터키가 체크되면 사용자의 입력이 완료되었다는 뜻, 문자열 완성, 함수 종료.
        
            buffer[i] = 0;            //엔터키 위치에 0을 넣어서 문자열 완성
            return 1;                   //정상적으로 입력이 완료됨
        }
    }
    
    //반복문을 빠져 나왔다는 뜻은 입력 개수 제한을 초과했다는 뜻
    //현재 위치에 0을 넣고 문자열 구성 완료
    buffer[i] = 0;
    rewind(stdin); //표준 입력 버퍼에 남아있는 문자들을 제거함.
    return 0;          //입력 초과 현상이 발생했음을 알림.
}


int main(int argc, const char * argv[]) {

    /*
    //14 - 1 표준 입력 함수를 사용하여 문자를 한개 입력 받음
    int input_data;
    input_data = getchar();
    // 입력받은 문자를 출력
    printf("input : %c \n" , input_data);
    //input : d
 
    
     //14 - 2 표준 입력 함수를 사용하여 문자를 한개 입력 받음
    int input_data2;
    input_data2 = getchar();
    printf("첫문자 : %c \n" , input_data2);
    input_data2 = getchar();
    printf("둘째문자 : %c \n" , input_data2);
  
    
    //14 - 3
    int input_data3 = getchar();
    rewind(stdin);      //표준 입력 버퍼에 있는 모든 입력값을 제거함.
    printf("first input : %c\n", input_data3);
    input_data3 = getchar();
    rewind(stdin);
    printf("second input : %c\n", input_data3);
    
    
    
    //14 - 4 마지막에 0을 포함해야 하므로 최대 9개의 문자까지 저장 가능
    char input_string[10];
    gets(input_string);
    printf("input string : %s \n" , input_string);
    
    
    
    //14 - 5 반환값이 NULL이 아니라 문자열을 입력 받아서 input_string에 저장함
    char input_string2[10];
    if (NULL != gets(input_string2)) {
        printf("input_string2 : %s \n" , input_string2);
    }else{
        printf("input => canceled \n");
    }
    
    
    
    //14 - 6 입력할 문자열 개수 지정
    char temp[10];
    int state;
    //최대 9개까지만 입력받겠다고 제안함.
    state = GetMyString(temp, 9);
    if (state == 1) {
        printf("input : %s \n", temp);                          //정상입력
    }else{
        printf("input : %s -> out of range\n", temp); // 범위를 초과
    }
    
    
    //14 - 7 문자를 정수로 변환
    
     //pos_num은 각 자릿수에 곱할 숫자, num은 정수로 변환될 숫자를 저장할 변수
     
    int pos_num = 100, num = 0 , i, temp_num;
    //정수로 변환할 문자열
    char num_string[4] = "423";
    
    for (i = 0; i < 3; i++) {
        
        //배열의 각 항목을 문자에서 정수로 변경
        temp_num = num_string[i] - '0';
        printf(" temp_num -> %d \n"  , temp_num);
        //정수화된 숫자에 자릿수에 해당하는 숫자를 곱해서 합산
        num = num + temp_num * pos_num;
        
        //다음 자릿수를 구성하기 위해서 10을 나눔. 100 -> 10 -> 1
        pos_num = pos_num / 10;
    }
    printf(" %s -> %d \n" , num_string , num);
     
    
    
    
    //14 - 8 문자를 정수로 변환
    int pos_number = 1, number = 0, i , countNum;
    char number_string[4] = "123";
    //문자열의 길이를 구해서 countNum 변수에 3이저장
    countNum = strlen(number_string);
    
    //문자열의 길이보다 1만큼 작게 반복하기 위해
    //10 * 10 * 10 = 1000  이 아니라 10 * 10 해서 100이 되어야 한다.
    printf("countNum -  : %d \n" , countNum);       //3
    //반복 1회마다 10씩 증가 1-> 10 -> 100
    for (i = 0; i < countNum - 1; i ++) {
        pos_number = pos_number * 10;
        printf("pos_num -  : %d \n" , pos_number);    // 10, 100
    }
    
    //문자열의 길이 만큼 반복
        for (i = 0; i < countNum; i++) {
            number = number + (number_string[i] - '0' ) * pos_number;
            pos_number = pos_number / 10;
            printf("number - v  : %d \n" , number);                 //100    , 120
            printf("pos_num - v  : %d \n" , pos_number);        //10,    1
        }
        printf(" %s -> %d \n", number_string , number);     // 123 -> 123
    
    
     //14 - 9 문자를 정수로 변환
    int num = 0, count = 0;
    char num_string[4] = "123";
    //문자열이 끝날 때까지 반복
    while (num_string[count] != 0) {
        //반복할 때마다 이전 값에 10을 곱해서 자릿수를 증가시킴
        //num의 변화 : 1 * 10 + 1 -> 12 * 10 + 2 -> 123
        num = num * 10 + (num_string[count] - '0');
        count++;    //다음문자로 이동
        printf(" num : %d \n" , num);
        // num : 1
        //num : 12
        //num : 123
    }
    printf(" %s -> %d \n" , num_string , num);
    
    
      //14 - 10 문자를 정수로 변환 - gets
    int first_num, second_num;
    char first_string[16] , second_string[16];
    
    printf("input first number : ");
    gets(first_string);     //첫번째 숫자를 입력받음
    printf("input second number : ");
    gets(second_string);  //두번째 숫자를 입력받음
    
    first_num = ArrayToInteger(first_string);               // 문자열 -> 정수
    second_num = ArrayToInteger(second_string);    //문자열 -> 정수
    
    printf("%d + %d = %d \n" , first_num, second_num , first_num+second_num);
    
    
    
    //14 - 11 문자를 정수로 변환 - atoi
    int first_num, second_num;
    char first_string[16] , second_string[16];
    printf("input first number : ");
    gets(first_string); // 첫 번째 숫자를 입력받음
    printf("input second number : ");
    gets(second_string);   //두 번째 숫자를 입력받음
    
    first_num = atoi(first_string);
    second_num = atoi(second_string);
    // 정수로 변환된 두 수를 합산한 결과 출력
    printf("%d + %d = %d\n", first_num, second_num, first_num + second_num);
    
     
    
    //14 - 12 입력    -  scanf
    int int_data;
    float float_data;
    
    scanf("%d" , &int_data);        //정수 값을 입력 받는다.
    scanf("%f" , &float_data);      //실수 값을 입력받는다.
    printf("input : %d, %f\n", int_data, float_data);       // 5 , 55.5
    
   
    
    
     //14 - 13 입력
    char temp[32];
    scanf("%s" , temp); //문자열을 입력받음. 배열 변수 이름을 사용하면 해당 배열의 시작 주소를 의미하기 때문에 &연산자를 쓰면안된다.
    printf("input string : %s \n", temp);
    
    
     //14 - 14 진수 변환
    int num1, num2, num3;
    scanf("%o %d %x", &num1, &num2 , &num3); // 8, 10, 16진수 입력받는다.
    printf("input : %d, %d, %d \n" , num1, num2, num3);
    //10
    //10
    //10
    //input : 8, 10, 16
    
     
    
    
    //14- 15 응용
    
    int num = 0;
    //정상적인 나이를 입력할 때까지 무한 반복
    while (1) {
        printf("input age : ");
        scanf("%d" , &num);     //한개의 정수 값을 입력 받음
        
        if (num > 0 && num <= 130) {
            break;  //정상입력 -> 반복문 빠져나감
        }else{
            //나이 범위 잘못되었다고 알림
            printf("잘못된 나이! \n");
        }
    }
    
    printf("당신의 나이는 : %d  입니다.\n" , num);   //문자열 입력하면 무한반복
    
     */
    
     //14- 16 응용
    
    int num = 0;
    while (1) {
        printf("input age : ");
        
        // scanf 함수는 실패하면 0을 반환한다.
        if (scanf("%d" , &num) ==0) {
            rewind(stdin);      //입력 버퍼를 모두 비운다.
            printf("enter digit number ! \n");
        }else{
            
            
            if (num > 0 && num <= 130) {
                break;
            }else{
                printf("잘못된 나이! \n");
            }
        }
    }
    printf("당신의 나이는 %d  입니다. \n" , num);
    
    
    
    
    
    return 0;
}

'ios 뽀개기 > C언어' 카테고리의 다른 글

c언어 기초4  (1) 2019.05.22
c언어 기초 3  (0) 2019.05.21
c언어 기초2  (0) 2019.05.17
c언어 기초1  (0) 2019.05.16
#include <stdio.h>
#include <string.h>
int result; //전역변수, 특별한 초기화 값이 없으면 0으로 초기화됨


 //배열 길이 구하기- 5
//매개변수 data의 [ ] 의 숫자는 적지 않아도 된다. 전달되는 배열의 크기에 영향을 받기 때문에 어떤 숫자를 적어도 무방하다.
int GetStringLength(char datas[]){
    int count = 0;
     /* 0이 나올 때까지 문자의 개수를 더한다 */
    while (datas[count]) {
        printf("배열안의 글자는 : %c \n" , datas[count]);
        /*
         배열안의 글자는 : h
         배열안의 글자는 : a
         배열안의 글자는 : p
         배열안의 글자는 : p
         배열안의 글자는 : y
         */
        count++;
    }
    return count;
}


//step3
void Test(){
    int data = 0;
    printf("%d  \n" , data++);
}

//*step2 전역변수
void Sum2(int data1, int data2){
    result = data1 + data2;
}


//*step1 지역변수 함수
int Sum(int data1, int data2){  // data1, data2는 지역변수 이다. 5와 3으로 초기화 된다.
    int result = data1 + data2; // 지역 변수, data + data2 값으로 초기화 됨.
    return result;
}






int main(int argc, const char * argv[]) {

    //지역변수 함수 *step 1
    int result;     //지역변수, 초기화 되지 않아서 어떤 값이 들어있을지 모름
    result = Sum(5, 3);
    printf("5 + 3 = %d \n" , result);
    //5 + 3 = 8
    
    
    //전역 변수 함수;
    Sum2(6, 4);
    printf("6 + 4 = %d \n", result); // ! 주의! 전역변수 result 를 참조하지 않고 지역변수 result 를 참조한다.
    //6 + 4 = 8
    
    
    //step3
    int i ;
    for (i=0; i<5; i++) {
        Test();
    }
    /*
     0
     0
     0
     0
     0  
     */
    
    
    //step 12
    
    //배열 - 1
    short student[20];
    student[1] = 10;
   printf("%d %d\n", student[1], student[2]);
    //10 0
    
    
    //배열 - 2
    short mystudent[20], ii;
    for (ii = 0; ii < 20; ii++) mystudent[ii] = 0;
    mystudent[1] = 10;
     printf("%d %d\n", mystudent[1], mystudent[2]);
    //10 0
    

    //배열 - 3
    short yourstudent[20] = {0,};
    yourstudent[1] = 10;
     printf("%d %d\n", yourstudent[1], yourstudent[2]);
    //10 0
    
    
    char data[5] = {1,2,3,4,5};
    int resultdata = 0, iii;
    for (iii = 0; iii<5; iii++) {
        resultdata = resultdata + data[iii];
    }
    printf("data 배열의 각 요소의 합은 %d 입니다. \n" , resultdata);
    //data 배열의 각 요소의 합은 15 입니다.
    
    
    
    //배열 - 4
    char dataArray[6] = {'h' , 'a' ,'p' ,'p' , 'y', 0};
    char ment[] = "C programming~";
    printf("%s \n" , dataArray);
    //happy
    printf("%s \n" , ment);
    //C programming~
    
    
    //배열 길이 구하기- 5
    int data_length; //문자열 길이를 저장할 변수
    char dataChars[10] = {'h' , 'a' , 'p'  , 'p'  , 'y' , 0 , };
    data_length = GetStringLength(dataChars);
    printf("data length = %d \n" , data_length);
    //data length = 5
    
    
    //배열 길이 구하기- 6
    int my_data_length;
    char mydatas[10] = {'h' , 'a', 'p', 'p' , 'y' , 0, };
    my_data_length = strlen(mydatas);
    printf("data length = %d \n" , my_data_length);
    //data length = 5
    
    
    
    //배열 더하기 , 복사 - 7
    char sdata[10] = {'a' , 'b' ,'c' , 0, };
    char sresult[16] ;  //새로운 문자열을 저장할 변수
    strcpy(sresult, sdata); //sdata에 저장된 문자열을 sresult로 복사
    strcat(sresult, "def");  /* result에 “def”를 덧붙임 */
    printf("%s + \"def\" = %s\n" ,sdata , sresult);
    //abc + "def" = abcdef
    
    
    //2차원 배열 - 8
    char doublearray[3][4] = {  {0,0,2,0} ,  {1,1,0,0} , {2,1,0,2} };
    int x, y;
    
    for (y = 0;  y < 3;  y++) {
        for (x = 0; x < 4; x++) {
            printf("%d행에 %d열에" , y + 1 , x + 1);
            if (doublearray[y][x] == 1) {
                printf("검은돌이 놓여있습니다. \n");
            } else if(doublearray[y][x] == 2){
                printf("흰돌이 놓여 있습니다. \n");
            }else{
                printf("는 돌이 놓여 있지 않습니다. \n");
            }
        
        }
    }
    /*
     1행에 1열에는 돌이 놓여 있지 않습니다.
     1행에 2열에는 돌이 놓여 있지 않습니다.
     1행에 3열에흰돌이 놓여 있습니다.
     1행에 4열에는 돌이 놓여 있지 않습니다.
     2행에 1열에검은돌이 놓여있습니다.
     2행에 2열에검은돌이 놓여있습니다.
     2행에 3열에는 돌이 놓여 있지 않습니다.
     2행에 4열에는 돌이 놓여 있지 않습니다.
     3행에 1열에흰돌이 놓여 있습니다.
     3행에 2열에검은돌이 놓여있습니다.
     3행에 3열에는 돌이 놓여 있지 않습니다.
     3행에 4열에흰돌이 놓여 있습니다.
     */
    
    
    
    
    
    return 0;
}

do it c언어 도서 참고

'ios 뽀개기 > C언어' 카테고리의 다른 글

c언어 기초4  (1) 2019.05.22
c언어 기초 3  (0) 2019.05.21
c언어 기초2  (0) 2019.05.17
c언어 기초1  (0) 2019.05.16
#include <stdio.h>


//더하기 함수
int Sum(int value1, int value2){
    
    int result = value1 + value2;
    return result;
    
}


int main(int argc, const char * argv[]) {

    //step 6 자료형 -
    
    int day;        //int형 변수 선언
    day = 17;     //int형 변수에 값 대입
    printf("오늘은 몇일 ? : %d 일 \n" , day );
    //출력 : 오늘은 몇일 ? : 17 일
    
    
    int sumResult;
    sumResult = Sum(5, 6);
    printf("두 수를 더한 값 : %d \n" , sumResult);
    //출력 : 두 수를 더한 값 : 11
    
    //문자 더하기
     putchar('m');
     putchar('y');
     putchar('~');
     putchar('\n');
    //출력: my~
    
    
    //출력 후 줄바꿈
    puts("hello~!");
    //hello~!
    
    //%c와 65가 짝을 이루기 때문에 65에 해당하는 ASCII 코드값 A가 출력 된다.
    printf("65의 ASCII 값은 %c 입니다. \n" , 65);
    //출력 : 65의 ASCII 값은 A 입니다.
    
    //첫번째 data는 A와 짝을 이루고, 두번째는 %d와 짝을 이룬다.
    char data = 65;
    printf("%c 의 ASCII 값은 %d 입니다. \n", data , data);
    //출력 : A 의 ASCII 값은 65 입니다.
    
    
    //실수 출력
    float value = 2.1f;
    printf("실수 float : %f \n" , value);
    //출력 : 실수 float : 2.100000
    
    //value2 값을 실수 형식과 정수 형식으로 출력
    float value2 = 5.6f;
    printf("정수 : %d, 실수 : %f  \n" , value2, value2);
    //출력 : 정수 : 0, 실수 : 5.600000
    
    //부호가 있는 int, 부호가 없는 int
    int data1 = -1;
    unsigned int data2 = 4294967295;
    printf("%d, %u, %d, %u \n" , data1, data1, data2 , data2);
    //출력 : -1 , 4294967295, -1, 4294967295

    
    //부호가 있는 char
    char data3 = -1;
    printf("%d , %u \n", data3,data3);
    //-1 , 4294967295
    
    
    //진수 변환
    int data_1 = 10;
    int data_2 = 010;
    int data_3 = 0x10;
    printf("%x, %d, %o \n" , data_1 , data_2 , data_3);     //16진수 , 10진수, 8진수;
    //a, 8, 20
    
    
    float data_4 = 15.54;
    printf("%f, %e, %E  \n" , data_4, data_4, data_4);
    //출력 : 15.540000, 1.554000e+01, 1.554000E+01
    
    
    
    //자릿수 확인 - 자릿수확인을 위해 [ ] 문자를 사용함.
    int data_5 = 7;
    printf("[%d] [%5d] \n" , data_5 ,data_5);
    //[7] [    7]
    
    
    //자릿수 확인2
    int data_6 = 7;
    printf("[%5d] [%05d] [%-5d] \n" , data_6, data_6 , data_6);
    //[    7] [00007] [7    ]
    
    
    //자릿수 확인3
    double data_7 =3.141592;
    printf("[%f] [%.4f] [%8.4f] [%-8.4f] \n" , data_7 , data_7 , data_7 , data_7);
    //[3.141592] [3.1416] [  3.1416] [3.1416  ]
    
    
    //***************  step 7  연산 ***************
    
    int data_8, data_9;
    data_8 = 5;             //변수에 상수 5를 대입
    data_9 = data_8;    //
    printf("data_8 = %d , data_9 = %d \n" , data_8 , data_9);
    //data_8 = 5 , data_9 = 5
    
    
    //연산
    int data_10 = 5, data_11 = 3;   //두 변수에 초기값을 5와 3으로 설정
    int result1 = data_10 + data_11;        // 덧셈
    int result2 = data_10 * data_11;        // 뺄셈
     int result3 = data_10 / data_11;       //나눗셈 (몫)
     int result4 = data_10 % data_11;     // 나눗셈 (나머지)
    printf("result : %d , %d , %d, %d \n" ,result1 ,result2 , result3 , result4);
    //result : 8 , 15 , 1, 2
    
    
    //관계연산
    int data_12 = 5, data_13 = 3;           // 두 변수에 초기값을 5와 3으로 설정
    int result_1 = data_12 > 7;               //거짓 : 0
    int result_2 = data_13 <= data_12; //참 : 1
    int result_3 = data_13 == 7;            //거짓 : 0
    int result_4 = data_13 != data_12;  //참 : 1
    printf("result : %d, %d, %d, %d \n" , result_1 , result_2 , result_3, result_4);
    //result : 0, 1, 0, 1
    
    
    //연산
    int mydata1 = 5, mydata2 = 3;
    int result__1 = 0 || 1;                                                 // 0 또는 1 : 참 (1)
    int result__2 = 3 && -1;                                            // 1 && 1과 같아서 참 (1)
    int result__3 = mydata1 == 3 || mydata2 == 3;
    int result__4 = mydata1 == 3 && mydata2 ==3;    //거짓
    int result__5 = !mydata1;                                         //거짓
    printf("result : %d , %d, %d, %d, %d \n" , result__1, result__2, result__3, result__4, result__5);
    //result : 1 , 1, 1, 0, 0
    
    
        //***************  step 8  if 문 ***************
    
    //if문 - 1
    int ifData = 5;
    if(ifData > 3){     //비교 연산은 참.
        printf("ifData는 3보다 큰 수 입니다. \n");
        printf("작업종료 \n");
    }
    //ifData는 3보다 큰 수 입니다.
    //작업종료
    
    
    
    
    //if문 - 2
    int score = 92;
    char grade;
    if (score >= 90) {
        grade = 'A';
        printf("점수는 %d 이고 등급은 %c 입니다. \n" , score, grade);
    }
    printf("작업 종료 \n");
    //점수는 92 이고 등급은 A 입니다.
    //작업 종료

    //if - else 문 1
    int hisdata = 5;
    if (hisdata > 3) {
        printf("hisdata는 3보다 큰 수 입니다.  \n");
    }else{
        printf("hisdata는 3보다 작거나 같은 수 입니다.  \n");
    }
    printf("작업을 종료 합니다.  \n");
    /*
     hisdata는 3보다 큰 수 입니다.
     작업을 종료 합니다.
     */
    
    
    //if - else 문2
    
    int myscore = 92;
    char mygrade;
    
    if (myscore >= 90) {
        mygrade = 'A';
        printf("점수는 %d이고 등급은 %c 입니다. \n" , myscore, mygrade);
    } else {
        mygrade = 'B';
        printf("점수는 %d이고 등급은 %c 입니다." , myscore, mygrade);
    }
    printf("작업을 종료 합니다.  \n");
    /*
     점수는 92이고 등급은 A 입니다.
     작업을 종료 합니다.
     */
    

    //if 문 - 응용 - 1
    int year = 2015 , month = 12, days = 31;
    days++;             //일을 +1 해주기
    //일이 31일 이상이면 월 +1 해주고 일 값을 1로 초기화
    if (days > 31) {
        month++;
        days = 1;
        //달이 12일 이상이면 년을 +1 해주고 월 값을 1로 초기화
        if (month > 12) {
            year++;
            month = 1;
        }
    }
    printf("date : %d년  %d월  %d일  \n" , year, month, days);
    //date : 2016년  1월  1일
    
    
    //if 문 - 응용 - 2
    int hisscore = 80;
    char hisgrade;
    if (hisscore >= 90) {
        hisgrade = 'A';
    } else {
        if(hisscore >= 80) {
            hisgrade = 'B';
        }else {
            if(hisscore >= 70) {
                hisgrade = 'C';
            }else {
                if(hisscore >= 60) {
                    hisgrade = 'D';
                }else {
                    if(hisscore >= 50) {
                        hisgrade = 'E';
                    }else {

                            hisgrade = 'F';
                        
                    }
                }
            }
        }
    }
    printf("그의 점수는 %d점이고 등급은 %c 입니다. \n" ,hisscore, hisgrade );
    //그의 점수는 80점이고 등급은 B 입니다.
    
    
    //switch 문
    int enscore = 86;
    char engrade;
    
    switch (enscore / 10) {
        case 10:
        case  9:
            engrade = 'A';
            break;
        case  8:
            engrade = 'B';
            break;
        case  7:
            engrade = 'C';
            break;
        case  6:
            engrade = 'D';
            break;
            
        default:
            engrade = 'F';
            break;
    }
    printf("영어 점수는 %d 이고 등급은 %c 입니다 \n" , enscore , engrade );
    //영어 점수는 86 이고 등급은 B 입니다
    
    
    
    //***************  step 9  for 문 ***************
    
    int sum_ = 0, num_;
    for (num_ = 1 ; num_ <= 5 ; num_ ++ ){
        printf("num(%d) + sum(%d) = " , num_ , sum_);
        sum_ = sum_ + num_;
        printf("%d \n" , sum_);
    }
    printf("\n results = : num = %d , sum = %d \n\n" , num_ , sum_);
    
    /*
     num(1) + sum(0) = 1
     num(2) + sum(1) = 3
     num(3) + sum(3) = 6
     num(4) + sum(6) = 10
     num(5) + sum(10) = 15
     results = : num = 6 , sum = 15
     */
    
    
    //for문 2
    int numsum = 0, dnum = 1;
    //무한루프
    for(  ;  ;  ){
        printf("dnum(%d) + numsum(%d) = " , numsum, dnum);
        numsum = numsum  + dnum;
        printf("%d\n" , numsum);
        dnum++;
        if (dnum > 5) {break;}      //빠져나온다.
    }
    printf("\n Result : dnum = %d numsum = %d \n" , dnum , numsum);
    /*
     dnum(0) + numsum(1) = 1
     dnum(1) + numsum(2) = 3
     dnum(3) + numsum(3) = 6
     dnum(6) + numsum(4) = 10
     dnum(10) + numsum(5) = 15
     Result : dnum = 6 numsum = 15
     */
    
    
    //while문
    int sumW = 0 , numW = 1;
    //5보다 같거나 작을때만 반복문 실행
    while (numW <= 5) {
        printf("num(%d) + sum(%d) = " , numW , sumW);
        sumW = sumW + numW;
        printf("%d \n" , sumW);
        numW++;
    }
    printf("\n Result : dnum = %d numsum = %d \n" , numW , sumW);
    /*
     num(1) + sum(0) = 1
     num(2) + sum(1) = 3
     num(3) + sum(3) = 6
     num(4) + sum(6) = 10
     num(5) + sum(10) = 15
     Result : dnum = 6 numsum = 15
     */
    
    
    //do while 문
    int sumdo = 0 , numdo = 1;
    do{
        printf("num(%d) + sum(%d) = " , numdo, sumdo);
        sumdo = sumdo + numdo;
        printf("%d\n" , sumdo);
        numdo++;
    }while(numdo <= 5);
    printf("\n Result : numdo = %d sumdo = %d \n" , numdo , sumdo);
    /*
     num(1) + sum(0) = 1
     num(2) + sum(1) = 3
     num(3) + sum(3) = 6
     num(4) + sum(6) = 10
     num(5) + sum(10) = 15
     Result : numdo = 6 sumdo = 15
     */
    
    // 구구단 반복문
    for(int bignum = 2; bignum < 10 ; bignum ++){
        for (int smallnum = 1; smallnum < 10; smallnum++) {
            printf("%d x %d = %d \n" , bignum , smallnum, (bignum*smallnum));
        }
    }
    /*
     2 x 1 = 2
     2 x 2 = 4
    (생략..........)
     9 x 8 = 72
     9 x 9 = 81
     */
    
    return 0;
}

do it c언어 참고

'ios 뽀개기 > C언어' 카테고리의 다른 글

c언어 기초4  (1) 2019.05.22
c언어 기초 3  (0) 2019.05.21
c언어 기초2  (0) 2019.05.17
c언어 기초1  (0) 2019.05.16

AVCaptureVideoDataOutput을 이용해서 카메라 만들기 3 - 녹화

로직 작성 순서는 아래와 같다

//녹화 step1
//IDCaptureSessionPipelineViewController클래스 -  녹화,정지 토글 버튼 기능

- (IBAction)toggleRecording:(id)sender
{
    NSLog(@"IDCaptureSessionPipelineViewController - toggleRecording 버튼");
    
    if (_recording) {
        [_captureSessionCoordinator stopRecording];
        
    } else {
        
        //잠자기 모드 비활성화
        [UIApplication sharedApplication].idleTimerDisabled = YES;
        //아직 세션이 완전히 시작된것이 아니기 때문에 일단 비활성화.
        //IDCaptureSessionCoordinatorDelegate methods 인 - (void)coordinatorDidBeginRecording 에서 다시 활성화 해준다.
        self.recordButton.enabled = NO;
        self.recordButton.title = @"정지";
        [self.captureSessionCoordinator startRecording];
        _recording = YES;
    }
}
//녹화 step2
//IDCaptureSessionPipelineViewController클래스 - 녹화가 시작되면 버튼 활성화

#pragma mark = IDCaptureSessionCoordinatorDelegate methods
- (void)coordinatorDidBeginRecording:(IDCaptureSessionCoordinator *)coordinator
{
    NSLog(@"IDCaptureSessionPipelineViewController coordinatorDidBeginRecording 호출");
    _recordButton.enabled = YES;

}



//녹화 step3
//IDCaptureSessionAssetWriterCoordinator 클래스
#pragma mark - Recording State Machine
// call under @synchonized( self ) // 레코딩 상태 변경해주기!
- (void)transitionToRecordingStatus:(RecordingStatus)newStatus error:(NSError *)error
{
    RecordingStatus oldStatus = _recordingStatus; //제일 처음에는 0
    _recordingStatus = newStatus;                           // 처음에 1을 넘겨 받는다.
    NSLog(@"IDCaptureSessionAssetWriterCoordinator - transitionToRecordingStatus 진입 : oldStatus : %ld , newStatus : %ld" , (long)oldStatus , (long)newStatus);
    //녹화버튼 클릭
    // oldStatus : 0, newStatus: 1
    //oldStatus : 1, newStatus: 2       //  녹화실행
    
    //정지 버튼 클릭
    //oldStatus : 2, newStatus: 3
    //oldStatus : 3, newStatus: 0
    

    if(newStatus != oldStatus){
      
        //newStatus 가 0, 멈춤 상태일때
        if (error && (newStatus == RecordingStatusIdle)) {
            NSLog(@"IDCaptureSessionAssetWriterCoordinator - 녹화 멈춤 상태");
            
        } else {
            
            //멈춤상태가 아니면
            error = nil;
            //RecordingStatusStartingRecording = 1,    RecordingStatusRecording = 2
            if (oldStatus == RecordingStatusStartingRecording && newStatus == RecordingStatusRecording) {
                //녹화 버튼을 -> 정지 한 뒤 -> 활성화 시켜준다.
                //delegateCallbackQueue는 부모 클래스인 IDCaptureSessionCoordinator에서 가지고 있고
                //IDCaptureSessionPipelineViewController 클래스에서 dispatch_get_main_queue()를 이미 넣어주었다.
                dispatch_async(self.delegateCallbackQueue, ^{
                    @autoreleasepool{
                        
                        [self.delegate coordinatorDidBeginRecording:self];
                    }
                });
                
            } else if(oldStatus == RecordingStatusStoppingRecording && newStatus == RecordingStatusIdle){
                NSLog(@"IDCaptureSessionAssetWriterCoordinator - 녹화 정지 상태");
            }
            
        }
    }
}



//녹화 step4

//IDAssetWriterCoordinator 클래스

-(instancetype)initWithURL:(NSURL *)URL
{
    
    if (!URL) {
        return nil;
    }
    
    self = [super init];
    if (self) {
         NSLog(@"IDAssetWriterCoordinator - initWithURL 진입");
        _writingQueue = dispatch_queue_create("com.example.assetwriter.writing", DISPATCH_QUEUE_SERIAL);
        _videoTrackTransform = CGAffineTransformMakeRotation(M_PI_2); // 세로 모드
        _URL = URL;
    }
    return self;
}



//녹화 step5

//IDAssetWriterCoordinator 클래스

//IDCaptureSessionAssetWriterCoordinator 클래스에서 넘겨준 오디오 관련 CMFormatDescriptionRef(output), NSDictionary 값 변수에 셋팅
- (void)addAudioTrackWithSourceFormatDescription:(CMFormatDescriptionRef)formatDescription settings:(NSDictionary *)audioSettings
{
     NSLog(@"IDAssetWriterCoordinator - addAudioTrackWithSourceFormatDescription 진입");
    //formatDescription 가 없다면
    if ( formatDescription == NULL ) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"NULL format description" userInfo:nil];
        return;
    }
    
    @synchronized(self)
    {
    
        //WriterStatusIdle = 0
        if (_status != WriterStatusIdle) {
            @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot add tracks while not idle" userInfo:nil];
            return;
        }
        
        if ( _audioTrackSourceFormatDescription ) {
            NSLog(@"IDAssetWriterCoordinator - Cannot add more than one audio track");
            @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot add more than one audio track" userInfo:nil];
            return;
        }
        _audioTrackSourceFormatDescription = (CMFormatDescriptionRef)CFRetain( formatDescription );
        _audioTrackSettings = [audioSettings copy];
    }
}



//녹화 step6
//IDAssetWriterCoordinator 클래스

//IDCaptureSessionAssetWriterCoordinator 클래스에서 넘겨준 오디오 관련 CMFormatDescriptionRef(output), NSDictionary 값 변수에 셋팅
- (void)addVideoTrackWithSourceFormatDescription:(CMFormatDescriptionRef)formatDescription settings:(NSDictionary *)videoSettings
{
    NSLog(@"IDAssetWriterCoordinator - addVideoTrackWithSourceFormatDescription 진입");
    if ( formatDescription == NULL ){
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"NULL format description" userInfo:nil];
        return;
    }
    @synchronized( self )
    {
        if (_status != WriterStatusIdle){
            @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot add tracks while not idle" userInfo:nil];
            return;
        }
        
        if(_videoTrackSourceFormatDescription ){
            @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Cannot add more than one video track" userInfo:nil];
            return;
        }
        
        _videoTrackSourceFormatDescription = (CMFormatDescriptionRef)CFRetain( formatDescription );
        _videoTrackSettings = [videoSettings copy];
    }
}

//녹화 step7
//IDAssetWriterCoordinator 클래스
- (void)setDelegate:(id<IDAssetWriterCoordinatorDelegate>)delegate callbackQueue:(dispatch_queue_t)delegateCallbackQueue
{
     NSLog(@"IDAssetWriterCoordinator - setDelegate <IDAssetWriterCoordinatorDelegate> 진입");
    NSLog(@"IDAssetWriterCoordinator - setDelegate 진입");
    if ( delegate && ( delegateCallbackQueue == NULL ) ) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Caller must provide a delegateCallbackQueue" userInfo:nil];
    }
    
    @synchronized( self )
    {
        _delegate = delegate;
        if ( delegateCallbackQueue != _delegateCallbackQueue  ) {
            _delegateCallbackQueue = delegateCallbackQueue;
        }
    }
}

//녹화 step8
//IDCaptureSessionAssetWriterCoordinator 클래스
#pragma mark - IDAssetWriterCoordinatorDelegate methods

//녹화 시작 버튼 클릭했을때 - 쓰기 준비!!
- (void)writerCoordinatorDidFinishPreparing:(IDAssetWriterCoordinator *)coordinator
{
    NSLog(@"IDCaptureSessionAssetWriterCoordinator -  writerCoordinatorDidFinishPreparing 진입 :");
    @synchronized(self)
    {
        if(_recordingStatus != RecordingStatusStartingRecording){
            @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Expected to be in StartingRecording state" userInfo:nil];
            return;
        }
        // 2
        [self transitionToRecordingStatus:RecordingStatusRecording error:nil];
    }
}

//녹화 step9
//IDCaptureSessionAssetWriterCoordinator 클래스
//녹화 시작 후 쓰기중에 오류발생 했을때!!
- (void)writerCoordinator:(IDAssetWriterCoordinator *)recorder didFailWithError:(NSError *)error
{
    NSLog(@"IDCaptureSessionAssetWriterCoordinator -  didFailWithError 진입 :");
    @synchronized( self ) {
        self.assetWriterCoordinator = nil;
        [self transitionToRecordingStatus:RecordingStatusIdle error:error];
    }
}



//녹화 step10
//IDAssetWriterCoordinator 클래스
// call under @synchonized( self )
- (void)transitionToStatus:(WriterStatus)newStatus error:(NSError *)error
{
    NSLog(@"IDAssetWriterCoordinator - transitionToStatus 진입");
     BOOL shouldNotifyDelegate = NO;
    
    // 처음에 준비할때 는 1
    
    if (newStatus != _status) {
        
        if((newStatus == WriterStatusFinished) || (newStatus == WriterStatusFailed)){
            
            shouldNotifyDelegate = YES;
            //asset writer과 인풋을 해체하기 전에 , 더 이상 샘플 버퍼가 없는지 확인!
            
            dispatch_async(_writingQueue, ^{
                self->_assetWriter = nil;
                self->_videoInput = nil;
                self -> _audioInput = nil;
                
                //상태가 실패면 임시 저장 파일에서 삭제
                if (newStatus == WriterStatusFailed) {
                    [[NSFileManager defaultManager] removeItemAtURL:self->_URL error:NULL];
                }
            });//dispatch_async end -
            
        }else if(newStatus == WriterStatusRecording){   //WriterStatusRecording = 2
            //녹화상태
            shouldNotifyDelegate = YES;
        }
        
        _status = newStatus;
    
    }
   
    
    if (shouldNotifyDelegate && self.delegate) { // delegate =  IDAssetWriterCoordinatorDelegate
        
        //IDCaptureSessionAssetWriterCoordinator 클래스에서 콜백큐 넣어준다.
        dispatch_async(_delegateCallbackQueue, ^{
            
            
            @autoreleasepool
            {
                switch (newStatus) {
                    case WriterStatusRecording:             //2 - 쓰기 준비
                        [self.delegate writerCoordinatorDidFinishPreparing:self];
                        break;
                    case WriterStatusFinished:              //5 -  쓰기 종료
                        [self.delegate writerCoordinatorDidFinishRecording:self];
                        break;
                    case WriterStatusFailed:                 //6
                        [self.delegate writerCoordinator:self didFailWithError:error];
                        break;
                        
                    default:
                        break;
                }
            }
            
        });
        
    }
}



//녹화 step11 - (NSDictionary)videoSettings 이 null 일때 호출됨
//IDAssetWriterCoordinator 클래스
- (NSDictionary *)fallbackVideoSettingsForSourceFormatDescription:(CMFormatDescriptionRef)videoFormatDescription
{
    NSLog(@"IDAssetWriterCoordinator - fallbackVideoSettingsForSourceFormatDescription 진입");
    
    float bitsPerPixel;
    CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(videoFormatDescription);
    int numPixels = dimensions.width * dimensions.height;
    int bitsPerSecond;
    
    NSLog(@"비디오 셋팅이 제공되지 않아, 기본값으로 셋팅합니다.....");
    
    //SD보다 낮은 해상도는 스트리밍을위한 것이고 낮은 비트 전송률을 사용한다고 가정한다.
    if (numPixels < (640 * 480)) {
        bitsPerPixel = 4.05;            //이 비트율은 AVCaptureSessionPresetMedium 또는 Low로 생성되는 퀄리티와 거의 일치한다.
    }
    else{
        bitsPerPixel = 10.1;            //이 비트 전송률은 AVCaptureSessionPresetHigh에서 생성되는 품질과 거의 일치한다.
    }
    
    bitsPerSecond = numPixels * bitsPerPixel;
    
    NSDictionary *compressionProperties = @{AVVideoAverageBitRateKey : @(bitsPerSecond),
                                            AVVideoExpectedSourceFrameRateKey : @(30),
                                            AVVideoMaxKeyFrameIntervalKey : @(30) };
    
    return @{
             AVVideoCodecKey : AVVideoCodecTypeH264 ,
             AVVideoWidthKey : @(dimensions.width),
             AVVideoHeightKey : @(dimensions.height),
             AVVideoCompressionPropertiesKey : compressionProperties
             };
}



//녹화 step11 - 2 (NSDictionary)videoSettings 이 null 일때 호출됨
//IDAssetWriterCoordinator 클래스
- (NSError *)cannotSetupInputError
{
    NSLog(@"IDAssetWriterCoordinator - cannotSetupInputError 진입");
    

    NSString *localizedDescription = NSLocalizedString( @"녹화가 시작되지 못했습니다.", nil );
    NSString *localizedFailureReason = NSLocalizedString( @"asset writer input 을 셋팅할 수 없습니다.....", nil );
    NSDictionary *errorDict = @{ NSLocalizedDescriptionKey : localizedDescription,
                                 NSLocalizedFailureReasonErrorKey : localizedFailureReason };
    return [NSError errorWithDomain:@"com.example" code:0 userInfo:errorDict];
}




//녹화 step12
//video input(AVAssetWriterInput) 생성 , assetWriter에 videoInput 추가
//IDAssetWriterCoordinator 클래스
- (BOOL)setupAssetWriterVideoInputWithSourceFormatDescription:(CMFormatDescriptionRef)videoFormatDescription transform:(CGAffineTransform)transform settings:(NSDictionary *)videoSettings error:(NSError **)errorOut
{
    NSLog(@"IDAssetWriterCoordinator - setupAssetWriterVideoInputWithSourceFormatDescription 진입");
    
    //비디오 셋팅이 존재하지 않으면 defalt 값을 만들어서 할당해 준다.
    if (!videoSettings) {
        videoSettings = [self fallbackVideoSettingsForSourceFormatDescription:videoFormatDescription];
    }
    
    //AVAssetWriter가 [비디오 셋팅값]을 지원한다면
    if ([_assetWriter canApplyOutputSettings:videoSettings forMediaType:AVMediaTypeVideo]) {
        
        //AVAssetWriterInput 에 [비디오 셋팅]과 [비디오 포멧 설명]을 넣고 초기화, 생성 해준다.
        _videoInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings sourceFormatHint:videoFormatDescription];
        _videoInput.expectsMediaDataInRealTime = YES;
        _videoInput.transform = transform;
        
        //AVAssetWriter 에 AVAssetWriterInput을 넣어준다.
        if ([_assetWriter canAddInput:_videoInput]) {
            [_assetWriter addInput:_videoInput];
        }else{
            
            if (errorOut) {
                *errorOut = [self cannotSetupInputError];
            }
             return NO;
        }
        
    }else{
        if (errorOut) {
            *errorOut = [self cannotSetupInputError];
        }
        return NO;
    }
    
    return YES;
}



//녹화 step12 - 2
//audio input(AVAssetWriterInput) 생성 , assetWriter에 videoInput 추가
//IDAssetWriterCoordinator 클래스
- (BOOL)setupAssetWriterAudioInputWithSourceFormatDescription:(CMFormatDescriptionRef)audioFormatDescription settings:(NSDictionary *)audioSettings error:(NSError **)errorOut
{
    NSLog(@"IDAssetWriterCoordinator - setupAssetWriterAudioInputWithSourceFormatDescription 진입");
    
    //audioSettings이 존재하지 않다면..
    if (!audioSettings) {
        audioSettings = @{AVFormatIDKey : @(kAudioFormatMPEG4AAC)};
    }
    
    //assetWriter 가 [오디오 셋팅]을 지원하는지 확인
    if ([_assetWriter canApplyOutputSettings:audioSettings forMediaType:AVMediaTypeAudio]) {
        _audioInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio outputSettings:audioSettings sourceFormatHint:audioFormatDescription];
        _audioInput.expectsMediaDataInRealTime = YES;
        
        //assetWriter 에 [오디오 인풋] 삽입
        if ([_assetWriter canAddInput:_audioInput]) {
            [_assetWriter addInput:_audioInput];
        }else {
            if (errorOut ) {
                *errorOut = [self cannotSetupInputError];
            }
            return NO;
        }
        
    }else
    {
        if (errorOut) {
            *errorOut = [self cannotSetupInputError];
        }
        return NO;
    }
    
    
    return YES;
}



//녹화 step13
- (void)prepareToRecord
{
     NSLog(@"IDAssetWriterCoordinator - prepareToRecord 진입");
    
    @synchronized( self )
    {
        if (_status != WriterStatusIdle){   //WriterStatusIdle = 0
            @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"예외: 이미 준비가 완료되었습니다, 다시 준비 할 수 없습니다." userInfo:nil];
            return;
        }
                        //  1
        [self transitionToStatus:WriterStatusPreparingToRecord error:nil];
    }
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        
        NSError *error = nil;
         // AVAssetWriter 은 이미 존재하고 있는 파일 경로에 덮어쓰기 할 수 없다..
        [[NSFileManager defaultManager] removeItemAtURL:self->_URL error:NULL];
        
        //assetWriter 초기화
        self->_assetWriter = [[AVAssetWriter alloc] initWithURL:self->_URL fileType:AVFileTypeQuickTimeMovie error:&error];
        
        //  assetWriter 생성하고  Writeinputs 넣기
        if (!error && self->_videoTrackSourceFormatDescription ) {
            
            [self setupAssetWriterVideoInputWithSourceFormatDescription:self->_videoTrackSourceFormatDescription transform:self->_videoTrackTransform settings:self->_videoTrackSettings error:&error];
            
        }
         //  assetWriter 생성하고  Writeinputs 넣기
        if (!error && self->_audioTrackSourceFormatDescription ) {
            [self setupAssetWriterAudioInputWithSourceFormatDescription:self->_audioTrackSourceFormatDescription settings:self->_audioTrackSettings error:&error];
        }
        
        //AVAssetWriter 시작!
        if (!error) {
            BOOL success = [self -> _assetWriter startWriting];
            if (!success) {
                error = self -> _assetWriter.error;
            }
        }
        
        @synchronized(self)
        {
            
            if (error) {
                //6번
                [self transitionToStatus:WriterStatusFailed error:error];
            }else{
                
                // 2 = 레코딩 시작 상태로 변경
                [self transitionToStatus:WriterStatusRecording error:nil];
                
            }
            
        }
        
        
    });
}



//녹화 step14
#pragma mark - Recording
//IDCaptureSessionAssetWriterCoordinator 클래스 (자식)
- (void)startRecording
{
   
    @synchronized(self)
    {
    
    // 처음 상태 값 : _recordingStatus : 0 ,  RecordingStatusIdle = 0
    NSLog(@"IDCaptureSessionAssetWriterCoordinator - startRecording 진입 : _recordingStatus : %ld , RecordingStatusIdle : %ld" , (long)_recordingStatus , (long)RecordingStatusIdle);
    
    //값이 다르면 이미 녹화가 되고 있다
    if (_recordingStatus != RecordingStatusIdle) {
        @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"이미 녹화되고 있습니다." userInfo:nil];
        return;
    }
    
    //  //레코딩 상태 변경해주기,  RecordingStatusStartingRecording = 1 값을 가지고 있다.
    [self transitionToRecordingStatus:RecordingStatusStartingRecording error:nil];
    
    }//@synchronized end
    
    //임시 저장 경로 얻기
    IDFileManager *fm = [IDFileManager new];
    _recordingURL = [fm tempFileURL];
    
    //레코딩 임시저장 경로 셋팅
    self.assetWriterCoordinator = [[IDAssetWriterCoordinator alloc] initWithURL:_recordingURL];
    
    // outputAudioFormatDescription 값이 존재한다면 ,  참고 : didOutputSampleBuffer 에서 할당 해줬음
    if (_outputAudioFormatDescription != nil) {
        
        [_assetWriterCoordinator addAudioTrackWithSourceFormatDescription:self.outputAudioFormatDescription settings:_audioCompressionSettings];
    
    }
    
      [_assetWriterCoordinator addVideoTrackWithSourceFormatDescription:self.outputVideoFormatDescription settings:_videoCompressionSettings];
    
    //writercallback  큐 생성
    dispatch_queue_t callbackQueue = dispatch_queue_create("com.example.capturesession.writercallback", DISPATCH_QUEUE_SERIAL);
    [_assetWriterCoordinator setDelegate:self callbackQueue:callbackQueue];
    
    //비동기식이면 recorderDidFinishPreparing : 또는 recorder : didFailWithError :를 호출하면 다시 호출됨.
      [_assetWriterCoordinator prepareToRecord];
}


//녹화 step15 -2
//IDAssetWriterCoordinator 클래스
//최종적으로 버퍼 더하기
- (void)appendSampleBuffer:(CMSampleBufferRef)sampleBuffer ofMediaType:(NSString *)mediaType
{
    NSLog(@"IDAssetWriterCoordinator - appendSampleBuffer 진입");
   
    //버퍼 값 유무 체크
    if (sampleBuffer == NULL) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"sample 버퍼가 비었어요.." userInfo:nil];
        return;
    }
    // 녹화 준비 체크
    @synchronized(self){
        if (_status < WriterStatusRecording) {
            @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"녹화 준비가 안됐습니다.." userInfo:nil];
            return;
        }
    }
    
    
    CFRetain(sampleBuffer);
    
    dispatch_async(_writingQueue, ^{
        
        @autoreleasepool
        {
            
            @synchronized(self)
            {
                // From the client's perspective the movie recorder can asynchronously transition to an error state as the result of an append.
                // Because of this we are lenient when samples are appended and we are no longer recording.
                // Instead of throwing an exception we just release the sample buffers and return.
                //클라이언트 관점에서 보면, 무비 레코더는 버퍼 추가의 결과로 비동기 적으로 오류 상태로 전환 될 수 있다..
                //이 때문에 샘플이 추가되고 더 이상 녹음하지 않을 때  오류 관리에 허술해질 수 있다.
                // 예외를 던지는 대신 샘플 버퍼를 해제하고 반환한다.
                
               
                if (self->_status  > WriterStatusFinishingRecordingPart1) {      //버퍼가 추가되기를 기다리는 중 3번 보다 클때
                    CFRelease(sampleBuffer);
                    return ;
                }

            } //@synchronized end /
            
            
            if (!self->_haveStartedSession && mediaType == AVMediaTypeVideo) {
                //기록!
                [self->_assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
                self -> _haveStartedSession = YES;
            }
            
            AVAssetWriterInput *input = (mediaType == AVMediaTypeVideo) ? self-> _videoInput : self -> _audioInput;
            
            if (input.readyForMoreMediaData) {
               //************input 준비하고 데이터 버퍼 더하기****************!!!
                
                BOOL success = [input appendSampleBuffer:sampleBuffer];
                if (!success) {
                    NSError *error = self->_assetWriter.error;
                    @synchronized(self){
                        NSLog(@"버퍼 기록 중 WriterStatusFailed");
                        [self transitionToStatus:WriterStatusFailed error:error];
                    }
                }
                

            }else{
                 NSLog( @"%@ 미디어 데이터를 위한 인풋이 준비되지 않았습니다.., dropping buffer", mediaType );
            }
            
            //버퍼 비우기?
            CFRelease(sampleBuffer);
            
        }//@autoreleasepool end /
    });
}



//녹화 step16 -1
//IDAssetWriterCoordinator 클래스
- (void)appendVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
     NSLog(@"IDAssetWriterCoordinator - appendVideoSampleBuffer 진입");
    [self appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeVideo];
}

//녹화 step16 -2
//IDAssetWriterCoordinator 클래스
- (void)appendAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
     NSLog(@"IDAssetWriterCoordinator - appendAudioSampleBuffer 진입");
     [self appendSampleBuffer:sampleBuffer ofMediaType:AVMediaTypeAudio];
}

- (void)finishRecording
{
     NSLog(@"IDAssetWriterCoordinator - finishRecording 진입");
}



#pragma mark - SampleBufferDelegate methods
////녹화 step17
//IDCaptureSessionAssetWriterCoordinator 클래스
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :");
    
    CMFormatDescriptionRef formatDescription =  CMSampleBufferGetFormatDescription(sampleBuffer);
    
    
    if (connection == _videoConnection) {
        NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection");
        
        if (self.outputVideoFormatDescription == nil) {
            NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription == nil");
            
            // Don't render the first sample buffer.
            // This gives us one frame interval (33ms at 30fps) for setupVideoPipelineWithInputFormatDescription: to complete.
            // Ideally this would be done asynchronously to ensure frames don't back up on slower devices.
            
            //TODO: outputVideoFormatDescription should be updated whenever video configuration is changed (frame rate, etc.)
            //Currently we don't use the outputVideoFormatDescription in IDAssetWriterRecoredSession
            
            /*
             
             첫 번째 샘플 버퍼를 렌더링하지 마십시오.
             이 작업을 완료하려면 setupVideoPipelineWithInputFormatDescription :에 대해 하나의 프레임 간격 (30fps에서 33ms)이 필요합니다.
             이상적으로 이것은 느린 장치에서 프레임이 백업되지 않도록 비동기 적으로 수행됩니다.
             
             TODO : 비디오 구성이 변경 될 때마다 outputVideoFormatDescription을 업데이트해야합니다 (프레임 속도 등)
             현재 우리는 IDAssetWriterRecoredSession에서 outputVideoFormatDescription을 사용하지 않습니다
             */
            
            [self setupVideoPipelineWithInputFormatDescription:formatDescription];
            
            
        }else{
            NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil");
            self.outputVideoFormatDescription = formatDescription;
            @synchronized(self){
                
                //2 이면
                if(_recordingStatus == RecordingStatusRecording){
                    
                    NSLog(@"IDCaptureSessionAssetWriterCoordinator - [_assetWriterCoordinator appendVideoSampleBuffer:sampleBuffer]  호출 :");
                    //버퍼 붙이기 *****
                    [_assetWriterCoordinator appendVideoSampleBuffer:sampleBuffer];
                }
                
            }
            
        }
        
        
    }else if(connection == _audioConnection){
        
       NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection");
        self.outputAudioFormatDescription = formatDescription;
        @synchronized( self ) {
             //2 면
            if(_recordingStatus == RecordingStatusRecording){
                //버퍼 붙이기 *****
                [_assetWriterCoordinator appendAudioSampleBuffer:sampleBuffer];
            }
        }
        
    }
    
}


////녹화 step18
//IDCaptureSessionAssetWriterCoordinator 클래스
//자식
- (void)startRecording
{
   
    @synchronized(self)
    {
    
    // 처음 상태 값 : _recordingStatus : 0 ,  RecordingStatusIdle = 0
    NSLog(@"IDCaptureSessionAssetWriterCoordinator - startRecording 진입 : _recordingStatus : %ld , RecordingStatusIdle : %ld" , (long)_recordingStatus , (long)RecordingStatusIdle);
    
    //값이 다르면 이미 녹화가 되고 있다
    if (_recordingStatus != RecordingStatusIdle) {
        @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"이미 녹화되고 있습니다." userInfo:nil];
        return;
    }
    
    //  //레코딩 상태 변경해주기,  RecordingStatusStartingRecording = 1 값을 가지고 있다.
    [self transitionToRecordingStatus:RecordingStatusStartingRecording error:nil];
    
    }//@synchronized end
    
    //임시 저장 경로 얻기
    IDFileManager *fm = [IDFileManager new];
    _recordingURL = [fm tempFileURL];
    
    //레코딩 임시저장 경로 셋팅
    self.assetWriterCoordinator = [[IDAssetWriterCoordinator alloc] initWithURL:_recordingURL];
    
    // outputAudioFormatDescription 값이 존재한다면 ,  참고 : didOutputSampleBuffer 에서 할당 해줬음
    if (_outputAudioFormatDescription != nil) {
        
        [_assetWriterCoordinator addAudioTrackWithSourceFormatDescription:self.outputAudioFormatDescription settings:_audioCompressionSettings];
    
    }
    
      [_assetWriterCoordinator addVideoTrackWithSourceFormatDescription:self.outputVideoFormatDescription settings:_videoCompressionSettings];
    
    //writercallback  큐 생성
    dispatch_queue_t callbackQueue = dispatch_queue_create("com.example.capturesession.writercallback", DISPATCH_QUEUE_SERIAL);
    [_assetWriterCoordinator setDelegate:self callbackQueue:callbackQueue];
    
    //비동기식이면 recorderDidFinishPreparing : 또는 recorder : didFailWithError :를 호출하면 다시 호출됨.
      [_assetWriterCoordinator prepareToRecord];
}




////녹화 step19
//IDCaptureSessionPipelineViewController 클래스
- (IBAction)toggleRecording:(id)sender
{
    NSLog(@"IDCaptureSessionPipelineViewController - toggleRecording 버튼");
    
    if (_recording) {
        [_captureSessionCoordinator stopRecording];
        
    } else {
        
        //잠자기 모드 비활성화
        [UIApplication sharedApplication].idleTimerDisabled = YES;
        //아직 세션이 완전히 시작된것이 아니기 때문에 일단 비활성화.
        //IDCaptureSessionCoordinatorDelegate methods 인 - (void)coordinatorDidBeginRecording 에서 다시 활성화 해준다.
        self.recordButton.enabled = NO;
        self.recordButton.title = @"정지";
        [self.captureSessionCoordinator startRecording];
        _recording = YES;
    }
}

 

 

 

 

 

 

 

 

 

 

AVCaptureVideoDataOutput을 이용해서 카메라 만들기 2 - 녹화

 

#.지난번 포스팅에 이어서 '녹화' 버튼을 클릭했을때 작동되는 로직 순서를 알아보자.

1.녹화버튼을 클릭 하면 IDCaptureSessionPipelineViewController클래스의 toggleRecording 메소드가 작동된다.

2.IDCaptureSessionAssetWriterCoordinator 클래스의 startRecording 진입해서 레코딩 상태변화를 시키기위해 transitionToRecordingStatus 메소드를 호출한다. // oldStatus : 0, newStatus: 1 

3.IDCaptureSessionAssetWriterCoordinator 클래스의 startRecording 에서 계속해서 임시저장 경로를 얻고 IDAssetWriterCoordinator를 초기화 해준다.

4.IDAssetWriterCoordinator 객체가 생성되고 쓰기 작업을 위한 writing queue 와 비디오 방향, url 값이 셋팅된다.

5.그후 outputAudioFormatDescription 값이 존재한다면( 참고 : didOutputSampleBuffer 에서 할당 해줬음) IDAssetWriterCoordinator  클래스의 addAudioTrackWithSourceFormatDescription 메소드에

오디오(비디오) 관련 CMFormatDescriptionRef(output), NSDictionary셋팅값을 변수에 셋팅을 해준다.

6.그후 IDAssetWriterCoordinator 객체에 callback queue 와 setDelegate 를 해준다. (Self = IDCaptureSessionAssetWriterCoordinator)

7.assetWriterCoordinator 객체에 녹화준비를 위한 메소드를 호출한다. (prepareToRecord)

8.IDAssetWriterCoordinator클래스의 prepareToRecord 메소드에서 transitionToStatus 메소드에 1을 넣어준다.

—중간중간 IDCaptureSessionAssetWriterCoordinator 클래스의 didOutputSampleBuffer 메소드 호출(아직 쓸수없는 상황)—

9.IDAssetWriterCoordinator클래스에서 AssetWriter 초기화 시켜준다.(url 이용)

10.setupAssetWriterVideoInputWithSourceFormatDescription 메소드를 이용해서 AVAssetWriter가 [비디오 셋팅값]을 지원한다면 AVAssetWriterInput 에 [비디오 셋팅]과 [비디오 포멧 설명]을 넣고 초기화, 생성 해준다 그리고 AVAssetWriter 에 AVAssetWriterInput을 넣어준다.

11.setupAssetWriterAudioInputWithSourceFormatDescription 메소드를 이용해서 assetWriter 가 [오디오 셋팅]을 지원하는지 확인 후 , assetWriter 에 [오디오 인풋] 삽입한다.

12.AVAssetWriter 시작! [assetWriter startWriting]

13. transitionToStatus 에 2를 넘겨준다.

14.델리게이트 메소드인 writerCoordinatorDidFinishPreparing를 호출하고 거기서 transitionToRecordingStatus에 2를 넣고 호출 해준다.  // oldStatus : 1, newStatus: 2

15.그럼 transitionToRecordingStatus메소드에서 coordinatorDidBeginRecording 메소드를 이용해서 비활성화 되었던 정지버튼을 활성화 시켜준다!! (여기까지가 쓰기 준비!!)

16.그후 IDCaptureSessionAssetWriterCoordinator클래스에서  [_assetWriterCoordinator appendVideoSampleBuffer:sampleBuffer];를 호출

17.IDAssetWriterCoordinator클래스에서 AVAssetWriterInput(비디오, 오디오) 객체 초기화 한뒤, 이 인풋에 샘플 버퍼를 넣어준다. [input appendSampleBuffer:sampleBuffer];

(계속 반복……정지 버튼 누르기 전까지)

 

#.로그

[카메라 실행!]

IDCaptureSessionPipelineViewController setupWithPipelineMode IDCaptureSessionAssetWriterCoordinator 객체 생성시작!

2019-04-17 13:51:38.958716+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionCoordinator init 진입

2019-04-17 13:51:38.958762+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionCoordinator setupCaptureSession 진입 

2019-04-17 13:51:38.962791+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionCoordinator addDefaultCameraInputToCaptureSession 진입

2019-04-17 13:51:38.965839+0900 AssertWriterVideoRecorderNew[697:119055] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles

2019-04-17 13:51:38.966153+0900 AssertWriterVideoRecorderNew[697:119055] [MC] Reading from public effective user settings.

2019-04-17 13:51:38.975087+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionCoordinator addInput toCaptureSession 진입

2019-04-17 13:51:38.977239+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionCoordinator addInput toCaptureSession - 세션에 캡쳐디바이스인풋 들어갔어요!

2019-04-17 13:51:38.977288+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionCoordinator addDefaultMicInputToCaptureSession 진입

2019-04-17 13:51:38.991662+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionCoordinator addInput toCaptureSession 진입

2019-04-17 13:51:38.993057+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionCoordinator addInput toCaptureSession - 세션에 캡쳐디바이스인풋 들어갔어요!

2019-04-17 13:51:38.993111+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionAssetWriterCoordinator - init 진입

2019-04-17 13:51:38.993158+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionAssetWriterCoordinator - addDataOutputsToCaptureSession 진입 : 비디오, 오디오 아웃풋 객체 생성 + 설정

2019-04-17 13:51:38.993419+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionCoordinator addOutput  toCaptureSession 진입

2019-04-17 13:51:38.994996+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionCoordinator addOutput  toCaptureSession 진입

2019-04-17 13:51:38.995910+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionAssetWriterCoordinator - setCompressionSettings 진입 :

2019-04-17 13:51:38.996646+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionCoordinator setDelegate 진입 - 전역변수에 IDCaptureSessionCoordinatorDelegate 할당, delegateCallbackQueue 할당

2019-04-17 13:51:38.996694+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionPipelineViewController - configureInterface 호출 -  AVCaptureVideoPreviewLayer 객체 생성

2019-04-17 13:51:38.996711+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionCoordinator previewLayer 캡쳐 세션 넣어줌 초기화

2019-04-17 13:51:38.998285+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionCoordinator startRunning 진입 - dispatch_sync(세션큐, 캡쳐쎄션 스타트러닝!)

2019-04-17 13:51:39.192085+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:39.192150+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

2019-04-17 13:51:39.192170+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription == nil

2019-04-17 13:51:39.192198+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - setupVideoPipelineWithInputFormatDescription 진입 :

2019-04-17 13:51:39.230852+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:39.236021+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:39.236830+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:39.236855+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

2019-04-17 13:51:39.236869+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil

2019-04-17 13:51:39.238669+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:39.238712+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:39.247593+0900 AssertWriterVideoRecorderNew[697:119073] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:39.247652+0900 AssertWriterVideoRecorderNew[697:119073] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

2019-04-17 13:51:39.247666+0900 AssertWriterVideoRecorderNew[697:119073] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil

 

[녹화 버튼 클릭!]

 

2019-04-17 13:51:41.199519+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionPipelineViewController - toggleRecording 버튼

2019-04-17 13:51:41.202423+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionAssetWriterCoordinator - startRecording 진입 : _recordingStatus : 0 , RecordingStatusIdle : 0

2019-04-17 13:51:41.202525+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionAssetWriterCoordinator - transitionToRecordingStatus 진입 : oldStatus : 0 , newStatus : 1

2019-04-17 13:51:41.203757+0900 AssertWriterVideoRecorderNew[697:119055] IDAssetWriterCoordinator - initWithURL 진입

2019-04-17 13:51:41.203879+0900 AssertWriterVideoRecorderNew[697:119055] IDAssetWriterCoordinator - addAudioTrackWithSourceFormatDescription 진입

2019-04-17 13:51:41.203994+0900 AssertWriterVideoRecorderNew[697:119055] IDAssetWriterCoordinator - addVideoTrackWithSourceFormatDescription 진입

2019-04-17 13:51:41.204057+0900 AssertWriterVideoRecorderNew[697:119055] IDAssetWriterCoordinator - setDelegate <IDAssetWriterCoordinatorDelegate> 진입

2019-04-17 13:51:41.204103+0900 AssertWriterVideoRecorderNew[697:119055] IDAssetWriterCoordinator - setDelegate 진입

2019-04-17 13:51:41.204151+0900 AssertWriterVideoRecorderNew[697:119055] IDAssetWriterCoordinator - prepareToRecord 진입

2019-04-17 13:51:41.204196+0900 AssertWriterVideoRecorderNew[697:119055] IDAssetWriterCoordinator - transitionToStatus 진입

2019-04-17 13:51:41.210955+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.211035+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:41.217960+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.218091+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

2019-04-17 13:51:41.218175+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil

2019-04-17 13:51:41.228418+0900 AssertWriterVideoRecorderNew[697:119069] IDAssetWriterCoordinator - setupAssetWriterVideoInputWithSourceFormatDescription 진입

2019-04-17 13:51:41.231649+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.231708+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:41.248734+0900 AssertWriterVideoRecorderNew[697:119069] IDAssetWriterCoordinator - setupAssetWriterAudioInputWithSourceFormatDescription 진입

2019-04-17 13:51:41.249014+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.249074+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

2019-04-17 13:51:41.249095+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil

2019-04-17 13:51:41.255088+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.255149+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:41.278486+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.278900+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:41.297037+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.297126+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

2019-04-17 13:51:41.297158+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil

2019-04-17 13:51:41.315903+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.315981+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:41.317641+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.317694+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

2019-04-17 13:51:41.317750+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil

2019-04-17 13:51:41.337010+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.337086+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:41.360373+0900 AssertWriterVideoRecorderNew[697:119073] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.360462+0900 AssertWriterVideoRecorderNew[697:119073] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

2019-04-17 13:51:41.360490+0900 AssertWriterVideoRecorderNew[697:119073] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil

2019-04-17 13:51:41.361846+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.361914+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:41.371072+0900 AssertWriterVideoRecorderNew[697:119073] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.371145+0900 AssertWriterVideoRecorderNew[697:119073] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:41.380267+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.380426+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

2019-04-17 13:51:41.380448+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil

2019-04-17 13:51:41.385946+0900 AssertWriterVideoRecorderNew[697:119069] IDAssetWriterCoordinator - transitionToStatus 진입

2019-04-17 13:51:41.386317+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator -  writerCoordinatorDidFinishPreparing 진입 :

2019-04-17 13:51:41.386380+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - transitionToRecordingStatus 진입 : oldStatus : 1 , newStatus : 2

2019-04-17 13:51:41.386578+0900 AssertWriterVideoRecorderNew[697:119055] IDCaptureSessionPipelineViewController coordinatorDidBeginRecording 호출

!!!!!!!- <본격적으로 쓰기에 버퍼 넣기 시작> !!!!!!!!

2019-04-17 13:51:41.394184+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.394251+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:41.394326+0900 AssertWriterVideoRecorderNew[697:119069] IDAssetWriterCoordinator - appendAudioSampleBuffer 진입

2019-04-17 13:51:41.394365+0900 AssertWriterVideoRecorderNew[697:119069] IDAssetWriterCoordinator - appendSampleBuffer 진입

2019-04-17 13:51:41.394609+0900 AssertWriterVideoRecorderNew[697:119069] input 준비하고 데이터 버퍼 더하기

 

2019-04-17 13:51:41.412497+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.412649+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

2019-04-17 13:51:41.412677+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil

2019-04-17 13:51:41.412720+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - [_assetWriterCoordinator append비디오SampleBuffer:sampleBuffer]  호출 :

2019-04-17 13:51:41.412764+0900 AssertWriterVideoRecorderNew[697:119069] IDAssetWriterCoordinator - appendVideoSampleBuffer 진입

2019-04-17 13:51:41.412789+0900 AssertWriterVideoRecorderNew[697:119069] IDAssetWriterCoordinator - appendSampleBuffer 진입

2019-04-17 13:51:41.412881+0900 AssertWriterVideoRecorderNew[697:119069] !self->_haveStartedSession && mediaType == AVMediaTypeVideo

2019-04-17 13:51:41.413553+0900 AssertWriterVideoRecorderNew[697:119069] input 준비하고 데이터 버퍼 더하기

 

2019-04-17 13:51:41.417366+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.417422+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:41.417450+0900 AssertWriterVideoRecorderNew[697:119069] IDAssetWriterCoordinator - appendAudioSampleBuffer 진입

2019-04-17 13:51:41.417474+0900 AssertWriterVideoRecorderNew[697:119069] IDAssetWriterCoordinator - appendSampleBuffer 진입

2019-04-17 13:51:41.417550+0900 AssertWriterVideoRecorderNew[697:119069] input 준비하고 데이터 버퍼 더하기

 

2019-04-17 13:51:41.443026+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.443116+0900 AssertWriterVideoRecorderNew[697:119071] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:41.443155+0900 AssertWriterVideoRecorderNew[697:119071] IDAssetWriterCoordinator - appendAudioSampleBuffer 진입

2019-04-17 13:51:41.443186+0900 AssertWriterVideoRecorderNew[697:119071] IDAssetWriterCoordinator - appendSampleBuffer 진입

2019-04-17 13:51:41.443296+0900 AssertWriterVideoRecorderNew[697:119071] input 준비하고 데이터 버퍼 더하기

 

2019-04-17 13:51:41.448048+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.448116+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

2019-04-17 13:51:41.448142+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil

2019-04-17 13:51:41.448177+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - [_assetWriterCoordinator append비디오SampleBuffer:sampleBuffer]  호출 :

2019-04-17 13:51:41.448225+0900 AssertWriterVideoRecorderNew[697:119069] IDAssetWriterCoordinator - appendVideoSampleBuffer 진입

2019-04-17 13:51:41.448264+0900 AssertWriterVideoRecorderNew[697:119069] IDAssetWriterCoordinator - appendSampleBuffer 진입

2019-04-17 13:51:41.448365+0900 AssertWriterVideoRecorderNew[697:119069] input 준비하고 데이터 버퍼 더하기

 

2019-04-17 13:51:41.464489+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

2019-04-17 13:51:41.464578+0900 AssertWriterVideoRecorderNew[697:119069] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

2019-04-17 13:51:41.464600+0900 AssertWriterVideoRecorderNew[697:119069] IDAssetWriterCoordinator - appendAudioSampleBuffer 진입

2019-04-17 13:51:41.464616+0900 AssertWriterVideoRecorderNew[697:119069] IDAssetWriterCoordinator - appendSampleBuffer 진입

2019-04-17 13:51:41.464692+0900 AssertWriterVideoRecorderNew[697:119069] input 준비하고 데이터 버퍼 더하기

 

파일다운로드

AssertWriterVideoRecorderNew2.zip
0.10MB

AVCaptureVideoDataOutput을 이용해서 카메라 만들기1

iOS 비디오 녹화 어플을 만들다가 생각지도 못한 난관에 봉착했다.

비디오를 녹화하는 도중에 녹화한 데이터크기를 실시간으로 체크하는 기능을 추가해야하는 상황이 발생했다.

젠장…

AVCaptureMovieFileOutput을 이용해서 비디오를 커스터마이징 하고 있었는데, AVCaptureMovieFileOutput으로는 실시간으로 데이터를 체크가 불가능했다.

대신, AVCaptureVideoDataOutput 과 AVAssetWriter를 이용해야지 데이터 체크가 가능했다. 

그리고 AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate 메소드를 이용해야 했다.

초기화는 AVCaptureMovieFileOutput 과 비슷하다.

초기화 할때 세션을 만들고 그 세션에 디바이스인풋과 아웃풋을 넣고 세션을 가동시키는 것은 동일하다.

AVCaptureVideoDataOutput의 다른 점은 세션이 가동될때 실시간으로 데이터를 

몇 가지 알아본 것들을 예제 파일로 정리해본다.

 

클래스 설명

IDCaptureSessionPipelineViewController 

  • UIViewController 를 부모로 상속하고, (IDCaptureSessionCoordinator 클래스의 ) IDCaptureSessionCoordinatorDelegate 프로토콜을 따른다.

예) coordinatorDidBeginRecording()  didFinishRecordingToOutputFileURL()

  • IDCaptureSessionCoordinator을 프로퍼티로 가지고 있다. @property (nonatomic, strong) IDCaptureSessionCoordinator *captureSessionCoordinator;  
  • 전체 ui, 기능을 컨트롤 한다.
  • IDCaptureSessionCoordinator(부모 클래스)를 먼저 초기화 하고, IDCaptureSessionAssetWriterCoordinator(자식 클래스)를 초기화 한다.
  • IDCaptureSessionCoordinator 에 델리게이트(자신)와 콜백큐(디스페치 메인큐)를 할당해준다.
  • IDCaptureSessionCoordinator 클래스를 호출 해서 AVCaptureVideoPreviewLayer에 세션을 넣어주고 반환값을 받는다. 
  • IDCaptureSessionCoordinator 의 세션을 실행 시킨다. dispatch_sync(_sessionQueue, ^{  [self->_captureSession startRunning]; });

IDCaptureSessionCoordinator(부모)

  • NSObject를 상속받는다.
  • AVCaptureSession, AVCaptureDevice, dispatch_queue_t(세션큐) ,dispatch_queue_t(콜백큐),  AVCaptureVideoPreviewLayer 를 프로퍼티로 가지고 있다.
  • IDCaptureSessionCoordinatorDelegate 프로토콜을 구현해 놓았다.
  • 세션을 만들고, 인풋, 아웃풋객체를 만들고 세션에 넣어주는 역할을 하는 클래스다.
  • 초기화 될때, 세션큐를 생성한다. 
  • 초기화 될때, AVCaptureSession 을 만들어서 AVCaptureDeviceInput(AVMediaTypeVideo)을 AVCaptureSession에 넣어주고, AVCaptureDevice를 초기화 해준다.
  • 그다음 AVCaptureDeviceInput(AVMediaTypeAudio)을 AVCaptureSession에 넣어준다.

IDCaptureSessionAssetWriterCoordinator(자식) 

  • IDCaptureSessionCoordinator을 상속하는 클래스
  • <AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate, IDAssetWriterCoordinatorDelegate> 프로토콜을 따른다.
  • IDAssetWriterCoordinatorDelegate는 IDAssetWriterCoordinator클래스에 구현해 놓았다.

- (void)writerCoordinatorDidFinishPreparing:(IDAssetWriterCoordinator *)coordinator;

- (void)writerCoordinator:(IDAssetWriterCoordinator *)coordinator didFailWithError:(NSError *)error;

- (void)writerCoordinatorDidFinishRecording:(IDAssetWriterCoordinator *)coordinator;

  • 비디오아웃풋, 오디오 아웃풋을 생성해서 세션에 넣어주는 것이 주요 역할이다. 그리고 세션이 시작되면 해당 델리게이트 메소드를 통해서 버퍼를 얻는다.
  • AVCaptureVideoDataOutput(비디오, 오디오), AVCaptureConnection(비디오, 오디오),AVAssetWriter, CMFormatDescriptionRef, IDAssetWriterCoordinator(클래스), NSDictionary *videoCompressionSettings를 프로퍼티로 가지고 있다.
  • 초기화 될때 videoDataOutputQueue를 만들고 실행시킽다. 초기화될때 audioDataOutputQueue를 만든다.
  • 초기화 될때 , videoDataOutput 을 생성하고, audioDataOutput을 생성하고 위에서 만든 비디오큐, 오디오 큐를 동시에 setSampleBufferDelegate 델리게이트 메소드에 넣어준다.
  • 비디오 아웃풋을 IDCaptureSessionCoordinator(클래스의) 세션에 넣어주고 videoConnection 객체를 만든다.
  • 오디오 아웃풋을 IDCaptureSessionCoordinator(클래스의) 세션에 넣어주고 audioConnection 객체를 만든다.
  • 비디오, 오디오 아웃풋을 이용해서 videoCompressionSettings, audioCompressionSettings 객체를 초기화 해준다.
  • 세션이 실행되면 델리게이트 메소드인 (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection 가 계속 호출된다.
  • 여기서 레코딩을 하는지 아니면 그냥 대기 상태인지를 체크해서 AVAssetWriter에 기록해준다.

IDAssetWriterCoordinator 

(2편에서...정리)

소스코드

IDCaptureSessionPipelineViewController 

#import <UIKit/UIKit.h>

@interface IDCaptureSessionPipelineViewController : UIViewController


@end

#import "IDFileManager.h"
#import "IDPermissionsManager.h"


#import "IDCaptureSessionPipelineViewController.h"
#import "IDCaptureSessionAssetWriterCoordinator.h"    //IDCaptureSessionCoordinator을 상속

// (IDCaptureSessionCoordinator 클래스의 ) IDCaptureSessionCoordinatorDelegate 프로토콜을 따르기 때문에 해당 메소드를 이곳에 구현해 놓았다.
// coordinatorDidBeginRecording()  didFinishRecordingToOutputFileURL(),

@interface IDCaptureSessionPipelineViewController () <IDCaptureSessionCoordinatorDelegate>


@property (nonatomic, strong) IDCaptureSessionCoordinator *captureSessionCoordinator;       //부모: 세션생성 하고  인풋디바이스를 세션에 넣어주는 클래스
@property (nonatomic, assign) BOOL recording;
@property (nonatomic, assign) BOOL dismissing;
@property (retain, nonatomic) IBOutlet UIBarButtonItem *recordButton;

@end

@implementation IDCaptureSessionPipelineViewController

- (void)viewDidLoad {
    [super viewDidLoad];
   
    NSLog(@"IDCaptureSessionPipelineViewController setupWithPipelineMode IDCaptureSessionAssetWriterCoordinator 객체 생성시작!");
    _captureSessionCoordinator = [IDCaptureSessionAssetWriterCoordinator new];          //부모객체 = 자식 new
    
    //캡쳐세션에 델리게이트 와 큐 설정
    [_captureSessionCoordinator setDelegate:self callbackQueue:dispatch_get_main_queue()];
    
    [self configureInterface];
}



- (IBAction)toggleRecording:(id)sender
{
    NSLog(@"IDCaptureSessionPipelineViewController - toggleRecording 버튼");
}

- (IBAction)closeCamera:(id)sender
{
    NSLog(@"IDCaptureSessionPipelineViewController - closeCamera 버튼");
}

#pragma mark - Private methods
- (void)configureInterface
{
 NSLog(@"IDCaptureSessionPipelineViewController - configureInterface 호출 -  AVCaptureVideoPreviewLayer 객체 생성");
    
    AVCaptureVideoPreviewLayer *previewLayer = [_captureSessionCoordinator previewLayer];
    previewLayer.frame = self.view.bounds;
    [self.view.layer insertSublayer:previewLayer atIndex:0];
    
    [_captureSessionCoordinator startRunning];
    
    
}





- (void)stopPipelineAndDismiss
{
    NSLog(@"IDCaptureSessionPipelineViewController - stopPipelineAndDismiss 호출");
}


- (void)checkPermissions
{
    NSLog(@"IDCaptureSessionPipelineViewController - IDPermissionsManager 객체 생성 + 카메라 오디오 권한 체크 시작");
}


#pragma mark = IDCaptureSessionCoordinatorDelegate methods
- (void)coordinatorDidBeginRecording:(IDCaptureSessionCoordinator *)coordinator
{
    NSLog(@"IDCaptureSessionPipelineViewController coordinatorDidBeginRecording 호출");

}


//녹화가 끝날때 (정지 버튼을 눌렀을 때)호출
- (void)coordinator:(IDCaptureSessionCoordinator *)coordinator didFinishRecordingToOutputFileURL:(NSURL *)outputFileURL error:(NSError *)error
{
    NSLog(@"IDCaptureSessionPipelineViewController didFinishRecordingToOutputFileURL 호출");

}



- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

 

IDCaptureSessionCoordinator(부모)

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>


@protocol IDCaptureSessionCoordinatorDelegate;

@interface IDCaptureSessionCoordinator : NSObject



@property (nonatomic, strong) AVCaptureSession *captureSession;                  //세션 객체
@property (nonatomic, strong) AVCaptureDevice *cameraDevice;                      //디바이스 객체
@property (nonatomic, strong) dispatch_queue_t delegateCallbackQueue;       //콜백 큐

@property (nonatomic, weak) id<IDCaptureSessionCoordinatorDelegate> delegate;

- (void)setDelegate:(id<IDCaptureSessionCoordinatorDelegate>)delegate callbackQueue:(dispatch_queue_t)delegateCallbackQueue;

- (BOOL)addInput:(AVCaptureDeviceInput *)input toCaptureSession:(AVCaptureSession *)captureSession;       //인풋객체 넣기

- (BOOL)addOutput:(AVCaptureOutput *)output toCaptureSession:(AVCaptureSession *)captureSession;         //아웃풋 객체 넣기

- (void)startRunning;
- (void)stopRunning;

- (void)startRecording;
- (void)stopRecording;

- (AVCaptureVideoPreviewLayer *)previewLayer;                                                                                                           //미리보기
@end


@protocol IDCaptureSessionCoordinatorDelegate <NSObject>
@required
- (void)coordinatorDidBeginRecording:(IDCaptureSessionCoordinator *)coordinator;
- (void)coordinator:(IDCaptureSessionCoordinator *)coordinator didFinishRecordingToOutputFileURL:(NSURL *)outputFileURL error:(NSError *)error;
@end
#import "IDCaptureSessionCoordinator.h"


@interface IDCaptureSessionCoordinator ()

@property (nonatomic, strong) dispatch_queue_t sessionQueue;
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer;

@end


@implementation IDCaptureSessionCoordinator


-(instancetype)init{
     NSLog(@"IDCaptureSessionCoordinator init 진입");
    self = [super init];
    if(self){
        
        //세션 큐 생성
        _sessionQueue = dispatch_queue_create("com.abc.my.session", DISPATCH_QUEUE_SERIAL);
        //캡쳐 세션 생성
        _captureSession = [self setupCaptureSession];
        
    }
    return self;
}


/*
 AVFoundation비디오 캡처에 사용할 때는 사용자 정의 사용자 인터페이스를 제공해야합니다.
 모든 카메라 인터페이스의 주요 구성 요소는 실시간 미리보기입니다.
 이것은 AVCaptureVideoPreviewLayer카메라보기에 하위 레이어로 추가 된 객체를 통해 가장 쉽게 구현됩니다 .
 */
- (AVCaptureVideoPreviewLayer *)previewLayer
{
    if(!_previewLayer && _captureSession){
        NSLog(@"IDCaptureSessionCoordinator previewLayer 캡쳐 세션 넣어줌 초기화");
        _previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_captureSession];
    }
    return _previewLayer;
}

- (void)setDelegate:(id<IDCaptureSessionCoordinatorDelegate>)delegate callbackQueue:(dispatch_queue_t)delegateCallbackQueue{
     NSLog(@"IDCaptureSessionCoordinator setDelegate 진입 - 전역변수에 IDCaptureSessionCoordinatorDelegate 할당, delegateCallbackQueue 할당");
    
    if (delegate && (delegateCallbackQueue == NULL)) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"호출자는 델리게이트 콜백큐를 제공해야 한다!" userInfo:nil];
    }
    
    @synchronized(self)
    {
        _delegate = delegate;
        if (delegateCallbackQueue != _delegateCallbackQueue) {
            _delegateCallbackQueue = delegateCallbackQueue;
        }
    }
}



//세션러닝 시작
- (void)startRunning{
     NSLog(@"IDCaptureSessionCoordinator startRunning 진입 - dispatch_sync(세션큐, 캡쳐쎄션 스타트러닝!)");
    dispatch_sync(_sessionQueue, ^{
        [self->_captureSession startRunning];
    });
    
}
//러닝 정지
- (void)stopRunning{
     NSLog(@"IDCaptureSessionCoordinator stopRunning 진입");
}
//녹화 시작
- (void)startRecording{
     NSLog(@"IDCaptureSessionCoordinator startRecording 진입");
}
//녹화 정지
- (void)stopRecording{
     NSLog(@"IDCaptureSessionCoordinator stopRecording 진입");
}


#pragma mark - Capture Session Setup
//세션 셋팅
- (AVCaptureSession *)setupCaptureSession
{
     NSLog(@"IDCaptureSessionCoordinator setupCaptureSession 진입 ");
    
    AVCaptureSession *captureSession = [AVCaptureSession new];
    
    //비디오 인풋 디바이스 생성 + 캡쳐 세션에 넣어주기 여부 체크
    if (![self addDefaultCameraInputToCaptureSession:captureSession]) {
        NSLog(@"비디오 인풋을 캡쳐세션에 넣기 실패");
    }
    
    //오디오 인풋 디바이스 생성 + 캡쳐 세션에 넣어주기 여부 체크
    if (![self addDefaultMicInputToCaptureSession:captureSession]) {
        NSLog(@"오디오 인풋을 캡쳐세션에 넣기 실패");
    }
    return captureSession;
}


//비디오 입력을 구성하려면 AVCaptureDeviceInput원하는 카메라 장치 로 개체를 만들고 캡처 세션에 추가
//기본 설정된 카메라를 캡쳐세션에 추가
- (BOOL)addDefaultCameraInputToCaptureSession:(AVCaptureSession *)captureSession
{
     NSLog(@"IDCaptureSessionCoordinator addDefaultCameraInputToCaptureSession 진입");
    NSError *error;
    
    //비디오 인풋 디바이스 객체 생성 및 초기화
    AVCaptureDeviceInput *cameraDeviceInput = [[AVCaptureDeviceInput alloc]initWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo] error:&error];
    
    if (error) {
        NSLog(@"error configuring 비디오 input : %@" , [error localizedDescription]);
        return NO;
    } else {
        //캡쳐 세션에  카메라 인풋 넣어주기
        BOOL success = [self addInput:cameraDeviceInput toCaptureSession:captureSession];
     
        //캡쳐 세션에 인풋디바이스 넣기 성공했으면 디바이스 객체를 할당해 준다.
        _cameraDevice= cameraDeviceInput.device;
        
          return success;
    }
}



//마이크 인풋디바이스 객체를 생성해서 캡쳐 세션에 넣어준다.
- (BOOL)addDefaultMicInputToCaptureSession:(AVCaptureSession *)captureSession
{
     NSLog(@"IDCaptureSessionCoordinator addDefaultMicInputToCaptureSession 진입");
    
    NSError *error;
    AVCaptureDeviceInput *micDeviceInput = [[AVCaptureDeviceInput alloc]initWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] error:&error];
    
    if (error) {
        NSLog(@"error configuring 오디오 input : %@" , [error localizedDescription]);
        return NO;
    } else {
      BOOL success =   [self addInput:micDeviceInput toCaptureSession:captureSession];
        return success;
    }
}


//인풋객체 넣기 -  전역 메소드
- (BOOL)addInput:(AVCaptureDeviceInput *)input toCaptureSession:(AVCaptureSession *)captureSession{
    NSLog(@"IDCaptureSessionCoordinator addInput toCaptureSession 진입");
    
    //캡쳐세션에 인풋 디바이스 넣을 수 있는지 확인
    if ([captureSession canAddInput:input]) {
        //캡쳐 세션에 인풋 디바이스 넣기
        [captureSession addInput:input];
        NSLog(@"IDCaptureSessionCoordinator addInput toCaptureSession - 세션에 캡쳐디바이스인풋 들어갔어요!");
        return YES;
    } else {
        NSLog(@"세션에 인풋 디바이스를 넣을 수 없습니다 : %@" , [input description]);
        return NO;
    }
    
    return YES;

}


//아웃풋 객체 넣기 -  전역 메소드
- (BOOL)addOutput:(AVCaptureOutput *)output toCaptureSession:(AVCaptureSession *)captureSession{
    NSLog(@"IDCaptureSessionCoordinator addOutput  toCaptureSession 진입");
    //아웃풋 객체를 세션에 넣어주기
    if ([captureSession canAddOutput:output]) {
        [captureSession addOutput:output];
        return YES;
    }else{
        NSLog(@"output을 세션에 넣을 수 없습니다! 설명 : %@" , [output description]);
    }
    
    
    return NO;

}

@end

 

IDCaptureSessionAssetWriterCoordinator(자식) 


#import "IDCaptureSessionCoordinator.h"

//@protocol IDCaptureSessionAssetWriterCoordinatorDelegate;
@interface IDCaptureSessionAssetWriterCoordinator : IDCaptureSessionCoordinator

@end

#import "IDCaptureSessionAssetWriterCoordinator.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import "IDAssetWriterCoordinator.h"
#import "IDFileManager.h"

typedef NS_ENUM( NSInteger, RecordingStatus )
{
    RecordingStatusIdle = 0,
    RecordingStatusStartingRecording,
    RecordingStatusRecording,
    RecordingStatusStoppingRecording,
};                                                                          //internal state machine


@interface IDCaptureSessionAssetWriterCoordinator () <AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate, IDAssetWriterCoordinatorDelegate>

@property (nonatomic, strong) dispatch_queue_t videoDataOutputQueue;
@property (nonatomic, strong) dispatch_queue_t audioDataOutputQueue;

//data아웃풋
@property (nonatomic, strong) AVCaptureVideoDataOutput *videoDataOutput;
@property (nonatomic, strong) AVCaptureAudioDataOutput *audioDataOutput;

//캡쳐 커넥션
@property (nonatomic, strong) AVCaptureConnection *audioConnection;
@property (nonatomic, strong) AVCaptureConnection *videoConnection;

//딕셔너리
@property (nonatomic, strong) NSDictionary *videoCompressionSettings;
@property (nonatomic, strong) NSDictionary *audioCompressionSettings;

@property (nonatomic, strong) AVAssetWriter *assetWriter;

@property (nonatomic, assign) RecordingStatus recordingStatus;              //NS_ENUM
@property (nonatomic, strong) NSURL *recordingURL;

@property(nonatomic, retain) __attribute__((NSObject)) CMFormatDescriptionRef outputVideoFormatDescription;
@property(nonatomic, retain) __attribute__((NSObject)) CMFormatDescriptionRef outputAudioFormatDescription;

@property(nonatomic, retain) IDAssetWriterCoordinator *assetWriterCoordinator;

@end

@implementation IDCaptureSessionAssetWriterCoordinator


- (instancetype)init
{
    self = [super init];
    if (self)
    {
        NSLog(@"IDCaptureSessionAssetWriterCoordinator - init 진입");
        
        //비디오 큐 생성
        self.videoDataOutputQueue = dispatch_queue_create("com.example.capturesession.videodata", DISPATCH_QUEUE_SERIAL);
        
        dispatch_set_target_queue(_videoDataOutputQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
        
        //오디오 큐 생성
        self.audioDataOutputQueue = dispatch_queue_create("com.example.capturesession.audiodata", DISPATCH_QUEUE_SERIAL);
        
        [self addDataOutputsToCaptureSession:self.captureSession];  //captureSession은 부모 클래스가 가지고 있다.
    }
     return self;
}




#pragma mark - Recording

//자식
- (void)startRecording
{
   
}

- (void)stopRecording
{
   
}

#pragma mark - Private methods

- (void)addDataOutputsToCaptureSession:(AVCaptureSession *)captureSession
{
    NSLog(@"IDCaptureSessionAssetWriterCoordinator - addDataOutputsToCaptureSession 진입 : 비디오, 오디오 아웃풋 객체 생성 + 설정");
    
    //비디오 객체 생성 + 설정
    self.videoDataOutput = [AVCaptureVideoDataOutput new];
    _videoDataOutput.videoSettings = nil;
    _videoDataOutput.alwaysDiscardsLateVideoFrames = NO;
    
    //아웃풋에 델리게이트 설정 + 비디오 데이터 아웃풋 큐  할당
    [_videoDataOutput setSampleBufferDelegate:self queue:_videoDataOutputQueue];
    
    //오디오 아웃풋 객체 생성 + 오디오 데이터 아웃풋 큐  할당
    self.audioDataOutput  = [AVCaptureAudioDataOutput new];
    [_audioDataOutput setSampleBufferDelegate:self queue:_audioDataOutputQueue];
    
    
    //캡쳐 세션에 비디오 갭쳐 아웃풋 넣기(이 클래스가 부모 클래스 capturesession 상속 받아서 capturesession 에서 진행됨)
    [self addOutput:_videoDataOutput toCaptureSession:self.captureSession];
    //비디오 커넥션 초기화
    _videoConnection = [_videoDataOutput connectionWithMediaType:AVMediaTypeVideo];
    
    
    //캡쳐 세션에 오디오 캡쳐 아웃풋 넣기
    [self addOutput:_audioDataOutput toCaptureSession:self.captureSession];
    //오디오 커넥터 초기화
    _audioConnection = [_audioDataOutput connectionWithMediaType:AVMediaTypeAudio];
    
    [self setCompressionSettings];
}



- (void)setupVideoPipelineWithInputFormatDescription:(CMFormatDescriptionRef)inputFormatDescription
{
    NSLog(@"IDCaptureSessionAssetWriterCoordinator - setupVideoPipelineWithInputFormatDescription 진입 :");
    self.outputVideoFormatDescription = inputFormatDescription;
}

- (void)teardownVideoPipeline
{
    NSLog(@"IDCaptureSessionAssetWriterCoordinator - teardownVideoPipeline 진입 :");
}


- (void)setCompressionSettings
{
    NSLog(@"IDCaptureSessionAssetWriterCoordinator - setCompressionSettings 진입 :");
    _videoCompressionSettings = [_videoDataOutput recommendedVideoSettingsForAssetWriterWithOutputFileType:AVFileTypeQuickTimeMovie];
    _audioCompressionSettings = [_audioDataOutput recommendedAudioSettingsForAssetWriterWithOutputFileType:AVFileTypeQuickTimeMovie];
}

#pragma mark - SampleBufferDelegate methods

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :");
    
    CMFormatDescriptionRef formatDescription =  CMSampleBufferGetFormatDescription(sampleBuffer);
    
    
    if (connection == _videoConnection) {
        NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection");
        
        if (self.outputVideoFormatDescription == nil) {
            NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription == nil");
            
            // Don't render the first sample buffer.
            // This gives us one frame interval (33ms at 30fps) for setupVideoPipelineWithInputFormatDescription: to complete.
            // Ideally this would be done asynchronously to ensure frames don't back up on slower devices.
            
            //TODO: outputVideoFormatDescription should be updated whenever video configuration is changed (frame rate, etc.)
            //Currently we don't use the outputVideoFormatDescription in IDAssetWriterRecoredSession
            
            /*
             
             첫 번째 샘플 버퍼를 렌더링하지 마십시오.
             이 작업을 완료하려면 setupVideoPipelineWithInputFormatDescription :에 대해 하나의 프레임 간격 (30fps에서 33ms)이 필요합니다.
             이상적으로 이것은 느린 장치에서 프레임이 백업되지 않도록 비동기 적으로 수행됩니다.
             
             TODO : 비디오 구성이 변경 될 때마다 outputVideoFormatDescription을 업데이트해야합니다 (프레임 속도 등)
             현재 우리는 IDAssetWriterRecoredSession에서 outputVideoFormatDescription을 사용하지 않습니다
             */
            
            [self setupVideoPipelineWithInputFormatDescription:formatDescription];
            
            
        }else{
            NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil");
            self.outputVideoFormatDescription = formatDescription;
            @synchronized(self){
                
                if(_recordingStatus == RecordingStatusRecording){
                    
                    NSLog(@"IDCaptureSessionAssetWriterCoordinator - [_assetWriterCoordinator appendAudioSampleBuffer:sampleBuffer]  호출 :");
                   // [_assetWriterCoordinator appendAudioSampleBuffer:sampleBuffer];
                }
                
            }
            
        }
        
        
    }else if(connection == _audioConnection){
        
       NSLog(@"IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection");
        self.outputAudioFormatDescription = formatDescription;
        @synchronized( self ) {
            if(_recordingStatus == RecordingStatusRecording){           //2 면
                [_assetWriterCoordinator appendAudioSampleBuffer:sampleBuffer];
            }
        }
        
    }
    
}

#pragma mark - IDAssetWriterCoordinatorDelegate methods

//녹화 시작 버튼 클릭했을때!!
- (void)writerCoordinatorDidFinishPreparing:(IDAssetWriterCoordinator *)coordinator
{
    NSLog(@"IDCaptureSessionAssetWriterCoordinator -  writerCoordinatorDidFinishPreparing 진입 :");
}

//오류발생 했을때!!
- (void)writerCoordinator:(IDAssetWriterCoordinator *)recorder didFailWithError:(NSError *)error
{
    NSLog(@"IDCaptureSessionAssetWriterCoordinator -  didFailWithError 진입 :");
}



// 정지 버튼 클릭되었을 때 호출
- (void)writerCoordinatorDidFinishRecording:(IDAssetWriterCoordinator *)coordinator
{
    NSLog(@"IDCaptureSessionAssetWriterCoordinator -  writerCoordinatorDidFinishRecording 진입 :");
}


#pragma mark - Recording State Machine
// call under @synchonized( self )
- (void)transitionToRecordingStatus:(RecordingStatus)newStatus error:(NSError *)error
{
    NSLog(@"IDCaptureSessionAssetWriterCoordinator - transitionToRecordingStatus 진입");
}



@end

IDAssetWriterCoordinator 


#import <Foundation/Foundation.h>
#import <CoreMedia/CoreMedia.h>
#import <AVFoundation/AVFoundation.h>


@protocol IDAssetWriterCoordinatorDelegate;

@interface IDAssetWriterCoordinator : NSObject


- (instancetype)initWithURL:(NSURL *)URL;

- (void)addVideoTrackWithSourceFormatDescription:(CMFormatDescriptionRef)formatDescription settings:(NSDictionary *)videoSettings;

- (void)addAudioTrackWithSourceFormatDescription:(CMFormatDescriptionRef)formatDescription settings:(NSDictionary *)audioSettings;

- (void)setDelegate:(id<IDAssetWriterCoordinatorDelegate>)delegate callbackQueue:(dispatch_queue_t)delegateCallbackQueue;

- (void)prepareToRecord;

- (void)appendVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer;

- (void)appendAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer;

- (void)finishRecording;

@end


@protocol IDAssetWriterCoordinatorDelegate <NSObject>

- (void)writerCoordinatorDidFinishPreparing:(IDAssetWriterCoordinator *)coordinator;
- (void)writerCoordinator:(IDAssetWriterCoordinator *)coordinator didFailWithError:(NSError *)error;
- (void)writerCoordinatorDidFinishRecording:(IDAssetWriterCoordinator *)coordinator;

@end

#import "IDAssetWriterCoordinator.h"

typedef NS_ENUM(NSInteger, WriterStatus){
    WriterStatusIdle = 0,
    WriterStatusPreparingToRecord,
    WriterStatusRecording,
    WriterStatusFinishingRecordingPart1,             // waiting for inflight buffers to be appended
    WriterStatusFinishingRecordingPart2,             // calling finish writing on the asset writer
    WriterStatusFinished,                                        // terminal state
    WriterStatusFailed                                             // terminal state
};                                                                              // internal state machine


@interface IDAssetWriterCoordinator()

@property (nonatomic, assign) WriterStatus status;                                      //NS_ENUM

@property (nonatomic) dispatch_queue_t writingQueue;
@property (nonatomic) dispatch_queue_t delegateCallbackQueue;
@property (nonatomic) NSURL *URL;

@property (nonatomic) AVAssetWriter *assetWriter;
@property (nonatomic) BOOL haveStartedSession;


//오디오 설명, 셋팅, 인풋
@property (nonatomic) CMFormatDescriptionRef audioTrackSourceFormatDescription;
@property (nonatomic) NSDictionary *audioTrackSettings;
@property (nonatomic) AVAssetWriterInput *audioInput;

//비디오 설명, 셋팅, 인풋
@property (nonatomic) CMFormatDescriptionRef videoTrackSourceFormatDescription;
@property (nonatomic) CGAffineTransform videoTrackTransform;
@property (nonatomic) NSDictionary *videoTrackSettings;
@property (nonatomic) AVAssetWriterInput *videoInput;

@end



@implementation IDAssetWriterCoordinator


-(instancetype)initWithURL:(NSURL *)URL
{
      NSLog(@"IDAssetWriterCoordinator - initWithURL 진입");
    self = [super init];
    if (self) {
        
    }
    return self;
}

- (void)addVideoTrackWithSourceFormatDescription:(CMFormatDescriptionRef)formatDescription settings:(NSDictionary *)videoSettings
{
     NSLog(@"IDAssetWriterCoordinator - addVideoTrackWithSourceFormatDescription 진입");
}

- (void)addAudioTrackWithSourceFormatDescription:(CMFormatDescriptionRef)formatDescription settings:(NSDictionary *)audioSettings
{
     NSLog(@"IDAssetWriterCoordinator - addAudioTrackWithSourceFormatDescription 진입");
}
- (void)setDelegate:(id<IDAssetWriterCoordinatorDelegate>)delegate callbackQueue:(dispatch_queue_t)delegateCallbackQueue
{
     NSLog(@"IDAssetWriterCoordinator - setDelegate <IDAssetWriterCoordinatorDelegate> 진입");
}

- (void)prepareToRecord
{
     NSLog(@"IDAssetWriterCoordinator - prepareToRecord 진입");
}

- (void)appendVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
     NSLog(@"IDAssetWriterCoordinator - appendVideoSampleBuffer 진입");
}

- (void)appendAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
     NSLog(@"IDAssetWriterCoordinator - appendAudioSampleBuffer 진입");
}

- (void)finishRecording
{
     NSLog(@"IDAssetWriterCoordinator - finishRecording 진입");
}



#pragma mark - Private methods -----------------------------------------------------------------------------
// audioInput 생성후, assetWriter에 넣어주기
- (BOOL)setupAssetWriterAudioInputWithSourceFormatDescription:(CMFormatDescriptionRef)audioFormatDescription settings:(NSDictionary *)audioSettings error:(NSError **)errorOut
{
    NSLog(@"IDAssetWriterCoordinator - setupAssetWriterAudioInputWithSourceFormatDescription 진입");
    
    return YES;
}



//video input 생성 , assetWriter에 videoInput 추가
- (BOOL)setupAssetWriterVideoInputWithSourceFormatDescription:(CMFormatDescriptionRef)videoFormatDescription transform:(CGAffineTransform)transform settings:(NSDictionary *)videoSettings error:(NSError **)errorOut
{
    NSLog(@"IDAssetWriterCoordinator - setupAssetWriterVideoInputWithSourceFormatDescription 진입");
    return YES;
}



- (NSDictionary *)fallbackVideoSettingsForSourceFormatDescription:(CMFormatDescriptionRef)videoFormatDescription
{
    NSLog(@"IDAssetWriterCoordinator - fallbackVideoSettingsForSourceFormatDescription 진입");
    return  nil;
    
}



//최종적으로 버퍼 더하기
- (void)appendSampleBuffer:(CMSampleBufferRef)sampleBuffer ofMediaType:(NSString *)mediaType
{
    NSLog(@"IDAssetWriterCoordinator - appendSampleBuffer 진입");
}



// call under @synchonized( self )
- (void)transitionToStatus:(WriterStatus)newStatus error:(NSError *)error
{
    NSLog(@"IDAssetWriterCoordinator - transitionToStatus 진입");
}


- (NSError *)cannotSetupInputError
{
    NSLog(@"IDAssetWriterCoordinator - cannotSetupInputError 진입");
    
    return nil;
}

@end

 

카메라 실행시켰을 때 로그

...더보기

2019-04-16 13:49:01.701875+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionPipelineViewController setupWithPipelineMode IDCaptureSessionAssetWriterCoordinator 객체 생성시작!

2019-04-16 13:49:01.701993+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionCoordinator init 진입

2019-04-16 13:49:01.702034+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionCoordinator setupCaptureSession 진입 

2019-04-16 13:49:01.706344+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionCoordinator addDefaultCameraInputToCaptureSession 진입

 

2019-04-16 13:49:01.719701+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionCoordinator addInput toCaptureSession 진입

 

2019-04-16 13:49:01.722364+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionCoordinator addInput toCaptureSession - 세션에 캡쳐디바이스인풋 들어갔어요!

 

2019-04-16 13:49:01.722407+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionCoordinator addDefaultMicInputToCaptureSession 진입

 

2019-04-16 13:49:01.731848+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionCoordinator addInput toCaptureSession 진입

 

2019-04-16 13:49:01.733323+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionCoordinator addInput toCaptureSession - 세션에 캡쳐디바이스인풋 들어갔어요!

 

2019-04-16 13:49:01.733360+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionAssetWriterCoordinator - init 진입

 

2019-04-16 13:49:01.733400+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionAssetWriterCoordinator - addDataOutputsToCaptureSession 진입 : 비디오, 오디오 아웃풋 객체 생성 + 설정

2019-04-16 13:49:01.733780+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionCoordinator addOutput  toCaptureSession 진입

 

2019-04-16 13:49:01.735467+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionCoordinator addOutput  toCaptureSession 진입

 

2019-04-16 13:49:01.736292+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionAssetWriterCoordinator - setCompressionSettings 진입 :

 

2019-04-16 13:49:01.737157+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionCoordinator setDelegate 진입 - 전역변수에 IDCaptureSessionCoordinatorDelegate 할당, delegateCallbackQueue 할당

 

2019-04-16 13:49:01.737205+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionPipelineViewController - configureInterface 호출 -  AVCaptureVideoPreviewLayer 객체 생성

 

2019-04-16 13:49:01.737225+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionCoordinator previewLayer 캡쳐 세션 넣어줌 초기화

 

2019-04-16 13:49:01.739046+0900 AssertWriterVideoRecorderNew[2591:505792] IDCaptureSessionCoordinator startRunning 진입 - dispatch_sync(세션큐, 캡쳐쎄션 스타트러닝!)

 

2019-04-16 13:49:02.041482+0900 AssertWriterVideoRecorderNew[2591:505846] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

 

2019-04-16 13:49:02.041546+0900 AssertWriterVideoRecorderNew[2591:505846] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

 

2019-04-16 13:49:02.041563+0900 AssertWriterVideoRecorderNew[2591:505846] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription == nil

 

2019-04-16 13:49:02.041582+0900 AssertWriterVideoRecorderNew[2591:505846] IDCaptureSessionAssetWriterCoordinator - setupVideoPipelineWithInputFormatDescription 진입 :

 

2019-04-16 13:49:02.042227+0900 AssertWriterVideoRecorderNew[2591:505847] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

 

2019-04-16 13:49:02.042250+0900 AssertWriterVideoRecorderNew[2591:505847] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

 

2019-04-16 13:49:02.042261+0900 AssertWriterVideoRecorderNew[2591:505847] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil

 

2019-04-16 13:49:02.078642+0900 AssertWriterVideoRecorderNew[2591:505845] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

 

2019-04-16 13:49:02.078711+0900 AssertWriterVideoRecorderNew[2591:505845] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

 

2019-04-16 13:49:02.078726+0900 AssertWriterVideoRecorderNew[2591:505845] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil

 

2019-04-16 13:49:02.109463+0900 AssertWriterVideoRecorderNew[2591:505847] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

 

2019-04-16 13:49:02.109554+0900 AssertWriterVideoRecorderNew[2591:505847] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

 

2019-04-16 13:49:02.111679+0900 AssertWriterVideoRecorderNew[2591:505844] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

 

2019-04-16 13:49:02.111712+0900 AssertWriterVideoRecorderNew[2591:505844] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  videoConnection

 

2019-04-16 13:49:02.111724+0900 AssertWriterVideoRecorderNew[2591:505844] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : outputVideoFormatDescription != nil

 

2019-04-16 13:49:02.161513+0900 AssertWriterVideoRecorderNew[2591:505844] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer 진입 :

 

2019-04-16 13:49:02.161828+0900 AssertWriterVideoRecorderNew[2591:505844] IDCaptureSessionAssetWriterCoordinator - didOutputSampleBuffer : connection =  audioConnection

 

 

예제파일

AssertWriterVideoRecorderNew.zip
0.09MB

 

Ios autoLayout 기초

장치가 세로일때 뷰 모양
장치가 가로일때 뷰모양

예제소스


#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
  
    
    //크기 고정 뷰 - 화면 정중앙에 배치될 정사각형의 뷰 만들기
    UIView *oneView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    oneView.translatesAutoresizingMaskIntoConstraints = NO;     //필수!
    [oneView setBackgroundColor:[UIColor brownColor]];
    [self.view addSubview:oneView];
    
    
  
    //*설명
    //constraintWithItem : 제약을 걸 객체
    //attribute : 제약 속성
    //relatedBy : 상대 객체와의 관계
    //toItem: 상대 뷰객체
    //attribute상대뷰 객체의 제약 속성

      //가로 길이 제약 조건
    NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:oneView
                                                                       attribute:NSLayoutAttributeWidth
                                                                       relatedBy:NSLayoutRelationEqual
                                                                          toItem:nil
                                                                       attribute:NSLayoutAttributeNotAnAttribute
                                                                      multiplier:1.f
                                                                        constant:50];
    //높이 제약 조건
    NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:oneView
                                                                        attribute:NSLayoutAttributeHeight
                                                                        relatedBy:NSLayoutRelationEqual
                                                                           toItem:nil
                                                                        attribute:NSLayoutAttributeNotAnAttribute
                                                                       multiplier:1.0
                                                                         constant:50];
    
    
    
    //oneView를 view 기준으로 view의 x 값 중앙에 위치.
    NSLayoutConstraint *centerX = [NSLayoutConstraint constraintWithItem:oneView
                                                               attribute:NSLayoutAttributeCenterX
                                                               relatedBy:NSLayoutRelationEqual
                                                                  toItem:self.view
                                                               attribute:NSLayoutAttributeCenterX
                                                              multiplier:1.0
                                                                constant:0];
    
    
    
    //oneView를 view 기준으로 view의 y 값 중앙에 위치.
    NSLayoutConstraint *centerY = [NSLayoutConstraint constraintWithItem:oneView
                                                               attribute:NSLayoutAttributeCenterY
                                                               relatedBy:NSLayoutRelationEqual
                                                                  toItem:self.view
                                                               attribute:NSLayoutAttributeCenterY
                                                              multiplier:1.0
                                                                constant:0];

    //크기 제약 조건 추가하기
    [oneView addConstraints:[NSArray arrayWithObjects:widthConstraint,heightConstraint,nil]];
    //위치 제약 조건 추가
    [self.view addConstraints:[NSArray arrayWithObjects:centerX,centerY,nil]];
    
    
    /*------------------------------------------------------------------------------------------------------------------*/
    
    //크기 유동 뷰 :  화면이 가로일때, 세로 일때 각각 다르게 변한다.
    UIView *twoView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    twoView.translatesAutoresizingMaskIntoConstraints = NO;
    [twoView setBackgroundColor:[UIColor yellowColor]];
    [self.view addSubview:twoView];
    
    
    //높이 50고정 : 높이는 변하지 않게 고정 ,  가로는 설정안해준다. 설정해주면 화면을 가로로했을때 가로길이가 바뀌지 않기 때문이다.
    NSLayoutConstraint *heightCons = [NSLayoutConstraint constraintWithItem:twoView
                                                                  attribute:NSLayoutAttributeHeight
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:nil
                                                                  attribute:NSLayoutAttributeNotAnAttribute
                                                                 multiplier:1.0
                                                                   constant:50];
    
    //twoView의 y 값은 view 중앙 y값
    NSLayoutConstraint *y = [NSLayoutConstraint constraintWithItem:twoView
                                                               attribute:NSLayoutAttributeTop
                                                               relatedBy:NSLayoutRelationEqual
                                                                  toItem:self.view
                                                               attribute:NSLayoutAttributeTop
                                                              multiplier:1.0
                                                                constant:30];
    
    
    
    
    //twoView의 왼쪽값은 view의 왼쪽에서 30 떨어지게
    NSLayoutConstraint *left = [NSLayoutConstraint constraintWithItem:twoView
                                                            attribute:NSLayoutAttributeLeading
                                                            relatedBy:NSLayoutRelationEqual
                                                               toItem:self.view
                                                            attribute:NSLayoutAttributeLeading
                                                           multiplier:1.0
                                                             constant:30];
    
     //twoView의 오른쪽값은 view의 오른쪽에서 30 떨어지게 (오른쪽은 self.view가 기준이 된다!!)
    NSLayoutConstraint *right = [NSLayoutConstraint constraintWithItem:self.view
                                                             attribute:NSLayoutAttributeTrailing
                                                             relatedBy:NSLayoutRelationEqual
                                                                toItem:twoView
                                                             attribute:NSLayoutAttributeTrailing
                                                            multiplier:1.0
                                                              constant:30];
    
    //높이 제약 조건 넣어주기.
    [twoView addConstraints:[NSArray arrayWithObjects:heightCons,nil]];
    //view에 y좌표, 왼쪽, 오른쪽 제약조건 넣어주기.
    [self.view addConstraints:[NSArray arrayWithObjects:y,left,right,nil]];
    
    
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

 

예제파일

AutoLayoutSampleCode.zip
0.06MB

ios 화면보호기 & 터치 감지하기

목표

iOS UIWindow에서 터치를 감지한 후 일정시간 터치가 없으면 화면보호기 viewcontroller 띄워주는 기능 구현

 

이 화면에서 3초이상 터치가 없으면 화면보호기를 띄워줄 것이다.

순서

*공통상수클래스 제작- CommonValues

설명: 화면보호기 시간을 설정해준다.

#import <Foundation/Foundation.h>

@interface CommonValues : NSObject

@property(nonatomic,assign) int SAVER_TIME;         //화면보호기 시간
@end

#import "CommonValues.h"

@implementation CommonValues
@synthesize SAVER_TIME;

- (instancetype)init
{
    self = [super init];
    if (self) {
        SAVER_TIME = 3;                                //화면보호기 설정 시간 - 3초 ;
    }
   return self;
}

@end

 

*화면보호기 클래스 제작- SViewController

설명 : 화면보호기 화면을 xib 파일로 생성한다. 그후, ui객체들을 SViewController 연결시킨다.

필요한 변수들을 생성한다. 버튼 IBAction 함수를 만들어서 버튼 or 삭제 버튼이 눌러질때 이벤트 처리를 해준다.

초기 비번은 0000. 맞으면 화면을 닫아주고, 틀리면 알림창을 띄워준다.(비밀번호 담는 변수 초기화)

비밀번호 확인 로직은 주석 참고.

화면보호기 ui


#import <UIKit/UIKit.h>

@interface SViewController : UIViewController

@end

#import "SViewController.h"

@interface SViewController ()

//숫자~ 삭제 버튼 맴버 변수
@property (weak, nonatomic) IBOutlet UIButton *one;
@property (weak, nonatomic) IBOutlet UIButton *two;
@property (weak, nonatomic) IBOutlet UIButton *three;
@property (weak, nonatomic) IBOutlet UIButton *four;
@property (weak, nonatomic) IBOutlet UIButton *five;
@property (weak, nonatomic) IBOutlet UIButton *six;
@property (weak, nonatomic) IBOutlet UIButton *seven;
@property (weak, nonatomic) IBOutlet UIButton *eight;
@property (weak, nonatomic) IBOutlet UIButton *nine;
@property (weak, nonatomic) IBOutlet UIButton *zero;
@property (weak, nonatomic) IBOutlet UIButton *deleteButton;

//비번
@property (weak, nonatomic) IBOutlet UITextField *textfield;
@property(nonatomic,strong)NSString *secretKey;
@end

@implementation SViewController

//세로 화면만 허용 하기
- (NSUInteger) supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait; //세로 화면만 허용
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //비번 초기화
    _secretKey = [[NSString alloc]init];
}



// 숫자키 ~ del 눌렀을때
- (IBAction)numBtnAction:(id)sender {
    
    //삭제키 눌렀을때
    if([[sender titleLabel].text isEqualToString:@"del"]){
        //비밀번호 개수가 한개 이상일때
        if ([_secretKey length] > 0) {
            //하나씩 지워 주기
            _secretKey = [_secretKey substringToIndex:[_secretKey length] - 1];
            
            self.textfield.text = _secretKey;
        } else {
            return;
        }
    }else{
        
        //숫자키 눌렀을 때 숫자하나씩더하기
        _secretKey = [_secretKey stringByAppendingString:[sender titleLabel].text];
        self.textfield.text = _secretKey;

        //비밀번호 개수가 4개 일때
        if (_secretKey.length > 3) {
            //비번체크 : 비번을 0000 라고 해준다.
            if([_secretKey isEqualToString:@"0000"]){
                
                //화면보호기 창 내려주기
                [self dismissViewControllerAnimated:YES completion:nil];
            }else{
                
                //비밀번호 틀림창 띄워주기
                UIAlertController * view=   [UIAlertController
                                                             alertControllerWithTitle:@"알림"
                                                             message:@"비밀번호를 확인해주세요"
                                                             preferredStyle:UIAlertControllerStyleActionSheet];
                
                UIAlertAction* ok = [UIAlertAction
                                                 actionWithTitle:@"확인"
                                                 style:UIAlertActionStyleDefault
                                                 handler:^(UIAlertAction * action)
                                                 {
                                                     [view dismissViewControllerAnimated:YES completion:nil];
                                                 }];
                
                [view addAction:ok];
                [self presentViewController:view animated:YES completion:^{
                    self.textfield.text = @"";
                    self->_secretKey = [[NSString alloc]init];
                }];
                
            }//
        }//
    }
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

*화면보호기를 띄울 클래스 제작- DetectTouchWindow

 화면 보호기를 컨트롤 하는 클래스는 UIWindow를 상속 받는다. NSTimer  객체변수와 UIWindow 객체 변수등 필요한 변수를 생성한다. 

idleTimerExceeded 함수: 타이머가 일정시간이 지났을때 호출되는 함수.화면보호기를 띄워준다.

resetIdleTimer 함수 : 터치가 인식되면 타이머를 계속 초기화 시켜준다. 터치가 안되면 타이머는 계속 진행된다.

sendEvent 함수: 터치 시작 or 터치 종료에 호출되는 함수


#import <UIKit/UIKit.h>
#import "SViewController.h"

@interface DetectTouchWindow : UIWindow                     //화면 보호기를 컨트롤 하는 클래스는 UIWindow를 상속 받는다.
@property (strong,nonatomic) NSTimer *idleTimer;         //타이머 객체
@property (strong, nonatomic) UIWindow *window;       //UIWindow 객체
@property (nonatomic,assign) int screenSaverTime;      //화면보호기가 나타날 시간(터치가 없고난 뒤 몇 시간뒤?)

@end
#import "DetectTouchWindow.h"
#import "CommonValues.h"
@implementation DetectTouchWindow
@synthesize idleTimer,screenSaverTime;



- (void)sendEvent:(UIEvent *)event {
    [super sendEvent:event];
    // 타이머 재설정 횟수를 줄이기 위해 시작 터치 또는 종료 터치에서만 타이머를 재설정.
    NSSet *allTouches = [event allTouches];
    
    // 눌렀을때 1, 누르고 손가락을 땔때 resetIdleTimer 를 호출한다.
    NSLog(@"sendEvent : %lu" , (unsigned long)[allTouches count]);
    if ([allTouches count] > 0) {
       
        //allTouches 수는 1로만 계산되므로 anyObject가 여기에서 작동합니다.
        UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase;
        if (phase == UITouchPhaseBegan || phase == UITouchPhaseEnded)
        [self resetIdleTimer];
    }
}

//터치가 되면 호출되는 함수 - 터치 중
- (void)resetIdleTimer {
    if (idleTimer) {
        NSLog(@"DetectTouchWindow - 터치가 감지되는 중...");
        [idleTimer invalidate]; //타이머 제거 = 타이머 초기화
    }
    
    //공통 상수 함수 - 화면보호기 시간
    CommonValues *cmVal = [[CommonValues alloc]init];
    screenSaverTime = cmVal.SAVER_TIME;
    //일정 시간뒤 화면보호기 창 띄우기 - 터치가 되면 계속 일정 시간으로 초기화 됨.
    idleTimer = [NSTimer scheduledTimerWithTimeInterval:screenSaverTime target:self selector:@selector(idleTimerExceeded) userInfo:nil repeats:NO] ;
}

- (void)idleTimerExceeded {
    NSLog(@"DetectTouchWindow - 화면 보호기 창을 띄웁니다.");
    
    //현재 띄워진 class 의 이름 - 특정 클래스 걸러내기
    UIViewController *curentViewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
    NSString *currentSelectedCViewController = NSStringFromClass([[((UINavigationController *)curentViewController) visibleViewController] class]);
    
    //사진촬영 중일때, 녹음중일때 제외하고 화면보호기 띄우기 - !
    if([currentSelectedCViewController isEqualToString:@"CaptureMedia"]){
         [self resetIdleTimer];     //화면 보호기 시간 계속 초기화(터치중임)
    }else if([currentSelectedCViewController isEqualToString:@"AudioRecorder"]){
         [self resetIdleTimer];
    }else if([currentSelectedCViewController isEqualToString:@"MediaView"]){
        [self resetIdleTimer];
    }else{

       
        //위의 클래스들이 아닐때 화면보호기 띄우기
        NSString *s_key = @"1234";
        if (s_key) {
            NSLog(@"화면보호기를 띄웁니다 ₩~~~~~~~  %@ " , s_key);
            
            //일정시간 이상 지나면 커스텀으로 만든 화면 보호기 띄워주기
            SViewController *sv = [[SViewController alloc]init];
           [[[UIApplication.sharedApplication keyWindow]rootViewController] presentViewController:sv animated:YES completion:nil];
       
            
//            UIViewController *activeController = [UIApplication sharedApplication].keyWindow.rootViewController;
//            if ([activeController isKindOfClass:[UINavigationController class]])
//                activeController = [(UINavigationController*) activeController visibleViewController];
//            else if (activeController.presentedViewController)
//                activeController = activeController.presentedViewController;
//            [activeController presentViewController:sv animated:YES completion:nil];

        }else{
            NSLog(@"화면보호기를 띄우지 않습니다~~~~~~~~~ %@ " , s_key);
        }

    }
   
}


@end

*앱 댈리게이트에 설정해주기 제작- AppDelegate

설명: 위에서 만든 클래스들을 객체로 만들어서 셋팅해준다. main 컨트롤러에 터치가 시작되면 DetectTouchWindow에서 반응을 한 뒤 카운터를 시작한다. 터치가 또 있으면 DetectTouchWindow의 sendEvent, idleTimerExceeded 함수가 호출 되면서 카운터가 초기화 된다. 일정 시간 터치가 없으면 타이머가 완료(초기화 x 정상종료)되면서 화면보호기가 뜬다. 

#import "AppDelegate.h"
#import "CommonValues.h"
#import "DetectTouchWindow.h"
#import "ViewController.h"
@interface AppDelegate ()
@property (strong,nonatomic) ViewController *vc;
@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    _window = [[DetectTouchWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    UIStoryboard *sboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    ViewController *vc = [sboard instantiateInitialViewController];
    
    UINavigationController *nc = [[UINavigationController alloc]initWithRootViewController:vc];
    
    [_window setBackgroundColor:[UIColor yellowColor]];
    [_window setRootViewController:nc];
    [_window makeKeyAndVisible];
    
    return YES;
}


@end

 

예제파일

DtectView.zip
0.08MB

날짜를 이용해서 파일 삭제하기

날짜를 이용해서 30일이 지난 폴더 또는 파일을 삭제하는 기능을 구현해 보자. 

먼저, 다큐먼트 폴더 안에 video, photo, audio 폴더가 각각있다고 가정하자. 위의 미디어 폴더 아래는 20190401, 20190402 와 같이 폴더 or 파일이름이 생성한 날짜인 파일들이 존재할 것이다.

참고로 다큐먼트 폴더는 사용자가 접근할 수 있는 최상위 폴더이다. 다큐먼트 이외의 폴더로는 애플 정책상(보안문제)접근이 어렵다.

필요한 데이터는 아래와 같다.

1.오늘날짜를 초를 변환한 값.

2.파일을 생성한 날짜를 초로 변환한 값

3.생성한 날짜에 30일(초값)을 더한 값. - 삭제예정일 (생성한 날짜를 초로 변환하고 30일을 초로 변환해서 계산 후 리턴 한다.)

위의 데이터를 구했으면 날짜값을 비교해서 파일을 삭제해준다. 

* 앱을 구동할때 날자를 계산해서 처리(AppDelegate.m)

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    NSLog(@"application - 앱실행!");
    
    //일정 시간 지난 미디어 파일 지우기
    [self deleteOverDateMediaFile];
    
    return YES;
}

//일정 시간 지난 미디어 파일 지우기
-(void)deleteOverDateMediaFile{
    
    NSLog(@"일정 시간 지난 미디어 파일 지우기 deleteOverDateMediaFile - appDelegate");
    float todaySeconds = [self getTodaySeconds];
    // video 폴더 안의 파일 확인 후 삭제,  audio 폴더 안의 파일 확인 후 삭제,  photo 폴더 안의 파일 확인 후 삭제,
    [self startDeleteMediaFile:@"/video" deleteStandardSeconds:todaySeconds ];
    [self startDeleteMediaFile:@"/audio" deleteStandardSeconds:todaySeconds ];
    [self startDeleteMediaFile:@"/photo" deleteStandardSeconds:todaySeconds ];
}

//비디오, 오디오, 사진 파일 삭제 :(오늘날짜를 초로변환 한 값을 전달해준다!)
-(void) startDeleteMediaFile :(NSString *) fileName  deleteStandardSeconds:(float) todaySeconds{
    NSLog(@"비디오, 오디오, 사진 파일 삭제 startDeleteMediaFile - appDelegate");
    //파일 매니져
    NSFileManager *fm = [NSFileManager defaultManager];
    //최종 문서  폴더
    NSArray *DocumentDrectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    //다큐먼트 폴더에서 제일 상위 폴더(video, audio, photo) 바로 아래 존재하는 폴더들(20190101, 20190202....)
    NSString *bigDir = [DocumentDrectory[0] stringByAppendingFormat:@"%@",fileName];
    
    //디렉토리 존재여부 확인
    if ([fm isWritableFileAtPath:bigDir] ) {
        NSLog(@" %@ Dir가 존재합니다." , bigDir);
    }else{
        NSLog(@" %@ Dir가 존재하지 않습니다. ㅜnㅜ 확인해주세요..." ,bigDir);
    }
    
    //폴더 안에 있는 날짜 폴더들을 배열에 담는다.
    NSError *err;
    NSArray *smallDateFileArray = [fm contentsOfDirectoryAtPath:bigDir error:&err];
    //해당 경로가 존재하면
    if (err == nil) {
        //날짜 폴더 개수별로 꺼내기
        for (int i = 0 ; i < (unsigned long)[smallDateFileArray count]; i++) {
            
            //삭제할 폴더의 전체 경로 - 아래쪽에서 삭제해줄때 사용됨.
            NSString *folderPathToDelete = [bigDir stringByAppendingFormat:@"/%@/",[smallDateFileArray objectAtIndex:i] ];
            
            //String 폴더명 가져와서 날짜 타입으로 바꾸고 초로 바꿔주기
            NSString *dateFileNameString =   [smallDateFileArray objectAtIndex:i];  //폴더의이름(가공전)
            NSDateFormatter *fileDateFormatter = [[NSDateFormatter alloc] init];   //날짜포멧
            [fileDateFormatter setDateFormat:@"yyyyMMdd"];                                  //날짜포멧
            NSDate *fileNameDate = [fileDateFormatter dateFromString:dateFileNameString];   //폴더이름 가공(NSDate)
            NSString *fileNameDateString = [fileDateFormatter stringFromDate:fileNameDate]; //폴더이름 가공(NSString)
            NSLog(@"파일이름(날짜) : %@" , fileNameDateString);
            NSTimeInterval fileNameToSeconds = [fileNameDate timeIntervalSinceReferenceDate];//초로변환
            NSLog(@"파일이름(초) : %f" , fileNameToSeconds);  //57 5737200  4월 1일(촬영일)
            
                                                                                                //57 6000000  4월 2일(오늘)
            
            
            //위의 폴더의 초에서 30일에 해당하는 초값을 계산해서 리턴
            float afterLongDaySec = [self delDateString:fileNameDateString];
            NSLog(@"afterLongDaySec(30일 이후 날짜) 초 : %f" , afterLongDaySec);
            //57 8329216  4월 40일(삭제기준일)
            
            
            // 오늘날짜초 >= 삭제예정날짜초
            // 오늘날짜와 비교해서 해당폴더의 30일 이후의 날짜값이 오늘날짜값 보다 작으면 .
            if (todaySeconds >= afterLongDaySec) {
                
                NSError *deleteErr;
                //해당 폴더 삭제!!
                [[NSFileManager defaultManager] removeItemAtPath:folderPathToDelete error:&deleteErr];
                
                if (deleteErr != nil) {
                    NSLog(@"이전 파일 삭제 중 오류가 발생했습니다.");
                    return;
                }else{
                    NSLog(@"삭제 성공!");
                    NSLog(@"%@ 폴더는 특정시간이 지난 파일이기 때문에 삭제 했습니다." , dateFileNameString);
                    NSLog(@"삭제 경로 : %@ " , folderPathToDelete);
                }
            }
        } // for - end
    }    // if - end
}


//해당 날짜에  30일(초값)을 더해서  초값으로 리턴
- (float) delDateString:(NSString *)date{
    int milsec = 3600 * 24 * 30;                    //30일
    //String 폴더명 가져와서 날짜 타입으로 바꾸고 숫자로 바꿔주기
    NSDateFormatter *fileDateFormatter = [[NSDateFormatter alloc] init];
    [fileDateFormatter setDateFormat:@"yyyyMMdd"];
    NSDate *fileNameDate = [fileDateFormatter dateFromString:date];
    
    //30일 이후의 날짜
    NSDate *delDate = [fileNameDate dateByAddingTimeInterval:milsec];
    
    //날짜 포멧형식으로 변경
    NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
    [formatter setDateFormat:@"yyyyMMdd"];
    NSString *delDateString = [formatter stringFromDate:delDate];
    NSLog(@"파일의 30일 이후 날짜 : %@" , delDateString);
    NSDate *dateDate = [formatter dateFromString:delDateString];
    NSTimeInterval seconds = [dateDate timeIntervalSinceReferenceDate];
    NSLog(@"파일의 30일 이후 날짜 초 : %f" , seconds);
    return seconds;
}

//오늘 날짜 초값으로 리턴
-(float)getTodaySeconds{
    //오늘 날짜
    NSDate *today = [NSDate date];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.dateFormat = @"yyyyMMdd";
    NSString *dateString = [dateFormatter stringFromDate:today];
    NSLog(@"오늘 날짜 : %@" , dateString);
    NSDate *dateDate = [dateFormatter dateFromString:dateString];
    NSTimeInterval seconds = [dateDate timeIntervalSinceReferenceDate];
    NSLog(@"오늘 날짜 초로 변환 : %f" , seconds);
    return seconds;
}

 

*파일

DeleteFileFromDate.zip
/ 0.06MB

+ Recent posts