나중에 또 같은 삽질을 하지 않기 위해 내가 시도해봤던 여러가지 방법을 다 기록해두기로 했다.


문제 상황


- UITableView 가 특정 상황에 multipleSelection 활성화가 된다.

- UITableView 의 각 셀 + UITableView 자체도 여러가지 GestureRecognizer 가 켜켜이 쌓여있다. (탭, 롱프레스 등등)


→ select 동작이 원활하지 않은 문제가 발생했다. touch 를 하면 셀렉트 되지 않고 꾸우욱 눌러야 didHighlight 로 겨우 들어가는.. 그런 상태



해결법 #1


gesture recognizer 들의 cancelsTouchesInView 옵션을 false 로 한다.

gestureRecognizer.cancelsTouchesInView = false


위 옵션은 제스처가 인식될 때 해당 터치 이벤트가 뷰에도 전달되는지와 관련 있는 옵션이다.


A Boolean value affecting whether touches are delivered to a view when a gesture is recognized.


출처: 애플 공식 가이드


근데 내 경우에는 이 옵션만으로는 해결되지 않았다.




해결법 #2


gesture recognizer 들에 아래 함수를 구현한 delegate 를 설정해준다.

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return /* true of false */
}


Gesture Recognizer 가 터치를 받아야 할지 말아야 할지를 반환하는 함수인데, false 를 반환하는 경우에는 gesture recognizer 가 터치 이벤트를 처리하지 않는다(?)

표현이 적합한지 모르겠어서 여기에도 문서를 첨부하자면


Ask the delegate if a gesture recognizer should receive an object representing a touch.


출처: 애플 공식 가이드


근데 이걸로도 해결되지 않았다 ㅎㅎ





해결법 #3


UITableView 의 delaysContentTouches 옵션을 false 로 한다

tableView.delaysContentTouches = false


이 옵션은 UIScrollView 하위의 옵션인데, 스크롤을 위해 터치 다운 액션에 대한 처리를 지연할 것인지 여부를 결정하는 옵션이다.


A Boolean value that determines whether the scroll view delays the handling of touch-down gestures.


출처: 애플 공식 가이드


결과적으로 이 설정을 통해 select 지연은 해결되었지만, 스크롤을 하려 해도 스크롤을 시작할 때 터치한 셀이 선택되는 문제가 생긴다.

더 좋은 방법은 없을지 더 알아봐야 할 것 같다.



부제는 

Fragment 에서 호출한 startActivityForResult 의 결과값이 Fragment 의 onActivityResult 로 안 와요 ㅠㅠ 


오늘도 질문 받아주다가 새로운 지식을 얻었다.



일단, 이전 버전의 fragment 는 잘 모르겠고

andorid.support.v4.app.Fragment 를 이용했다면 분명, Fragment에서도 startActivityForResult가 잘 동작해야 한다.


그런데 이게 fragment 내에 정의된 onActivityResult로 안 온다?! 옴마나?!


싶을 때는 해당 fragment의 상위 activity 코드를 열어보자.


만약, Activity에 onActivityResult method가 override 되어있다면 (사실 만약이 아니고 무조건 그럴 거임)

해당 onActivityResult 코드에 super.onActivityResult 가 빠져있는지 확인해보자. (아마 빠져있을 거임 ㅎㅎ)



알아보니 startActivityResult의 결과는,

먼저 Activity 의 onActivityResult 를 거쳐서 fragment 의 onActivityResult 로 넘어오는 것 같다. (노확실 영어못함)

그리고 그 fragment로 넘겨주는 부분이 Activity 의 onActivityResult 에 정의되어 있는 듯.


그러니 fragment 의 상위 activity 가 onActivityResult를 아예 override 하고 있지 않거나, super.onActivityResult 를 제대로 호출해주고 있다면

fragment 까지 잘 넘어가겟지만,

그렇지 않다면 아예 fragment 의 onActivityResult로 넘어갈 기회조차 잃는 것..



method override 하면서 super.~ 를 지울 때에는 막 지우지말고 역할이 뭔지 판단 후에 지우는 습관을 들이는 것이 좋을 것 같다.


는 나도 잘 못하지만 ㅎㅎㅎ

[MQTT / Mosca] Mqtt Packet 에 Command Type 추가하기 (for authentication) #1

[MQTT / Mosca] Mqtt Packet 에 Command Type 추가하기 (for authentication) #2

[MQTT / Mosca] Mqtt Packet 에 Command Type 추가하기 (for authentication) #3

[MQTT / Mosca] Mqtt Packet 에 Command Type 추가하기 (for authentication) #4


경축축 이제 진짜 드디어 마지막 글이다!!


이제 Command Type 을 추가하는 게 끝났다.


하지만 거기까지 해서는


Authentication 을 위한 Packet 흐름


1. Connect Packet (Device -> Server)

2. Authenticate Packet (Server -> Device)

3. Authenticate Packet (Device -> Server)

4. Connect ACK Packet (Server -> Device)

에서의 네번째 단계가 아마 안 될 거다...





#Connect ACK Packet 받기


세번째 글에서 stream.pipe 란 애를 요주의라고 했었다.


그니까!! 

3단계까지 무사히 마쳤으니

Server 에서 authenticate packet 을 받아서 처리할 수 있는 상태가 되었다.


그래서 처리를 해주고, authentication 이 완료되면 Connect ACK Packet 을 device 에 보내줘야한다.

그래야 비로소 connection 이 완료되니까!


근데!!!!!! 지금 상태에선 Device 가 그놈의 Connect ACK Packet 을 못 받는다!!!!!


대체 왜인지 알기 위해 진짜 디버깅을 엄청 했다

Wire shark 도 켜서 막 패킷 캡쳐해보고... 별짓을 다했는데

확실해진 건 Server -> Device 로 가는 Connect ACK Packet 이 확실히 보내졌다는 거다.


그럼 문제가 뭘까?

Device 쪽 코드에서 받아서 뭔가 처리를 하는데 생긴 문제일텐데,

stream 에서 데이터를 아예 못받나? 싶어서

client 코드 들어가서 

this.stream.on('data', function(data) {
  console.log(data);
});


해봤더니 넘나 잘 나옴!!

심지어 Wire shark 에서 캡쳐한 Packet 이랑 byte array 도 일치한다 ㅠㅠ


그럼 뭐가 문제일까...

하다보니 문득 위의 this.stream 이 readable stream 이란 것과,

실제로 받은 packet 의 처리는 stream.pipe 에 destination 으로 설정됐던 writable stream 에서 한다는 생각이 스쳐갔다.



그럼 왜인지는 몰라도 저 pipe 가 한번만 동작하고 있는 건 아닐까?

싶어서 코드를 수정해봤다

