socket.on callback repeat multiple times in socket.io swift - swift

I'm using pod 'Socket.IO-Client-Swift', '~> 14.0.0'
here is my socket manager file
I don't know why socketGetLatestMsg() callback repeat multiple times when I send message once this call one but after second message callback repeat twice after 3 msg callback repeat thrice and so on...
and go back to user lists inbox and come back to same user all repeated message show as it is but in database repeated message not save.
class SocketIOManager: NSObject {
static let shared = SocketIOManager(chatID: nil, token: nil);
var socket: SocketIOClient?
var manager: SocketManager?
init(chatID: Int?, token: String?) {
super.init();
if chatID != nil && token != nil {
manager = SocketManager(socketURL: URL(string: SOCKET_URL)!, config: [.log(true), .compress, .connectParams(["auth_token": token!, "user_id": chatID!])])
self.socket = manager!.defaultSocket;
} else if UserDefaults.standard.value(forKey: "userID") != nil && UserDefaults.standard.value(forKey: "authToken") != nil {
manager = SocketManager(socketURL: URL(string: SOCKET_URL)!, config: [.log(true), .compress, .connectParams(["auth_token": UserDefaults.standard.value(forKey: "authToken") as! String, "user_id": UserDefaults.standard.value(forKey: "userID") as! Int])])
self.socket = manager!.defaultSocket;
}
self.socket?.connect();
}
//MARK:- All Emit
func socketInboxEmit(userID: Int?,dictData: [String: Any]?) {
if dictData != nil {
self.socket?.on(clientEvent: .connect, callback: { (data, ack) in
self.socket?.emit("chatroom.inbox", dictData!);
self.getInboxList(chatID: userID);
print("get Inbox socket List");
})
}
}
func socketGetUsrMsgEmit(chatListID: Int?, userID: Int?, dictData: [String: Any]?) {
if chatListID != nil && dictData != nil && userID != nil {
self.socket?.emit("chatroom.messages", dictData!);
self.socketGetAllMsg(userID: userID, chatListID: chatListID!);
}
}
func socketSaveMsg(chatListID: Int, dictData: [String: Any]?) {
//self.socket?.on(clientEvent: .connect, callback: { (data, ack) in
self.socket?.emit("save_message", dictData!);
self.socketGetLatestMsg(chatListID: chatListID);
print("socket connected");
//})
}
func contactListEmit(dictData: [String: Any]?) {
self.socket?.emit("chatroom.friends", dictData!);
}
//MARK:- Get All Data
func socketCallBack(chatID: Int?, token: String?, completionHandler: #escaping (String, [String: Any]?) -> Void) {
if chatID != nil {
completionHandler("Fail", nil)
}
else {
print("chat id not match or nil");
}
}
func socketGetLatestMsg(chatListID: Int?) {
socket?.on("chatroom.\(chatListID!)", callback: { (data, ack) in
for filterData in data {
if ((filterData as? [String: Any]) != nil) {
ChatVM.sharedInstance.appendNewMsg(responseDict: filterData as! JSONDictionary)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "tblReload"), object: nil, userInfo: ["table": "reload"])
} else {
print("Ok")
}
}
})
}
func socketGetAllMsg(userID: Int?, chatListID: Int) {
self.socket?.on("chat.\(userID!).room.\(chatListID)", callback: { (data, ack) in
for filterData in data {
if ((filterData as? [String: Any]) != nil) {
ChatVM.sharedInstance.parseChatListData(responseDict: filterData as! JSONDictionary)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "tblReload"), object: nil, userInfo: ["table": "reload"])
} else {
print("Ok")
}
}
})
}
func getInboxList(chatID: Int?) {
socket?.on("user.\(chatID!).chatinbox", callback: { (data, ack) in
for filterData in data {
if ((filterData as? [String: Any]) != nil) {
ChatVM.sharedInstance.parseInboxListData(responseDict: filterData as! JSONDictionary);
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "tblInboxReload"), object: nil);
//completionHandler("Success", filterData as? [String : Any])
} else {
print("Ok")
//completionHandler("Fail", nil)
}
}
})
}
func getContactList(userID: Int?) {
socket?.on("user.\(userID!).friends", callback: { (data, ack) in
for filterData in data {
if ((filterData as? [String: Any]) != nil) {
ChatVM.sharedInstance.parseFriendListData(responseDict: filterData as! JSONDictionary)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "tblPendingReload"), object: nil, userInfo: filterData as! [AnyHashable : Any])
//completionHandler("Success", filterData as? [String : Any])
} else {
print("Ok")
//completionHandler("Fail", nil)
}
}
})
}
func closeSocket() {
self.socket?.disconnect();
}
func activeSocket() {
self.socket?.connect();
}
}