/*
writable._write = function (buf, enc, done) {
  completeParse = done;
  parser.parse(buf);
  process();
};

this.stream.pipe(writable);
*/

this.stream.on('data', function(buf) {
  completeParse = function(err) {
    if (err) {
      console.log(err);
      return;
    }
  };
  parser.parse(buf);
  process();
});


원래 있던 pipe 관련 코드들을 다 주석처리하고

저렇게 직접 readable stream 에서 읽은 data buffer 를 처리하도록 해줬더니.............

세상에 넘나 잘 된다


대체 왜 pipe 가 한번만 동작하고 막힌 건지는 모르겠다.

어딘가에서 막힌 것 같은데 그것까지 찾아볼 생각도 없고...

기나긴 삽질에 지쳐 그럴 자신도 없다 ㅠㅠ




그래도 이제 authentication 을 위한 모든 준비가 끝났다!

암호화만 하면 된다. ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ



나말고 읽을 사람이 있을지 의문이지만

혹시 이 대장정을 다 읽으셨다면


수고하셨습니다!!

[MQTT / Mosca] Mqtt Packet 에 Command Type 추가하기 (for authentication) #1

[MQTT / Mosca] Mqtt Packet 에 Command Type 추가하기 (for authentication) #2

[MQTT / Mosca] Mqtt Packet 에 Command Type 추가하기 (for authentication) #3



흐흐흐 드디어 네번째 글이다

정말 정리하기 넘 힘들군..


그래도 이번엔 드디어 3단계로 넘어간다!!

경축경축!!!



#MQTT Packet Command 추가하기



Authentication 을 위한 Packet 흐름


1. Connect Packet (Device -> Server)

2. Authenticate Packet (Server -> Device)

3. Authenticate Packet (Device -> Server)

4. Connect ACK Packet (Server -> Device)


크으으 이제 무려 3단계다


2단계에서 워낙 삽질을 많이 해서

3단계는 좀 수월하게 할 수 있다.



일단 2단계에서의 접근과 동일하게,

이번엔 Device 에서 Packet 을 generate 하는 부분을 봐야 되니까!

Device 쪽 mqtt 라이브러리를 봐야한다.



#5. mqtt (send)


내가 원하는 건

publish 할 때 처럼!!

그냥 client.authenticate(~) 해서 authenticate 요청을 날려버리는 거다!


그러니 publish function 이 어떻게 정의되어 있는지부터 한번 보자.


(mqtt/lib/client.js)

/**
 * publish - publish <message> to <topic>
 *
 * @param {String} topic - topic to publish to
 * @param {String, Buffer} message - message to publish
 * @param {Object} [opts] - publish options, includes:
 *    {Number} qos - qos level to publish on
 *    {Boolean} retain - whether or not to retain the message
 * @param {Function} [callback] - function(err){}
 *    called when publish succeeds or fails
 * @returns {MqttClient} this - for chaining
 * @api public
 *
 * @example client.publish('topic', 'message');
 * @example
 *     client.publish('topic', 'message', {qos: 1, retain: true});
 * @example client.publish('topic', 'message', console.log);
 */
MqttClient.prototype.publish = function (topic, message, opts, callback) {
  var packet;

  // .publish(topic, payload, cb);
  if ('function' === typeof opts) {
    callback = opts;
    opts = null;
  }

  // Default opts
  if (!opts) {
    opts = {qos: 0, retain: false};
  }

  if (this._checkDisconnecting(callback)) {
    return this;
  }

  packet = {
    cmd: 'publish',
    topic: topic,
    payload: message,
    qos: opts.qos,
    retain: opts.retain,
    messageId: this._nextId()
  };

  switch (opts.qos) {
    case 1:
    case 2:

      // Add to callbacks
      this.outgoing[packet.messageId] = callback || nop;
      this._sendPacket(packet);
      break;
    default:
      this._sendPacket(packet, callback);
      break;
  }

  return this;
};


안녕? publish function 아!

나는 너를 복붙해다가, 겁나 고쳐댈거야!


말 그대로다.

그냥 얘 복붙해서 맘에 들도록 짜깁기하면 된다.

2편에서 generate 할 때 그랬던 것처럼!



짜깁기 하여 완성된 코드▼

MqttClient.prototype.authenticate = function (clientId, username, secretKey, callback) {
  var packet;

  if (this._checkDisconnecting(callback)) {
    return this;
  }

  packet = {
    cmd: 'authenticate',
    keepalive: 1,
    qos: 0,
    retain: false,
    clientId: clientId,
    username: username,
    password: secretKey
  };

  this._sendPacket(packet, callback);

  return this;
};

비교적 매우 심플하다.

왜냐면 별 처리를 안 했거든..


근데 여기서는 packet에 들어갈 정보들만 명시해준 다음,

_sendPacket 이란 function 으로 넘겨준다.


그럼 저 function 이 뭘 하는 앤지 정도는 살펴봐야겠지?

저기서 막 또 cmd case 별로 나눠서 뭔 짓 해주면 어떡해..


보러 가자!

/**
 * _sendPacket - send or queue a packet
 * @param {String} type - packet type (see `protocol`)
 * @param {Object} packet - packet options
 * @param {Function} cb - callback when the packet is sent
 * @api private
 */
MqttClient.prototype._sendPacket = function (packet, cb) {
  if (!this.connected) {
    if (((packet.qos || 0) === 0 && this.queueQoSZero) || packet.cmd !== 'publish') {
      this.queue.push({ packet: packet, cb: cb })
    } else if (packet.qos > 0) {
      this.outgoingStore.put(packet, function (err) {
        if (err) {
          return cb && cb(err)
        }
      })
    } else if (cb) {
      cb(new Error('No connection to broker'))
    }

    return
  }

  // When sending a packet, reschedule the ping timer
  this._shiftPingInterval()

  if (packet.cmd !== 'publish') {
    sendPacket(this, packet, cb)
    return
  }

  switch (packet.qos) {
    case 2:
    case 1:
      storeAndSend(this, packet, cb)
      break
    /**
     * no need of case here since it will be caught by default
     * and jshint comply that before default it must be a break
     * anyway it will result in -1 evaluation
     */
    case 0:
      /* falls through */
    default:
      sendPacket(this, packet, cb)
      break
  }
}


거봐 확인 안 했으면 어쩔뻔 했어


나는 authenticate packet 을 client 가 connect 되기 전에 요청한다.

근데 저 function 에 따르면, connected 상태에 따라 처리해주는 부분이 있어버려서,

제대로 sendPacket 까지 도달하지 못하는 것 같다. (실제로 이 function 을 처리 안 해주고 그냥 돌리면 에러가 난다. 테스트 해봄)



이 코드를 수정하는 데에는 여러가지 방법이 있겠지만,