Related

swift 4 shared.URLS Session from other class?

I got a getJSON Function with url parameter:
func getJsons(jsonUrl: String) {
guard let url = URL(string: jsonUrl) else { return }
URLSession.shared.dataTask(with: url:) { (data, response, err) in
if err != nil {
print("ERROR: \(err!.localizedDescription)")
let alertController = UIAlertController(title: "Error", message:
err!.localizedDescription, preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.default,handler: nil))
var topController:UIViewController = UIApplication.shared.keyWindow!.rootViewController!
while ((topController.presentedViewController) != nil) {
topController = topController.presentedViewController!;
}
topController.present(alertController, animated: true, completion: nil)
}
guard let data = data else { return }
do {
let test = try JSONDecoder().decode([ArticleStruct].self, from: data)
DispatchQueue.main.async {
self.myArticles = test
print(self.myArticles?.count ?? 0 )
self.myTableView.reloadData()
}
} catch let jsonErr {
print("Error:", jsonErr)
}
}.resume()
}
now i want to move the function to another class (network class).
what must I do to add a completionHandler to the function and how do I call it from other classes.
i want to return the json to the caller class.
My plan:
in MainActivity -> viewDidLoad: call network completionHandler(getJsons(192.168.178.100/getPicture.php))
on completion -> myJsonDataMainActivity = (json data from completionHandler)
-> MainActivity.TableView.reload
in otherClass -> call network completionHandler(getJsons(192.168.178.100/getData.php))
on completion -> myJsonDataOtherClass = (json data from completionHandler)
-> otherClass.TableView.reload
Thanks for your help!
You can use delegate.
myJsonDataOtherClass:
protocol NetworkDelegate {
func didFinish(result: Data)
}
class myJsonDataOtherClass {
var delegate: NetworkDelegate? = nil
...
func getJsons(jsonUrl: String) {
...
URLSession.shared.dataTask(with: url:) { (data, response, err) in
...
delegate?.didFinish(data)
}.resume()
}
}
and set delegate at MainActivity
class MainActivity: UIViewController, NetworkDelegate{
...
let jsonClass = myJsonDataOtherClass()
jsonClass.delegate = self
jsonClass.getJsons(jsonUrl:url)
func didFinish(result:Data) {
// process data
}
}
You should add a completion handler in your function and pass the JSON object.
func getJsons(jsonUrl: String, completion:#escaping (_ success: Bool,_ json: [String: Any]?) -> Void) {
...
completion(true, json)
}

store facebook information into Firebase Database using Facebook IOS swift SDK