코드 하나하나 뜯어보고 무슨 의미인지 알아내기엔 이미 삽질을 너무 많이 해서

그냥 가장 간단한 방법으로 고쳤다.

/**
 * _sendPacket - send or queue a packet
 * @param {String} type - packet type (see `protocol`)
 * @param {Object} packet - packet options
 * @param {Function} cb - callback when the packet is sent
 * @api private
 */
MqttClient.prototype._sendPacket = function (packet, cb) {
  if (!this.connected && packet.cmd !== 'authenticate') {
    if (0 < packet.qos || 'publish' !== packet.cmd || this.queueQoSZero) {
      this.queue.push({ packet: packet, cb: cb });
    } else if (cb) {
      cb(new Error('No connection to broker'));
    }

    return;
  }

  // When sending a packet, reschedule the ping timer
  this._shiftPingInterval();

  switch (packet.qos) {
    case 2:
    case 1:
      storeAndSend(this, packet, cb);
      break;
    /**
     * no need of case here since it will be caught by default
     * and jshint comply that before default it must be a break
     * anyway it will result in -1 evaluation
     */
    case 0:
      /* falls through */
    default:
      sendPacket(this, packet, cb);
      break;
  }
};

다른 그림찾기 넘 고난이도일까봐 bold 처리 했다.


저렇게 그냥 단순히 cmd가 authenticate일 경우에는 connected 가 false 여도 다음 로직으로 넘어가도록 해버렸다.



이제 여기까지 하면!!

거의 다 됐는데!!!



이전편처럼 project directory 가 구분되어 있는 사람들은

Deivce project 쪽 mqtt-packet library 에서도 authenticate type packet generate 하는 부분을 다시 추가해줘야 한다.


근데 나만 그런 건지는 모르겠는데,

npm install mosca 로 설치된 Server project 쪽 mqtt-packet library

npm install mqtt 로 설치된 Device project 쪽 mqtt-packet library 가 

코드 구성이 조금 상이하다.


주의할 것.. 아마 버전 차이인 것 같다.


npm install mqtt 로 설치된 mqtt-packet library 에서는 

generate 하는 코드를 mqtt-packet/writeToStream.js 파일에서 찾을 수 있다.



방식이 좀 다르긴 한데

stream 이란 두번째 parameter 가 추가되었다.


그래도 이전에 했던 것처럼 connect 복붙해서 적당히 수정하면 된다.

진짜 똑같이 수정하면 됨!! 괜히 이상한 부분만 안 건드리면..


대신 진짜진짜 주의할 부분이 있다!!!!

나도 원래는 같은 project directory 에서 Server, Device process 둘 다 돌리면서 테스트 하고 분리했었는데,

이걸 안 건드려서 한참 삽질을 또 했다.


(Device side mqtt-packet/constants.js)

/* Authenticate */
protocol.AUTHENTICATE_HEADER = Buffer.from([protocol.codes['authenticate'] << protocol.CMD_SHIFT])

저 파일을 열고 밑으로 스크롤 죽죽 내려보면

HEADER 를 generate 해서 명시해두는 부분이 있는데,

이게 Server side 에서 쓰는 mqtt-packet library 버전이랑 달라서 ㅠㅠ

Server 에서는 그냥 connect function 적당히 수정하면 HEADER parsing 도 같이 처리됐었는데

여기선 따로 저렇게 constants.js 파일에서 관리한다.

꼭꼭 명시해줘야됨..


저걸 모르고 그냥 돌렸더니 세상에

authenticate 형식으로 잘 파싱해놓고

보낼 때 connect로 보내버린다.


저 코드를 constants.js 에 추가해줬다면

다시 writeToStream.js 파일로 돌아와서 복붙해서 짜깁기해둔 authenticate parsing function 에다가

  // Generate header
  stream.write(protocol.AUTHENTICATE_HEADER)

라고 수정해주면 된다.




여기까지하면 device에서 authenticate packet 을 보내는 것도 잘 된다!



Server 에서 저 authenticate packet 을 parsing 해서 받는 부분은

Device 에서 받을 때와 동일한 파일들을 동일하게 수정해주면 된다.

다행히 이 부분은 버전별 차이가 없다 ㅠㅠ 다행






길고 긴 여정이 끝났다!


이렇게 하면 

Mqtt Packet 에 Command Type 추가하기 완료!!!



흐하항행복해

이제 추가한 타입을 멋대로 가져다 쓰면 된다

이벤트도 받고 패킷도 쏘고~~~ 앗싸링!!


아 근데 글 하나 더 있다. ㅎ

[MQTT / Mosca] Mqtt Packet 에 Command Type 추가하기 (for authentication) #1

[MQTT / Mosca] Mqtt Packet 에 Command Type 추가하기 (for authentication) #2



하잇!

아 이어서 정리하려니까 넘 힘들다

언젠가는 내가 이걸 다시 보고 공부를 할까?

왜 자꾸 부질 없는 것 같지... 흑흑


그래도 내가 공부한 거 정리하는 거니까 마저 열씨미 해야징 ㅠㅠ





#MQTT Packet Command 추가하기



Authentication 을 위한 Packet 흐름


1. Connect Packet (Device -> Server)

2. Authenticate Packet (Server -> Device)

3. Authenticate Packet (Device -> Server)

4. Connect ACK Packet (Server -> Device)


2단계를 하고 있었다.

저번 글에서는 #4. mqtt-packet (generate) 에서 모듈 열어보고, authenticate type packet 을 generate 하는 코드 추가해주고 테스트까지 해봤다.


그럼 2단계인 Server -> Device 에서

"Server -" 정도 까지 된 거다.

남은 "> Device" 부분을 이 글에서 볼 거고!



이제 Server 측에서 authenticate type packet 을 보내는 걸 했으니!

Device 에서 받아야한다. 

해당 Packet 을 제대로 받으려면, Authenticate type 을 parsing 할 수 있어야겠지?


Device 쪽 코드에서는 mqtt library 를 쓴다.

그러니 이번에는 mqtt library를 열어볼 거야



#5. mqtt (receive)


Device 에서는 mqtt library를 이용해 client 를 생성하고,Server 에 Connect 한다.


그리고 client 에다 관련 event들을 handling 하는 callback function 들을 달아주는데,

우리는 authenticate type 의 packet 에 대한 event 도 받아야 한다!

그러려면 일단 event를 emit 하는 부분을 찾아봐야 하지 않을까?!


(mqtt/lib/client.js)

MqttClient.prototype._handleConnack = function (packet) {
  var rc = packet.returnCode,
    // TODO: move to protocol
    errors = [
      '',
      'Unacceptable protocol version',
      'Identifier rejected',
      'Server unavailable',
      'Bad username or password',
      'Not authorized'
    ];

  clearTimeout(this.connackTimer);

  if (0 === rc) {
    this.reconnecting = false;
    this.emit('connect', packet);
  } else if (0 < rc) {
    this.emit('error',
        new Error('Connection refused: ' + errors[rc]));
  }
};

그래서 evnet를 emit 하는 부분을 찾아보았다!

저거 말고도 많은데, 일단 connect event 를 emit 하는 부분을 찾아봤더니, 저렇게 되어있었다.


그럼 다음 수순은 뭘까?

저 function 을 호출하는 부분을 찾아야겠지!?


(mqtt/lib/client.js)

MqttClient.prototype._handlePacket = function (packet, done) {
  this.emit('packetreceive', packet);

  switch (packet.cmd) {
    case 'publish':
      this._handlePublish(packet, done);
      break;
    case 'authenticate':
      this.emit('authenticate', packet);
      break;
    case 'puback':
    case 'pubrec':
    case 'pubcomp':
    case 'suback':
    case 'unsuback':
      this._handleAck(packet);
      done();
      break;
    case 'pubrel':
      this._handlePubrel(packet, done);
      break;
    case 'connack':
      this._handleConnack(packet);
      done();
      break;
    case 'pingresp':
      this._handlePingresp(packet);
      done();
      break;
    default:
      // do nothing
      // maybe we should do an error handling
      // or just log it
      break;
  }
};


짜잔!


이제 packet type 에 따라 event를 발생시켜주는 부분을 알았으니,

저기다 authenticate event 를 emit 하는 코드를 추가해주면

client 에서 authenticate event 를 handling 할 수 있을 거야!!


그래서 authenticate case 를 추가해줬다.


이럼 끝일까?


젠젠,, ㅠ


Packet Parsing 이 남았다.

기껏 authenticate packet 을 전송하는 거랑, 해당 event 를 처리할 수 있도록 준비까지 다 마쳐놨으니!

제대로 authenticate packet 타입을 처리해줘야 한다.


parsing 을 어디서 하는지 알아보자.


일단 저 _handlePacket function이 packet 이란 parameter 를 받는 것으로 보아,

어딘가에서 packet을 parsing 해서 _handlePacket function 을 호출해주겠지?



(mqtt/lib/client.js) 요 파일 많이많이 본다

function process () {
  var packet = packets.shift(),
    done = completeParse;
  if (packet) {
    that._handlePacket(packet, process);
  } else {
    completeParse = null;
    done();
  }
}


여기다!

근데 packet 을 packets 라는 변수에서 꺼내오는 걸 보니

packet 은 이 function 이 호출되기 전에 이미 처리되어 packets 에 저장되는 것 같다.

그럼 또 이 process function 이 호출되는 곳을 봐야한다.



(mqtt/lib/client.js)

writable._write = function (buf, enc, done) {
  console.log(buf)
  completeParse = done;
  parser.parse(buf);
  process();
};

this.stream.pipe(writable);

요기 있지!!


여길 보면 stream.pipe 란 애가 있는데, 요주의..

아마 나중에 또 등장할 거다. 한 마지막편쯤에..


어쨌든 저 pipe 란 애의 원래 역할은, 

readable stream 에 pipe 로 writable stream 을 연결해주면,

readable stream 이 읽어들인 data buffer 를 writable stream 으로 토스해준다.

그리고 그 buffer 를 처리해주는 부분이 writable._write 로 정의된 저 function 인 것 같다.

자세한 건 document 참조


여튼 그렇게 읽어들인 buffer 를 처리하는 부분에

내가 찾던 키워드가 있다!


parser.parse(buf)...


저 parser 는 또 뭘까 해서 보니

2편에 나왔던 그 아이다

mqtt-packet..




#6. mqtt-packet (parse)


그럼 다시 봐야지 뭐..


(mqtt-packet/parser.js)

Parser.prototype._parsePayload = function () {
  var result = false

  // Do we have a payload? Do we have enough data to complete the payload?
  // PINGs have no payload
  if (this.packet.length === 0 || this._list.length >= this.packet.length) {

    this._pos = 0

    switch (this.packet.cmd) {
      case 'connect':
        this._parseConnect()
        break
      case 'authenticate':
        this._parseAuth()
        break
      case 'connack':
        this._parseConnack()
        break
      case 'publish':
        this._parsePublish()
        break
      case 'puback':
      case 'pubrec':
      case 'pubrel':
      case 'pubcomp':
        this._parseMessageId()
        break
      case 'subscribe':
        this._parseSubscribe()
        break
      case 'suback':
        this._parseSuback()
        break
      case 'unsubscribe':
        this._parseUnsubscribe()
        break
      case 'unsuback':
        this._parseUnsuback()
        break
      case 'pingreq':
      case 'pingresp':
      case 'disconnect':
        // these are empty, nothing to do
        break
      default:
        this._emitError(new Error('not supported'))
    }

    result = true
  }

  return result
}

흐흐 나 코드 따라가는 것 좀 잘 하는 것 같다.


여튼 여기 등장! 빠밤

그럼 여기다 또 언제나 그래왔듯 authenticate case 를 추가해주면 된다.


근데 또 못보던 function 이 은근슬쩍 등장했다.

그렇다. 만들어줘야한다.

진짜 개귀찮다. 내가 왜 이 짓을 시작했지

역시 코딩은 삽질의 연속이다

인정? 어인정~ 동의? 어 보감~ 동휘? 어 보검~~~~~~~~~~~~~~~



(mqtt-packet/parser.js)

Parser.prototype._parseAuth = function() {
  var protocolId // constants id
    , clientId // Client id
    , password // Password
    , username // Username
    , flags = {}
    , packet = this.packet

  // Parse constants id
  protocolId = this._parseString()
  if (protocolId === null)
    return this._emitError(new Error('cannot parse protocol id'))

  if (protocolId != 'MQTT' && protocolId != 'MQIsdp') {

    return this._emitError(new Error('invalid protocol id'))
  }

  packet.protocolId = protocolId

  // Parse constants version number
  if(this._pos >= this._list.length)
    return this._emitError(new Error('packet too short'))

  packet.protocolVersion = this._list.readUInt8(this._pos)

  if(packet.protocolVersion != 3 && packet.protocolVersion != 4) {

    return this._emitError(new Error('invalid protocol version'))
  }

  this._pos++
  if(this._pos >= this._list.length)
    return this._emitError(new Error('packet too short'))

  // Parse connect flags
  flags.username  = (this._list.readUInt8(this._pos) & constants.USERNAME_MASK)
  flags.password  = (this._list.readUInt8(this._pos) & constants.PASSWORD_MASK)

  packet.clean = (this._list.readUInt8(this._pos) & constants.CLEAN_SESSION_MASK) !== 0
  this._pos++

  // Parse keepalive
  packet.keepalive = this._parseNum()
  if(packet.keepalive === -1)
    return this._emitError(new Error('packet too short'))

  // Parse client ID
  clientId = this._parseString()
  if(clientId === null)
    return this._emitError(new Error('packet too short'))
  packet.clientId = clientId

  // Parse username
  if (flags.username) {
    username = this._parseString()
    if(username === null)
      return this._emitError(new Error('cannot parse username'))
    packet.username = username
  }

  // Parse password
  if(flags.password) {
    password = this._parseBuffer()
    if(password === null)
      return this._emitError(new Error('cannot parse username'))
    packet.password = password
  }

  return packet
}