I'm trying to store facebook user data into firebase database but I keep getting the error "Cannot convert Any? to expected type String"
((FBSDKAccessToken.current()) != nil){
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, picture.type(large), email"]).start(completionHandler: { (connection, result, error) -> Void in
if (error == nil && result != nil) {
guard let fbData = result as? [String:Any] else { return }
let fbid = fbData["id"]
let name = fbData["name"]
self.ref.child("users").child(fbid).setValue([
"id": fbid,
"name": name
])
}
})
I also want to store the picture url into the database. How can I do this?
Using Facebook IOS Swift SDK and Firebase
Try my I implement this function. This is from the production app and it works well for us. I also recommend uploading profile image in Firebase storage or other storage, because after a while the profile image url is not valid.
class func getAllFacebookData(success: ((_ result: [String : Any]) -> Void)?, fail: ((_ error: Error) -> Void)?) {
guard !isGetDataFromFacebook else { return }
DispatchQueue.global(qos: .background).async {
guard let tokenString = FBSDKAccessToken.current()?.tokenString else { return }
guard let req = FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "name,age_range,birthday,gender,email,first_name,last_name,picture.width(1000).height(1000),work,education,hometown,location, friends"], tokenString: tokenString, version: nil, httpMethod: "GET") else { return }
req.start { (connection, result, error) in
if error == nil {
guard let _result = result as? [String : Any] else { return }
let _picture = _result["picture"] as? [String : Any]
let _pictureData = _picture?["data"] as? [String : Any]
let _isSilhouette = _pictureData?["is_silhouette"] as? Bool
let userPref = UserDefaults.standard
userPref.set(_isSilhouette, forKey: "UserHasSilhouetteImage")
userPref.synchronize()
debugPrint("facebook result", _result)
isGetDataFromFacebook = true
syncUserInfoInDatabase(_result)
success?(_result)
} else {
debugPrint("request", error!)
fail?(error!)
}
}
}
}
fileprivate class func syncUserInfoInDatabase(_ userInfo: [String : Any]) {
let realmManager = RealmManager()
guard let currentUser = realmManager.getCurrentUser() else { return }
guard let userInfoModel = createUserInfoModel(userInfo) else { return }
do {
let realm = try Realm()
try realm.write {
currentUser.info = userInfoModel
}
} catch {
debugPrint("realm syncUserInfoInDatabase error", error.localizedDescription)
}
savePhoto(userInfo)
let firebaseDatabaseGeneralManager = FirebaseDatabaseGeneralManager()
firebaseDatabaseGeneralManager.updateCurrentUser(success: nil, fail: nil)
// crete a personal settings
let firUserSettingsDatabaseManager = FIRUserSettingsDatabaseManager()
firUserSettingsDatabaseManager.createStartPeopleFilterSettings(success: nil, fail: nil)
let userSearchLocationModel = UserSearchLocationModel()
userSearchLocationModel.userID = currentUser.id
userSearchLocationModel.birthdayTimeStamp = currentUser.birthdayTimeStamp
userSearchLocationModel.gender = currentUser.gender
switch currentUser.gender {
case UserPeopleFilterSettings.FilterGenderMode.female.description:
userSearchLocationModel.genderIndex = UserPeopleFilterSettings.FilterGenderMode.female.index
case UserPeopleFilterSettings.FilterGenderMode.male.description:
userSearchLocationModel.genderIndex = UserPeopleFilterSettings.FilterGenderMode.male.index
default: break
}
let firPeopleSearchDatabaseManager = FIRPeopleSearchDatabaseManager()
firPeopleSearchDatabaseManager.saveUserSearchLocationModel(userSearchLocationModel, success: nil, fail: nil)
}
private class func savePhoto(_ userInfo: [String : Any]) {
if let pictureDict = userInfo["picture"] as? [String : Any], let pictureDataDict = pictureDict["data"] as? [String : Any] {
if let urlPath = pictureDataDict["url"] as? String {
let firImageDatabaseManager = FIRImageDatabaseManager()
firImageDatabaseManager.saveProfileImage(urlPath, fileName: nil, isFacebookPhoto: true, realmSaved: nil)
}
}
}

Delivering WCSession messages using NSNotifications on Apple Watch