이번에도 역시 _parseConnect 를 참고했다.

참고하되 저번에 만들어둔 Authenticate Generate Function 을 꼭 고려하면서 짜깁기해야한다!

그때 넣었던 데이터들 잘 참고해서 고쳐줘야함!


요까지 해주면 device 에서 authenticate packet 을 받는 것도 매우 잘 된다!


아래는 받는 코드

client.on('authenticate', function(packet) {
  console.log(packet);
});


아 근데 주의할 점이 있다.


혹여나 (가 아니고 대개 그렇겠지만)

Server process 를 돌리는 project directory 와

Device process 를 돌리는 project directory 가 다르다면

각각 node_modules directory 에서 적합한 library 를 수정해줘야 하는데,


지금까지 한 Authenticate Packet (Server -> Device) 단계에서

Server / Device 둘 모두에서 고쳐줘야 하는 부분이 있다.


constants.js !!


authenticate 타입을

Server 에선 추가해줬는데 Device 에서 안 해줬거나

혹은 그 반대의 상황이어도

보내거나 받는 게 제대로 안 된다.


꼭 확인!


ㅠㅠ 다음편엔 3단계로 넘어갈 예정.

정리 넘 힘들당 

너무 길어질 것 같아서 잘랐다.


[MQTT / Mosca] Mqtt Packet 에 Command Type 추가하기 (for authentication) #1



요기서 Connect 를 받지 않은 채로 Server - Device 간 통신을 어찌할지에 대해 언급만 하고 끝났다.


사실 더 많은 삽질을 했었는데, (Mosca, mqtt 라이브러리의 stream에 직접 접근한다거나..)

아무래도 라이브러리 내부적으로 처리하는 것들이 많다보니, 그렇게 직접 접근해서 데이터를 갖다 박아버리는 방식은 적합하지 않은 것 같았다.

가는 건 되는데 오는 게 안 된다거나, 보내고 받고 보냈는데 마지막 받는 걸 못한다거나..


그래서 라이브러리 자체 로직을 따르기로 했다.

Mqtt packet 을 generating, parsing 하는 부분 코드를 참조해서 Authentication 을 위한 Packet Type 을 만들어버리는 방법으로!




#MQTT Packet Command 추가하기


Authentication 을 위한 Packet 흐름이


1. Connect Packet (Device -> Server)

2. Authenticate Packet (Server -> Device)

3. Authenticate Packet (Device -> Server)

4. Connect ACK Packet (Server -> Device)


니까!!

2 단계인 Server -> Device 부터 보자.



#1. Mosca Authenticate


Mosca 에서 제공하는 Authentication 기능과 적절히 섞어서 사용할 거기 때문에,

Mosca 의 authenticate function 을 먼저 봐야 한다.


var authenticate = function(client, username, password, callback) {
  if (/* authentication success */)
    callback(null, true);
  else /* authentication failed */
    callback(null, false);
};


Mosca 의 기본 authentication 기능을 이용하려면, 위와 같이 authenticate function 을 내 입맛대로 정의해준 뒤에

Mosca Server setup 단계에서 아래와 같이 설정해주면 된다.


function setup() {
  server.authenticate = authenticate;
  /* authorizePublish, authorizeSubscribe 도 필요하면 셋팅해주면 된다. */
};

server.on('ready', setup);

당연한 얘기지만 setup function 을 server.on 에다 바로 정의해도 된다.


여튼 Mosca 의 Authentication 은 저렇게 쓰는 건데, 우린 저 단계에서 callback 으로 success parameter 를 넘기기 전에 device 와 통신을 해야한다.



그러기 위해서는 일단 Mosca 에서 Mqtt Packet 을 어떤 식으로 보내는지부터 봐야한다.




#2. Mosca authenticate function callback


Packet 전송을 어디서 어떻게 하는지 해당 코드를 찾는 가장 확실한 방법은

현재로선 위 authenticate function 에서 호출하도록 되어있는 callback 을 보는 것이다.

true, false 에 따라 Connect ACK Packet 을 보낼지 말지 결정할 거 아냐?

그래서 따라가봤다.


(mosca/lib/client.js)

that.server.authenticate(this, packet.username, packet.password,
                           function(err, verdict) {

    if (err) {
      logger.info({ username: packet.username }, "authentication error");
      client.connack({
        returnCode: 4
      });
      client.stream.end();
      return;
    }

    if (!verdict) {
      logger.info({ username: packet.username }, "authentication denied");
      client.connack({
        returnCode: 5
      });
      client.stream.end();
      return;
    }

    that.keepalive = packet.keepalive;
    that.will = packet.will;

    that.clean = packet.clean;

    if (that.id in that.server.clients){
      that.server.clients[that.id].close(completeConnection, "new connection request");
    } else {
      completeConnection();
    }
  });


요것이 해당코드!

저길 보면 client.connack(~) 하는 부분이 실제 Connect ACK Packet 을 전송하는 부분이라 볼 수 있다. (이름부터가.. ConnectACK -> CONNACK)


엥 근데 client? 

하고 client 가 뭔지 찾아올라가봤더니,

대체 mosca 에서 왜 이런 네이밍을 한 건지 모르겠지만, 


var that = this, logger, client = this.connection;

이란 부분이 있다.


그렇다면 저 connection 이란 애가 실제 Packet 전송을 담당한다는 건데.

쟤가 바로 'mqtt-connection' 이라는 module 이다.




#3. mqtt-connection


그럼 이제 이 모듈도 봐야 돼..

node_modules 에 있는 mqtt-connection directory 를 보면

mqtt-connection/connection.js 라는 파일이 있다.


걔를 열고 좀 내려보면

['connect',
  'connack',
  'publish',
  'puback',
  'pubrec',
  'pubrel',
  'pubcomp',
  'subscribe',
  'suback',
  'unsubscribe',
  'unsuback',
  'pingreq',
  'pingresp',
  'disconnect'].forEach(function(cmd) {
    Connection.prototype[cmd] = function(opts, cb) {
      opts = opts || {}
      opts.cmd = cmd;

      // flush the buffer if needed
      // UGLY hack, we should listen for the 'drain' event
      // and start writing again, but this works too
      this.write(opts)
      if (cb)
        setImmediate(cb)
    }
  });

이런 코드가 있는데,

저기 줄줄이 나열된 애들이 mqtt protocol 에서 원래 쓰는? command 들이다!


각 command 마다 

Connection.prototype.connect

뭐 이런 식으로 function 을 만들어주는 코드인데,

우리는 authenticate 를 쓸 거니까 저 배열에 은근슬쩍 'authenticate' 를 끼워주면 된다.


['connect',
  'connack',
  'publish',
  'puback',
  'pubrec',
  'pubrel',
  'pubcomp',
  'subscribe',
  'suback',
  'unsubscribe',
  'unsuback',
  'pingreq',
  'pingresp',
  'disconnect',
  'authenticate'].forEach(function(cmd) {
    Connection.prototype[cmd] = function(opts, cb) {
      opts = opts || {}
      opts.cmd = cmd;

      // flush the buffer if needed
      // UGLY hack, we should listen for the 'drain' event
      // and start writing again, but this works too
      this.write(opts)
      if (cb)
        setImmediate(cb)
    }
  });

이렇게!! 거의 다른그림찾기 수준,, ㅎㅎ


여튼 이렇게 해두고

지금은 [2. Authenticate Packet (Server -> Device)] 를 보고 있는 거니까!

서버에서 보내는 상황!


그럼 authenticate type 에 맞게 MQTT Packet 을 generate 해줘야겠지?



(mqtt-connection/lib/generateStream.js)

var through   = require('through2')
  , generate  = require('mqtt-packet').generate
  , empty     = new Buffer(0)

function generateStream() {
  var stream  = through.obj(process)

  function process(chunk, enc, cb) {
    var packet = empty;

    try {
      packet = generate(chunk)
    } catch(err) {
      this.emit('error', err)
      return;
    }

    this.push(packet)
    cb()
  }

  return stream
}

module.exports = generateStream;


응?

generateStream.js 를 보니,,

얘 정작 Packet generate 하는 데에는 또 다른 모듈을 쓴다!! ㅠㅠ

봐야할 것 ++..


그럼 이번엔 mqtt-packet 이란 모듈을 한번 보자.




#4. mqtt-packet (generate)


요 라이브러리도 코드를 열어보면, 딱 앗 얘가 generate 하는 애구나!! 싶은 파일이 있다. (이름이 generate.js 거든..)

근데 generate.js 를 보기 전에!!

파일 목록을 찬찬히 보다보면 거슬리는 놈이 하나 있다.


constants.js!!


이름부터가 뭔가 상수관리를 할 것 같은, 아주 중요해보이는 아이니까

얘를 먼저 한번 보자.

/* Command code => mnemonic */
module.exports.types = {
  0: 'reserved' /* -> authenticate */,
  1: 'connect',
  2: 'connack',
  3: 'publish',
  4: 'puback',
  5: 'pubrec',
  6: 'pubrel',
  7: 'pubcomp',
  8: 'subscribe',
  9: 'suback',
  10: 'unsubscribe',
  11: 'unsuback',
  12: 'pingreq',
  13: 'pingresp',
  14: 'disconnect',
  15: 'reserved'
};

호엑 역시 중요한 칭구였어

Packet Command 관련 상수들이 관리되는 파일인 것 같다.


1~14 까지는 다 원래 중요하게 쓰이는 Command 들이니 건드리지 말고

아래 위로 껴있는 reserved 중 하나를 authenticate 로 바꿔보자.

사실 reserved 가 무슨 역할인지는 아직 모른다.


0, 15 둘 중 아무거나 바꿔도 된다. 둘 다 테스트 해봤는데 잘 됨!

근데 신기한 건, 모든 과정을 끝내고 테스트하면서 Packet Capture를 떠보면, Authenticate Packet 도 Reserve 로 잡힌다.

MQTT Protocol 약속인가봐...

약속된 값을 건드렸는데도 문제가 없음에 감사하며 ㅠㅠ



constants.js 를 무사히 수정했다면 다시 원래 목적인 generate.js 를 보자.



(mqtt-packet/generate.js)

function generate(packet) {

  switch (packet.cmd) {
    case 'connect':
      return connect(packet)
    case 'authenticate':
      return authenticate(packet)
    case 'connack':
      return connack(packet)
    case 'publish':
      return publish(packet)
    case 'puback':
    case 'pubrec':
    case 'pubrel':
    case 'pubcomp':
    case 'unsuback':
      return confirmation(packet)
    case 'subscribe':
      return subscribe(packet)
    case 'suback':
      return suback(packet)
    case 'unsubscribe':
      return unsubscribe(packet)
    case 'pingreq':
    case 'pingresp':
    case 'disconnect':
      return emptyPacket(packet)
    default:
      throw new Error('unknown command')
  }
}

일단 파일을 열자마자 7번째 줄에 이런 코드가 있을 거다.

저기서 case 문들 중 아무 곳에나 'authenticate' case 를 끼워넣자.

나는 connect 다음에다 끼워넣었다.


근데 내가 적은 코드를 자세히 보니

return authenticate(packet)??

저게 원래 있던 function일까?


그럴리가.. 직접 추가해줘야한다.


이미 라이브러리에 포함되어 있는

다른 관련 function 코드를 참고해서 만들면 된다.


나는 username과 password를 포함한다는 점에서,

connect 와 비슷한 점이 많은 것 같아 connect function 을 복붙해서 고쳤다.


필요한 정보는 남기고, 불필요한 정보는 지우고,

필요한지 불필요한지 잘모르겠는 건 일단 남기고 ㅎㅎㅎㅎ


그렇게 짜깁기로 완성한 authenticate function code ▼