I have NSNotifications using Kugel that are working great on the watch simulator and both the iPhone and iPhone simulator to deliver messages to update the UI/state but these are failing to deliver on the watch side when testing on the devices.
The issue I believe is that the NSNotifications are triggered based on a WCSession message from the iPhone. Everything works fine on the simulator and iPhone side possibly because the connection and notifications are always delivered since the sim keep the watch app active all the time and the iPhone has full session support. On the watch there is the potential for failure of both the session and possibly the notification based on the state of the watch.
Debugging on the watch is painfully slow. It's taking 5-10 minutes just to start the debug process!
Can someone point me to some reading on how best to ensure a phone message is received on the watch and the watch app informed of the need to update based on a message? Or maybe some good debugging code that can log WCSession and NSNotification information that I can review later?
My code is fairly straightforward but still a work in progress ....
On both sides I create a singleton to manage the session, here is the phone side code:
import WatchConnectivity
import UIKit
// This class manages messaging between the Watch and iPhone
class PhoneSession: NSObject, WCSessionDelegate
{
static let manager = PhoneSession()
private var appDelegate: AppDelegate!
private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
private var validSession: WCSession?
{
if let session = session where session.reachable
{
return session
}
return nil
}
func startSession()
{
session?.delegate = self
session?.activateSession()
appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
}
func isEditing() -> Bool
{
if (UIApplication.sharedApplication().applicationState == .Active)
{
if (appDelegate.mainView.visible && appDelegate.mainView.currentDay.isToday())
{
return false
}
return true
}
return false
}
}
extension PhoneSession
{
func sendEditing()
{
if session!.reachable
{
sendMessage([Keys.UpdateType : PhoneUpdateType.Editing.rawValue])
}
}
func sendDoneEditing()
{
if session!.reachable
{
sendMessage([Keys.UpdateType : PhoneUpdateType.DoneEdit.rawValue])
}
}
func sendTable()
{
let tableInfo: WatchWorkout = PhoneData().buildWatchTableData()
let archivedTable: NSData = NSKeyedArchiver.archivedDataWithRootObject(tableInfo)
if session!.reachable
{
sendMessage([Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue, Keys.Workout: archivedTable])
}
else
{
do
{
try updateApplicationContext([Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue, Keys.Workout: archivedTable])
}
catch
{
print("error sending info: \(error)")
}
}
}
func sendRowDone(row: Int, done: Bool)
{
if session!.reachable
{
sendMessage([Keys.UpdateType : PhoneUpdateType.RowDone.rawValue,
Keys.RowIndex: row, Keys.Done: done])
}
else
{
let tableInfo: WatchWorkout = PhoneData().buildWatchTableData()
let archivedTable: NSData = NSKeyedArchiver.archivedDataWithRootObject(tableInfo)
do
{
try updateApplicationContext( [Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue,
Keys.Workout: archivedTable])
}
catch
{
print("error sending info: \(error)")
}
}
}
func receivedRowDone(info: [String : AnyObject])
{
let row: Int = info[Keys.Row] as! Int
let done: Bool = info[Keys.Done] as! Bool
PhoneData.manager.updateInfoFromWatch(row, done: done)
}
func receivedRowInfo(info: [String : AnyObject])
{
let row: Int = info[Keys.Row] as! Int
let rest: Int = info[Keys.Rest] as! Int
let reps: Int = info[Keys.Reps] as! Int
let force: Double = info[Keys.Force] as! Double
PhoneData.manager.updateSetInfoFromWatch(row, rest: rest, reps: reps, force: force)
}
func receivedTableDone(info: [String : AnyObject])
{
let date: Int = info[Keys.Date] as! Int
let dones: [Bool] = info[Keys.TableDones] as! [Bool]
PhoneData.manager.updateDones(dones, forDate: date)
Kugel.publish(PhoneNotificationKeys.ReloadTable)
}
func receivedTableComplete()
{
Kugel.publish(PhoneNotificationKeys.ReloadTable)
}
func receivedStartRest()
{
Kugel.publish(PhoneNotificationKeys.StartRest)
}
func receivedInfo(info: [String : AnyObject]) -> NSData?
{
let messageString: String = info[Keys.UpdateType] as! String
let updateType: WatchUpdateType = WatchUpdateType.getType(messageString)
switch (updateType)
{
case .RowInfo:
receivedRowInfo(info)
case .TableDone:
receivedTableDone(info)
case .RowDone:
receivedRowDone(info)
case .TableComplete:
receivedTableComplete()
case .StartRest:
receivedStartRest()
case .RequestUpdate:
let tableInfo: WatchWorkout = PhoneData().buildWatchTableData()
let archivedTable: NSData = NSKeyedArchiver.archivedDataWithRootObject(tableInfo)
return archivedTable
case .Ignore:
print("Opps")
}
return nil
}
}
// MARK: Interactive Messaging
extension PhoneSession
{
// Sender
func sendMessage(message: [String : AnyObject], replyHandler: (([String : AnyObject]) -> Void)? = nil, errorHandler: ((NSError) -> Void)? = nil)
{
validSession!.sendMessage(message,
replyHandler:
{
(returnMessage: [String : AnyObject]) -> Void in
if let theMessage = returnMessage[Keys.MessageStatus]
{
print("Return Message from Watch: \(theMessage)")
}
},
errorHandler:
{
(error) -> Void in
print("Error Message during transfer to Watch: \(error)")
}
)
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject])
{
self.receivedInfo(message)
}
// Receiver
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
{
let returnMessage = self.receivedInfo(message)
if (returnMessage != nil)
{
if let archivedTable: NSData = returnMessage!
{
let replyValues = [Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue, Keys.Workout: archivedTable] // Data to be returned
replyHandler(replyValues)
}
}
}
}
// MARK: Application Context
// use when your app needs only the latest information, if the data was not sent, it will be replaced
extension PhoneSession
{
// Sender
func updateApplicationContext(applicationContext: [String : AnyObject]) throws
{
if ((session) != nil)
{
do
{
try session!.updateApplicationContext(applicationContext)
}
catch let error
{
throw error
}
}
}
// Receiver
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject])
{
self.receivedInfo(applicationContext)
}
}
and this is the watch side:
import WatchConnectivity
class WatchSession: NSObject, WCSessionDelegate
{
static let manager = WatchSession()
private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
private var validSession: WCSession?
{
if let session = session where session.reachable
{
return session
}
return nil
}
func startSession()
{
session?.delegate = self
session?.activateSession()
}
}
extension WatchSession
{
func sendRowInfo(row:Int, rest: Int, reps: Int, force: Double)
{
if session!.reachable
{
let message: [String: AnyObject] = [Keys.UpdateType : WatchUpdateType.RowInfo.rawValue,
Keys.Row : row,
Keys.Rest : rest,
Keys.Reps : reps,
Keys.Force : force]
sendMessage(message)
print("sent row done to Phone: \(message)")
}
else
{
sendTableDone()
print("failed to connect to Phone, sent table done context to Phone")
}
}
func sendRowDone(row:Int, done: Bool)
{
if session!.reachable
{
let message: [String: AnyObject] = [Keys.UpdateType : WatchUpdateType.RowDone.rawValue,
Keys.Row : row,
Keys.Done : done]
sendMessage(message)
print("sent row done to Phone: \(message)")
}
else
{
sendTableDone()
print("failed to connect to Phone, sent table done context to Phone")
}
}
func sendTableDone()
{
let tableDones: [Bool] = WatchData.manager.watchTableDone()
let date: Int = WatchData.manager.date()
do
{
try updateApplicationContext( [Keys.UpdateType : WatchUpdateType.TableDone.rawValue,
Keys.Date : date, Keys.TableDones: tableDones])
}
catch _
{
print("error trying to send TableDones")
}
}
func sendTableComplete()
{
if session!.reachable
{
sendMessage([Keys.UpdateType : WatchUpdateType.TableComplete.rawValue])
}
else
{
let date: Int = WatchData.manager.date()
do
{
try updateApplicationContext( [Keys.UpdateType : WatchUpdateType.TableComplete.rawValue,
Keys.Date : date])
}
catch _
{
print("error trying to send TableComplete")
}
}
}
func sendRest() -> Bool
{
var sent: Bool = false
if session!.reachable
{
sendMessage([Keys.UpdateType : WatchUpdateType.StartRest.rawValue])
sent = true
}
return sent
}
func requestUpdate() -> Bool
{
var sent: Bool = false
if session!.reachable
{
print("requesting update reply")
sendMessage([Keys.UpdateType : WatchUpdateType.RequestUpdate.rawValue])
sent = true
}
return sent
}
func receivedUpdateReply(info: [String : AnyObject])
{
}
func receiveRowDone(info: [String : AnyObject])
{
let row: Int = info[Keys.RowIndex] as! Int
let done: Bool = info[Keys.Done] as! Bool
WatchData.manager.updateWatchTable(row, done: done)
Kugel.publish(WatchNotificationKeys.UpdateRow)
}
func receivedTable(archivedTable: NSData)
{
let workout: WatchWorkout = NSKeyedUnarchiver.unarchiveObjectWithData(archivedTable) as! WatchWorkout
WatchData.manager.updateWatchWorkout(workout)
Kugel.publish(WatchNotificationKeys.ReloadTable)
}
func receivedStartEditStatus()
{
Kugel.publish(WatchNotificationKeys.StartEdit)
}
func receivedDoneEditStatus()
{
WatchData.manager.retrieveWorkout()
Kugel.publish(WatchNotificationKeys.DoneEdit)
}
func receivedStopRest()
{
Kugel.publish(WatchNotificationKeys.StopRest)
}
func receivedInfo(info: [String : AnyObject])
{
let messageString: String = info[Keys.UpdateType] as! String
let updateType: PhoneUpdateType = PhoneUpdateType.getType(messageString)
switch (updateType)
{
case .TableInfo:
receivedTable(info[Keys.Workout] as! NSData)
case .Editing:
receivedStartEditStatus()
case .DoneEdit:
receivedDoneEditStatus()
case .RowDone:
receiveRowDone(info)
case .StopRest:
receivedStopRest()
case .Ignore:
print("Opps")
}
}
func receivedReply(info: [String : AnyObject])
{
if let replyString: String = info[Keys.ReplyType] as? String
{
let replyType: ReplyType = ReplyType.getType(replyString)
switch (replyType)
{
case .Table:
print("received Reply Table")
receivedTable(info[Keys.Workout] as! NSData)
case .NoData:
print("Opps ... nodata in reply")
case .Ignore:
print("Opps replyType message error")
}
}
}
}
// MARK: Interactive Messaging
extension WatchSession
{
// Sender
func sendMessage(message: [String : AnyObject], replyHandler: (([String : AnyObject]) -> Void)? = nil, errorHandler: ((NSError) -> Void)? = nil)
{
validSession!.sendMessage(message,
replyHandler:
{
(replyMessage: [String : AnyObject]) -> Void in
if let typeMessage: String = replyMessage[Keys.ReplyType] as? String
{
self.receivedReply(replyMessage)
print("Return Message from Phone: \(typeMessage)")
}
},
errorHandler:
{
(error) -> Void in
print("Error Message during transfer to Phone: \(error)")
}
)
}
// Receiver
func session(session: WCSession, didReceiveMessage message: [String : AnyObject])
{
self.receivedInfo(message)
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
{
self.receivedInfo(message)
let replyValues = [Keys.MessageStatus : "Watch received message"] // Data to be returned
replyHandler(replyValues)
}
}
// MARK: Application Context
extension WatchSession
{
// Sender
func updateApplicationContext(applicationContext: [String : AnyObject]) throws
{
if let session = validSession
{
do
{
try session.updateApplicationContext(applicationContext)
}
catch let error
{
throw error
}
}
}
// Receiver
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject])
{
// handle receiving application context
receivedInfo(applicationContext)
}
}
I create the singleton in my AppDelegate on the iPhone side and the ExtensionDelegate on the watch side, here is the phone side:
var phoneSession: PhoneSession!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
self.phoneSession = PhoneSession()
PhoneSession.manager.startSession()
The basic logic when sending a message is it looks if the other side is reachable, if it is sendMessage is used, if it is not reachable then sendApplicationContext is used.
When the message is received on the phone side there is some extra logic to see if the app is in the foreground or background, if in foreground it will push a notification onto the main thread, if in background just the info is updated. On the watch side it always pushes onto the main thread since my understanding is messages will not be received in the background.