function authenticate(opts) {
  var opts = opts || {}
    , cmd = opts.cmd
    , protocolId = opts.protocolId || 'MQTT'
    , protocolVersion = opts.protocolVersion || 4
    , clean = opts.clean
    , keepalive = opts.keepalive || 0
    , clientId = opts.clientId || ""
    , username = opts.username
    , password = opts.password

  if (clean === undefined) {
    clean = true
  }

  var length = 0

  // Must be a string and non-falsy
  if (!protocolId ||
     (typeof protocolId !== "string" && !Buffer.isBuffer(protocolId))) {
    throw new Error('Invalid protocol id')
  } else {
    length += protocolId.length + 2
  }

  // Must be a 1 byte number
  if (!protocolVersion ||
      'number' !== typeof protocolVersion ||
      protocolVersion > 255 ||
      protocolVersion < 0) {

    throw new Error('Invalid protocol version')
  } else {
    length += 1
  }

  // ClientId might be omitted in 3.1.1, but only if cleanSession is set to 1
  if ((typeof clientId === "string" || Buffer.isBuffer(clientId)) &&
     (clientId || protocolVersion == 4) &&
     (clientId || clean)) {

    length += clientId.length + 2
  } else {

    if(protocolVersion < 4) {

      throw new Error('clientId must be supplied before 3.1.1');
    }

    if(clean == 0) {

      throw new Error('clientId must be given if cleanSession set to 0');
    }
  }

  // Must be a two byte number
  if ('number' !== typeof keepalive ||
      keepalive < 0 ||
      keepalive > 65535) {
    throw new Error('Invalid keepalive')
  } else {
    length += 2
  }

  // Connect flags
  length += 1

  // Username
  if (username) {
    if (username.length) {
      length += Buffer.byteLength(username) + 2
    } else {
      throw new Error('Invalid username')
    }
  }

  // Password
  if (password) {
    if (password.length) {
      length += byteLength(password) + 2
    } else {
      throw new Error('Invalid password')
    }
  }

  var buffer = new Buffer(1 + calcLengthLength(length) + length)
    , pos = 0

  // Generate header
  buffer.writeUInt8(protocol.codes[cmd] << protocol.CMD_SHIFT, pos++, true)

  // Generate length
  pos += writeLength(buffer, pos, length)

  // Generate protocol ID
  pos += writeStringOrBuffer(buffer, pos, protocolId)
  buffer.writeUInt8(protocolVersion, pos++, true)

  // Connect flags
  var flags = 0
  flags |= username ? protocol.USERNAME_MASK : 0
  flags |= password ? protocol.PASSWORD_MASK : 0
  flags |= clean ? protocol.CLEAN_SESSION_MASK : 0

  buffer.writeUInt8(flags, pos++, true)

  // Keepalive
  pos += writeNumber(buffer, pos, keepalive)

  // Client ID
  pos += writeStringOrBuffer(buffer, pos, clientId)

  // Username and password
  if (username)
    pos += writeStringOrBuffer(buffer, pos, username)

  if (password)
    pos += writeStringOrBuffer(buffer, pos, password)

  return buffer
}


이제 여기까지 했으면

authenticate type packet 을 generate 하는 것부터, 

mqtt-connection 라이브러리에 해당 타입(authenticate)의 패킷을 전송하는 function도 추가가 되었다.



이제 보내기만 하면 되지!!

var secretKey = +new Date();

client.connection.authenticate({
  clientId: client.id,
  username: "",
  password: secretKey.toString()
});

이게 실제로 보내는 코드다.

필요한대로 활용하면 된다.

나는 이 코드를 authenticate function 안에 넣어뒀다.


으으 이제 Device 에서 authenticate type Packet 을 받는 부분을 손봐야 한다.


원래 Server -> Device 부분을 한 글에 끝내버리려고 했는데...........

넘 길어져버려서 이쯤에서 끊어야 할 것 같다

여기서 더 쓰면 나중에 내가 다시 보기 힘들 듯 ㅎㅎ


안녕!!

Error:Execution failed for task ':app:mergeDebugResources'. > Error: Some file crunching failed, see logs for details

라는 에러가 떴다.


see logs for details 라길래 확인해봤더니

어쩌구저쩌구내나인패치경로~~ 9.png malformed. 라고 나와있었다.


구글링을 해봤더니

YOUR_SDK_PATH/tools/draw9patch 라는 실행파일을 실행해서

저 프로그램으로 해당 나인패치 파일을 열었다가 다시 저장하면 된단다.


잘됨..


이딴 오류는 왜 있는 거야 대체 

오늘의 삽질!

이라기보다 지난 한달간의 기록에 가까운 것 같다.

정말 삽질에 삽질에 삽질을 거듭해서 겨우 MQTT Packet에 Command Type을 하나 추가했다.

휴.. 라이브러리를 어찌나 뜯어고쳤던지




#MQTT란?


MQTT 란 텔레메트리 장치, 모바일 기기에 최적화된 라이트 메시징 프로토콜로서
더 다양한 앱과 서비스의 등장으로 HTTP등의 기존 프로토콜만으로는 커뮤니케이션의 다양한 요구사항을 수용할 수 없게되었고,
제한된 통신 환경을 고려하여 디자인된 MQTT 프로토콜은 모바일 영역의 진화에 따라 최적의 프로토콜로 주목받고 있습니다.


뭐.. 그렇다고 한다.

사실 나한테는 내가 써야하는 프로토콜! 외의 큰 의미는 없었다.

내가 그 프로토콜 타입까지 샅샅이 뒤져봐야 할 줄 몰랐을 때까진.. 그랬다.


"제한된 통신 환경을 고려하여 디자인된" 이라서 그런가. 

iOT Device 와 양방향 통신을 하기에 꽤나 적합한 것 같다.

그래서 많이들 추천했겠지.


사실 나는 쓰기 간단하길래 택했다.

이렇게 될줄 몰랐지 나는..




#MQTT에서의 Authentication, Authorization (with Mosca)


MQTT는 꽤나 간단한 구조로 고려된 프로토콜이라, 따로 Authentication이나 Authorization을 위한 기능이 없다.

connect시 username / password 는 포함할 수 있던데.. 뭔지 모르겠다.


여튼 그래서 Mosca를 썼다. authenticate랑 authorization을 제공한다지 뭐야!


근데 Mosca 에서 제공하는 authentication은 username/password 방식으로 구현되어 있다.

이게 우리가 보통 생각하는 서비스 계정 로그인 같은 경우에야 적합하겠지만,

iOT device를 managing 하기에는 적합하지 않다.


사람이야 자기 password를 숨길 수도 있고, 어쩌다 유출당하면 바꿔버리면 그만이지만,

device는 공장에서 찍혀나간 후에는 password 역할을 할 코드를 유출당해버리면 아주,, 어마어마한 보안상의 문제가 되어버리기 때문에!

저 password 자체를 connect 시에 그냥 보내버리면 문제가 될 수 있다.

추가적인 인증 로직이 필요하단 거..


내가 지금 있는 곳에서는 보안을 중요시한 iOT Device Management System 을 만들어라! 하는 플젝을 하고 있기 때문에

Security... 특히 Device Authentication / Authorization 은 매우 중요하다.

그게 내가 Mosca 에서 제공하는 username / password 기반의 Authentication 기능을 그대로 가져다 쓸 수 없었던 이유고,

이 삽질을 시작한 이유.




#How to authenticate a iOS device


그럼 어떻게 인증하지?


기본적으로 두가지 가정을 한다.


1. 각 Device는 본인의 식별번호와, 비밀번호 역할을 할 Secret Number 를 가지고 있다.

2. 모든 Device 정보는 출고시에 Management System DB에 식별번호, Secret Number 를 포함하여 registration 된다.


위의 두가지가 전제될 때,

각 Device 는 Server 에 mqtt connection 을 요청하면서 

자신의 식별번호와 Secret Number 를 username / password 처럼 담아 보낼거다.


근데 그냥 보내면 안 되니까!

내가 의도했던 인증 로직은 아래와 같다.


1. Device 에서 server 에 connect 요청을 한다. 요청시에는 Secret Number를 제외한 식별번호만 username 에 담아 보낸다.


2. Server 에서 connect 요청을 받으면, 일단 username 에 담겨 온 식별번호가 유효한 식별번호인지 DB를 통해 확인한다.


3. 유효한 식별번호라 판단되면 Secret key 역할을 할 임의의 String을 Device 로 보낸다.


4. Device 는 Server 로부터 받은 String을 통해 자신의 Secret Number를 암호화하고, 

    username (식별번호) 와 함께 단방향 암호화된 Secret Number를 password 에 담아 다시 Server 로 보낸다.


5. Server 는 해당 식별번호의 Device 로 보냈던 Secret Key 를 통해 전달받은 password 를 복호화하여, 

    DB에 있는 해당 Device 의 Secret Number 와 일치하는지 확인한다.

    -> 큰일날 소리였다. 비번을 복호화해서 확인하는 인간이 어딨대. 다 생각해놓고 흥분해서 막판에 멍청한 짓을 했다.

         Server 에서도 Secret Key 와 password 를 단방향 암호화하여 Device 가 보낸 암호화 스트링과 일치하는지 확인!


6. 일치하면 Authentication 완료! 불일치하면 Connection Failed!


후,, 완벽해..


일단 이걸 하려면 Server 에서 Connection 요청을 받지 않은 상태에서 Server - Device 간 통신 (패킷 교환) 이 이루어져야 한다.

근데 세상에,, Mosca 에서 제공하는 authentication 방식으로 그걸 어찌 하지???


Big-삽질의 시작이었다.

아!!!!!!!!!!!!!

넘 화난당!!!!!!!!!!!!!!!!!!!


TestFlight 연동을 해야하는데,

App store 에 build 올리는 거에 대해 정리된 문서를 보다가 

영어 문장을 너무 대충 읽어서


"니 앱이 적절한 Bundle ID를 가지고 있고, Team ID가 잘 셋팅되었는지, 그리고 Code Signing Identity 란에 automatic distribution certificates 를 설정해뒀는지 확인해라"

라고 적혀있는데

적절하게 섞어보는 바람에


"블라블라~ Code Signing Identity 란에 Team ID가 잘 셋팅되었는지 봐라" 로 보는 바람에,,


이 부분을,, 수정해버렸었다


원래는 

Debug / Any iOS SDK 는 둘 다 iOS Developer

Release / Any iOS SDK 는 둘 다 iOS Distribution 이어야 한다고 되어있다.



그래서 저 부분을 잘못해서 Team ID로 셋팅해주고 돌렷더니

conflicting provisioning settings 어쩌구~~

니 Code Signing은 Automatic 하게 잡는 게 어떠하니 어쩌구~~ 거려가지고


헉 하고 다시 문서 확인해보니 iOS Distribution 으로 셋팅된 게 맞는지 확인해보라고.. 여서...

다시 셋팅 바꾸고 Archive 를 돌렷는데!!!!!!!!!!


나는 분명 셋팅을 바꿨는데 이자식이 자꾸 똑같은 에러를 뱉음 ㅠㅠㅠㅠㅠㅠㅠㅠㅠ

Clean 도 해봤는데..........




넘 빡쳐서 또 구글링을 해보았더니

갓-택오버플로우에서 그거 엑코 버그라고.. 해결법을 알려줬다.



저기 보이는 Automatically manage signing 을 체크 해제했다가 다시 체크하고, Team 선택도 다시 해주면 된다!!



근데 웃긴게 저러고 나면 Archive도 잘 되고 다 좋은데


Release 하위의 Any iOS SDK 가 iOS Developer 로 바뀐다?!


아직 왜인지 모르겠다.

어쨌든 잘 되니 된 거 아님...........? ㅎㅎ


다음에는 TestFlight 연동하는 걸 정리해서 올려봐야겠다.

한글로 된 글들은 전부다 예에에엣날 글들이라 ㅠㅠ 흑흑잉

'Programming > iOS / Swift' 카테고리의 다른 글

[Bug/Issue] UITableView Select 먹히는 문제  (0) 2018.11.21
Swift 컨벤션 검사 - Swiftlint  (0) 2017.09.18

후배가 도와달라고.. colorpicker 라이브러리 추가하다가 에러가 떴는데 왜인지 모르겠다고 해서 봤다.


아 Failed to resolve 어쩌구~ 뜨면 젤 머리아프다.


이번엔 또 어떤 땡깡을 부리나 싶어서 봤는데

내 최근 플젝 SDK version이 25.~ 였어서 아마 접할 일 없었던 에러인 것 같다.


Setting up Gradle for api 26 (Android)


요기서 도움을 많이 받았는데

요약하면 (영어 못함 해석 틀렸을 가능성 ++++++++)


26.0.0 버전부터는 support libarary 에 google 의 maven storage가 적용되어서,

일일이 support tool? 들을 설치해주지 않아도 가져다 쓸 수 있다고 한다.


그 google maven storage 를 어찌 적용하나 하면


project build.gradle (top level build.gradle file) 에서 


allprojects {

repositories {

jcenter()

}

}

라는 부분이 있는데!!


이 부분을 고쳐주면 된다. 고친다기보다 코드 추가!


gradle version 3부터는 google() 이란 게 추가되어서


#1. gradle version >= 3

allprojects {

repositories {

jcenter()

google()

}

}

이렇게만 해주면 되고



gradle version 3 미만인 경우


#2. gradle version < 3

allprojects {

repositories {

jcenter()

maven { url "https://maven.google.com" }

}

}

이렇게 해주면 됨!!





주의할 점은..


절대,, jcenter() 위에 maven 블럭을 쓰지 말라는 거..


쓴다고 바로 빨간줄이 뜬다거나 하면 차라리 나을텐데

그냥 효력이 없는 것 같다.


나랑 내 후배는 저 maven 태그도 잘 써져있고 google maven repository 주소도 잘 적어뒀는데 왜 계속 같은 에러가 나지?! 했었는데

jcenter() 가 맨 밑에 있었다..

올려주니 넘나 잘 됨 흑흑




역시 삽질하며 배우는 거죠


근데 왜 colorpicker 라이브러리를 추가하기 전에는 잘 됏던 걸까?

maven block 이 효과없었던 건 라이브러리 추가 전이나 추가 후나 매한가지일텐데 왜지..


미스테리다. 알아봐야 알 수 있겠지만 이거 검색하고 있기엔 할 일이 넘나 많은 것~

+ Recent posts