Swift Ensembles Set Up & ubiquityContainerIdentifier

The book states,
“An ensemble identifier is used to match stores across devices. It is
important that this be the same for each store in the ensemble.”
let ensembleFileSystem = CDEICloudFileSystem(ubiquityContainerIdentifier: "???")
Does this need to be unique for all users ? or just for my application?
If anyone has a Swift version of how the set up Ensembles that would be great.
What I have so far, is this all that is needed?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let ensembleFileSystem = CDEICloudFileSystem(ubiquityContainerIdentifier: "???")
let modelURL = NSBundle.mainBundle().URLForResource("DataModel", withExtension: "momd")!
let url = applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
let ensemble = CDEPersistentStoreEnsemble(ensembleIdentifier: "mainstore", persistentStoreURL: url, managedObjectModelURL: modelURL, cloudFileSystem: ensembleFileSystem!)
if !ensemble.leeched {
ensemble.leechPersistentStoreWithCompletion { (error) -> Void in
if error != nil {
print("cannot leech")
print(error!.localizedDescription)
}
}
}
NSNotificationCenter.defaultCenter().addObserver(self, selector: "syncWithCompletion:", name: CDEMonitoredManagedObjectContextDidSaveNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "syncWithCompletion:", name: CDEICloudFileSystemDidDownloadFilesNotification, object: nil)
return true
}
func syncWithCompletion(notification:NSNotification) {
print("synced \(notification)")
managedObjectContext.mergeChangesFromContextDidSaveNotification(notification)
}
Something is missing Im getting this error log
User is not logged into iCloud
Despite being logged in as evident
print(NSFileManager.defaultManager().ubiquityIdentityToken)
Not being nil
Got it to work in the end - found example apps in 1.0 Git
I belive I was leeching to fast - not giving enough time for the set up process to complete.
Support this framework - buy ensembles 2, if you like ver 1.
Update .. easier way
I just use the normal core data stack apple provides.
Here is the extras to get ensembles working.
var ensemble:CDEPersistentStoreEnsemble!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let file = CDEICloudFileSystem(ubiquityContainerIdentifier: nil)
let modelURL = NSBundle.mainBundle().URLForResource("DataModel", withExtension: "momd")!
let storeurl = self.applicationDocumentsDirectory.URLByAppendingPathComponent("store.sqlite")
ensemble = CDEPersistentStoreEnsemble(ensembleIdentifier: "MyStoreName", persistentStoreURL: storeurl, managedObjectModelURL: modelURL, cloudFileSystem: file)
ensemble.delegate = self
NSNotificationCenter.defaultCenter().addObserver(self, selector: "localSaveOccurred:", name: CDEMonitoredManagedObjectContextDidSaveNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "cloudDataDidDownload:", name: CDEICloudFileSystemDidDownloadFilesNotification, object: nil)
syncWithCompletion { completed in
if completed {
print("SUCCESSS")
}
else {
print("FAIL")
}
}
return true
}
// MARK: - Sync
func applicationDidEnterBackground(application: UIApplication) {
print("Did Enter Background Save from App Delegate")
let identifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
saveContext()
syncWithCompletion { (completed) -> Void in
if completed {
UIApplication.sharedApplication().endBackgroundTask(identifier)
}
}
}
func applicationWillEnterForeground(application: UIApplication) {
syncWithCompletion { (completed) -> Void in
}
}
func localSaveOccurred(note:NSNotification) {
syncWithCompletion { (completed) -> Void in
}
}
func cloudDataDidDownload(note:NSNotification) {
syncWithCompletion { (completed) -> Void in
print("items from iCloud arrived")
}
}
func syncWithCompletion(completion:(completed:Bool) -> Void) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
if !ensemble.leeched {
ensemble.leechPersistentStoreWithCompletion(nil)
}
else {
ensemble.mergeWithCompletion{ error in
if error != nil {
print("cannot merge \(error!.localizedDescription)")
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
completion(completed: false)
}
else {
print("merged")
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
completion(completed: true)
}
}
}
}
// MARK: - Ensemble Delegate Methods
func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble!, didSaveMergeChangesWithNotification notification: NSNotification!) {
managedObjectContext.performBlockAndWait { () -> Void in
self.managedObjectContext.mergeChangesFromContextDidSaveNotification(notification)
}
}
func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble!, globalIdentifiersForManagedObjects objects: [AnyObject]!) -> [AnyObject]! {
return (objects as NSArray).valueForKeyPath("uniqueIdentifier") as! [AnyObject]
}
My First Way
Here it is in Swift, with a few extras
var ensemble:CDEPersistentStoreEnsemble!
var cloudFileSystem:CDEICloudFileSystem!
var managedObjectContext: NSManagedObjectContext!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
setUpCoreData()
let modelURL = NSBundle.mainBundle().URLForResource("YourDataModel", withExtension: "momd")!
cloudFileSystem = CDEICloudFileSystem(ubiquityContainerIdentifier:"USE_YOUR_APPS_REVERSE DOMAIN NAME HERE")
From the developer: RE ubiquityContainerIdentifier
This is not part of Ensembles per se. It is from iCloud. Every app
using iCloud has to have a ubiquity container id. You can find it in
your app settings when you enable iCloud. It is unique per app, and we
only use it if you are choosing for iCloud (eg not Dropbox).
ensemble = CDEPersistentStoreEnsemble(ensembleIdentifier: "store", persistentStoreURL: storeURL(), managedObjectModelURL: modelURL, cloudFileSystem: cloudFileSystem!)
ensemble.delegate = self
NSNotificationCenter.defaultCenter().addObserver(self, selector: "localSaveOccurred:", name: CDEMonitoredManagedObjectContextDidSaveNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "cloudDataDidDownload:", name: CDEICloudFileSystemDidDownloadFilesNotification, object: nil)
syncWithCompletion { completed in
if completed {
print("SUCCESSS")
}
else {
print("FAIL")
}
}
return true
}
// MARK: - Core Data Stack
func setUpCoreData() {
let modelURL = NSBundle.mainBundle().URLForResource("DataModel", withExtension: "momd")!
guard let model = NSManagedObjectModel(contentsOfURL: modelURL) else { fatalError("cannot use model") }
do {
try NSFileManager.defaultManager().createDirectoryAtURL(storeDirectoryURL(), withIntermediateDirectories: true, attributes: nil)
}
catch {
fatalError("cannot create dir")
}
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
//NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption: #YES, NSInferMappingModelAutomaticallyOption: #YES};
let failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL(), options: nil)
managedObjectContext = NSManagedObjectContext.init(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
abort()
}
}
func storeDirectoryURL() -> NSURL {
let directoryURL = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
return directoryURL
}
func storeURL() -> NSURL {
let url = storeDirectoryURL().URLByAppendingPathComponent("store.sqlite")
return url
}
// MARK: - Sync
func applicationDidEnterBackground(application: UIApplication) {
print("Did Enter Background Save from App Delegate")
let identifier = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
saveContext()
syncWithCompletion { (completed) -> Void in
if completed {
UIApplication.sharedApplication().endBackgroundTask(identifier)
}
}
}
func applicationWillEnterForeground(application: UIApplication) {
syncWithCompletion { (completed) -> Void in
}
}
func localSaveOccurred(note:NSNotification) {
syncWithCompletion { (completed) -> Void in
}
}
func cloudDataDidDownload(note:NSNotification) {
syncWithCompletion { (completed) -> Void in
}
}
func syncWithCompletion(completion:(completed:Bool) -> Void) {
if !ensemble.leeched {
ensemble.leechPersistentStoreWithCompletion { error in
if error != nil {
print("cannot leech \(error!.localizedDescription)")
completion(completed: false)
}
else {
print("leached!!")
completion(completed: true)
}
}
}
else {
ensemble.mergeWithCompletion{ error in
if error != nil {
print("cannot merge \(error!.localizedDescription)")
completion(completed: false)
}
else {
print("merged!!")
completion(completed: true)
}
}
}
}
// MARK: - Ensemble Delegate Methods
func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble!, didSaveMergeChangesWithNotification notification: NSNotification!) {
print("did merge changes with note")
managedObjectContext.mergeChangesFromContextDidSaveNotification(notification)
}
func persistentStoreEnsemble(ensemble: CDEPersistentStoreEnsemble!, globalIdentifiersForManagedObjects objects: [AnyObject]!) -> [AnyObject]! {
return (objects as NSArray).valueForKeyPath("uniqueIdentifier") as! [AnyObject]
}

Facebook login using RxSwift

I'm trying to implement the following RxSwift example:
Login in with facebook in my application -> retrieve the user information -> retrieve user's profile photo.
I have these three functions and they must be executed in this order: requestAccess() -> fetchUserInformation() -> fetchUserPhoto()
func requestAccess() -> Observable<(ACAccount)> {
return create { observer -> Disposable in
let accountStore = ACAccountStore()
let accountType = accountStore.accountTypeWithAccountTypeIdentifier(ACAccountTypeIdentifierFacebook)
let dictionary: [NSObject : AnyObject] = [ACFacebookAppIdKey:"***APPID***", ACFacebookPermissionsKey:["public_profile", "email", "user_friends"]]
accountStore.requestAccessToAccountsWithType(accountType, options: dictionary) { granted, error in
if granted == false || error != nil {
sendError(observer, error ?? UnknownError)
} else {
let accounts = accountStore.accountsWithAccountType(accountType)
let account = accounts.last as! ACAccount
sendNext(observer, account)
sendCompleted(observer)
}
}
return AnonymousDisposable({})
}
}
func fetchUserInformation(account: ACAccount) -> Observable<User> {
return create { observer -> Disposable in
let url = NSURL(string: "https://graph.facebook.com/me")
let request = SLRequest(forServiceType: SLServiceTypeFacebook, requestMethod: .GET, URL: url, parameters: nil)
request.account = account
request.performRequestWithHandler { (data, response, error) -> Void in
if data == nil || response == nil {
sendError(observer, error ?? UnknownError)
} else {
let result: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
let user = User()
user.updateInformationWithJSON(result! as! JSONObject)
sendNext(observer, user)
sendCompleted(observer)
}
}
return AnonymousDisposable({})
}
}
func fetchUserPhoto(user: User) -> Observable<AnyObject> {
return create { observer -> Disposable in
let url = NSURL(string: "https://graph.facebook.com/***myid***/picture")
let params = ["redirect":"false", "height":"200", "width":"200"]
let request = SLRequest(forServiceType: SLServiceTypeFacebook, requestMethod: .GET, URL: url, parameters: params)
request.account = SocialController.account
request.performRequestWithHandler { (data, response, error) -> Void in
if data == nil || response == nil {
sendError(observer, error ?? UnknownError)
} else {
let result: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
sendNext(observer, result!)
sendCompleted(observer)
}
}
return AnonymousDisposable({})
}
}
I already tried to implement this flow but it doesn't feel right. What is the best way to solve this problem?
requestAccess()
>- subscribeNext { account in
fetchUserInformation(account)
>- map { user in return UserViewModel(model: user) }
>- subscribeNext { viewModel in self.viewModel = viewModel }
}
Have you tried using flatMap?
It's an equivalent to then in the JavaScript bluebird or Q world. The difference between map and flatMap is that flatMap must return an Observable<T> which will then be unwrapped in the following block/closure.
requestAccess()
>- flatMap{ account in
return fetchUserInformation(account)
}
>- map { user in
return UserViewModel(model:user)
}
>- subscribeNext { viewModel in
self.viewModel = viewModel
}
Tidbit #1: Consider using unowned self when referencing self to avoid a retain cycle.
Tidbit #2: These two are pretty much the same thing.
flatMap { return just("hello") }
>- subscribeNext{ greeting in println(greeting) }
map { return "hello" }
>- subscribeNext{ greeting in println(greeting) }
private
func requestFacebookAccess(_ viewController: UIViewController) -> Observable<LoginManagerLoginResult?> {
return Observable.create { observer -> Disposable in
let loginManager = LoginManager()
loginManager.logIn(permissions: ["public_profile", "email"], from: viewController, handler: { result, error in
observer.onNext(result)
observer.onCompleted()
})
return Disposables.create()
}
}