How to upload an App with universal framework to AppStore?

Cocoapods is a popular dependencies management tool for iOS development. We notice that in Cocoapods-v1.3, the framework MUST include simulator (x86_64, i386) architectures, otherwise the pod spec lint will fail and the pod cannot be uploaded to Cocoapods server. It shows error as follows:

xcodebuild: fatal error: lipo: -remove's specified would result in an empty fat file
xcodebuild: ld: warning: ld: warning: ignoring file SampleSDK/iOS/Sample.framework/Sample, missing required architecture x86_64 in file

Ref: Pod lint fails

BUT the Apps with framework including simulator architectures will not be accepted by AppStore.

The binary you uploaded was invalid
Unsupported Architecture. Your executable contains unsupported architecture '[x86_64, i386]

When we are trying to upload the App with architectures (x86_64, i386), it will show error as follows:

Generally if a framework including armv7,arm64 architectures only, the host App with the framework won't be installed in Simulators.

Achitecture Simulator Real Device Cocoapods AppStore
x86_64/i386
armv7/arm64
x86_64/i386+armv7/arm64

For simulator: x86_64/i386
For real device: armv7/arm64

So the ideal framework is a universal(x86_64/i386+armv7/arm64) one, the framework should be able to run in both simulator and real device, and also should be able to upload to AppStore.

Solution

The solution here is that we continue using universal framework. When archiving the host App, we remove the x86_64/i386 architectures in achieve/pre-actions, and restore x86_64/i386 architectures in achieve/post-actions.

Imaging we have a Cocoapods framework in our project. Usually the framework locates in

${SRCROOT}/Pods/YOUR-FRAMEWORK

1. Add script to Archive: pre-actions

Please remember to replace the framework name with your own framework name

2. Add script to Archive: post-actions

Please remember to replace the framework name with your own framework name

3. Done

Posted in iOS, Mobile | Tagged | 1 Comment

Cocoapod tips

check installed cocoapod plugin

gem list --local | grep cocoapods

create pod

pod spec create POD_NAME

https://eladnava.com/publish-a-universal-binary-ios-framework-in-swift-using-cocoapods/

Register an account by running the following, entering your full name and e-mail address:

pod trunk register you@email.com 'Full Name' 

create pod demo

pod lib create SwiftKit
pod spec lint --verbose
......

pod trunk push SwiftKit.podspec  

https://s3.amazonaws.com/elasticbeanstalk-us-east-1-564874457370/SwiftKit.zip

check pod info

you can use pod trunk info [pod] to get information on a pod and pod trunk me can be used to verify your local account.

pod trunk info RxSwift
pod trunk me

view trunk

pod trunk me

You can view your own registered information, including name, email, since, sessions, all Pods you submitted to Cocopods, etc.

Add more owners

You can add more owners to pod:

pod trunk add-owner crafttang crafttang@gmail.com

lipo

lipo is a usefull command, it is used to view framework's supported architectures, or merge/split framework.

1. -info

查看刚才编译的 Framework 库在 debug 和 release 下支持的框架:
“`
$ lipo -info iddc-appstore.framework/iddc
Architectures in the fat file: iddc-appstore.framework/iddc are: armv7 arm64

15:50:02  ~/Downloads/lipo 
$ lipo -info iddc-universal.framework/iddc
Architectures in the fat file: iddc-universal.framework/iddc are: i386 armv7 x86_64 arm64
“`

2. -create

上面生成的库,要么是只支持模拟器的,要么是只支持真机的,那么如何才能又能兼顾真机和模拟器呢? -create 使用方式:

lipo -create 库1 库2 -output 新库  

3. -thin

如果有一个 fat file 但是你不需要支持那么多框架,也可以通过拆分,为库瘦身, -thin 使用方式:

lipo 旧库 -thin 需拆分框架 -output 新库

reference:

一篇较为详细的 iOS动态静态库创建打包方法 总结

遗留问题:

Pod lint fails when containing dynamic-frameworks without simulator architectures

Posted in iOS, Mobile | Tagged | Leave a comment

Hide implementation of swift framework when distributing

In this article, I will demonstrate how to create a Swift NiceLogger framework and push to Cocoapods without sharing source code.

You can download the final project from NiceLogger

1. Create a Swift framework

1.1 Create a new framework project


Product Name NiceLogger, Set language to Swift

1.2 Create a new file NiceLogger.swift


Copy the code from Here into NiceLogger.swift and save it.

1.3 Build to generate framework

Now build it and you will find the colour of the framework name turns to black, which means the framework has been generated.

1.4 Create Zip Archive

Right click the NiceLogger.framework, select Show in Finder, copy the NiceLogger.framework and MIT License to the NiceLogger home folder:

1.5 Create a Zip Archive

Archive them inside a .zip, upload it to your server, and link to it using the s.source parameter in the .podspec file.

Create the NiceLogger.zip file by running the following command in your project directory:

zip -r NiceLogger.zip LICENSE NiceLogger.framework  

You can upload the NiceLogger.zip file to your server, in a path similar to the following:

https://s3.amazonaws.com/elasticbeanstalk-us-east-1-564874457370/NiceLogger.zip

You can also create a GitHub repository and push the .zip file to it.

Once you upload the .zip, link it in the s.source parameter of the .podspec file.

  s.source            = { :http => 'https://s3.amazonaws.com/elasticbeanstalk-us-east-1-564874457370/NiceLogger.zip' }

2. Create Cocoapod repo

2.1 Create .podspec

Create a NiceLogger.podspec file in your project directory which will contain information about the CocoaPod you are publishing, for instance name, version, sources, and more.

pod spec create NiceLogger

To learn more about Podspec attributes, see http://docs.cocoapods.org/specification.html
To understand how to work with Podspecs in the CocoaPods, see https://github.com/CocoaPods/Specs/

for example:


Pod::Spec.new do |s|

  s.name         = "NiceLogger"
  s.version      = "0.0.1"
  s.summary      = "A nice logger tool in Swift."
  s.description  = <<-DESC
  A nice logger tool in Swift. Cheers
                   DESC

  s.homepage     = "https://github.com/zhihuitang"
  s.license      = { :type => "MIT", :file => "license" }
  s.author             = { "Zhihui Tang" => "crafttang@gmail.com" }
  s.ios.deployment_target = '8.0'
  s.ios.vendored_frameworks = 'NiceLogger.framework'
  s.source            = { :http => 'https://s3.amazonaws.com/elasticbeanstalk-us-east-1-564874457370/NiceLogger.zip' }
  s.exclude_files = "Classes/Exclude"

end

2.2 Ensure this is a valid .podspec

pod spec lint --verbose

If everything is working, you would see the following output:

......

** BUILD SUCCEEDED **

   Testing with xcodebuild.
 -> NiceLogger (0.0.1)
    - NOTE  | [iOS] xcodebuild:  ld: warning: ignoring file NiceLogger/NiceLogger.framework/NiceLogger, file was built for x86_64 which is not the architecture being linked (i386): NiceLogger/NiceLogger.framework/NiceLogger

Analyzed 1 podspec.

NiceLogger.podspec passed validation.

2.3. Register a Trunk Account

In order to publish your .podspec file to the CocoaPods repository, first you must register an account with the CocoaPods Trunk.

The CocoaPods Trunk is an authentication and CocoaPods API service. For publishing new or update library to CocoaPods for public release, you will need to be registered with the Trunk and have a valid Trunk session on your current device.

Register an account by running the following, entering your full name and e-mail address:

pod trunk register you@email.com 'Full Name'  

Now, check your e-mail. There will be a confirmation link. Click it.

Now your Trunk account is activated and you can publish your CocoaPod!

2.4. Publish the Pod

Run the following command in the same directory as the .podspec to publish it to the CocoaPods repository:

pod trunk push NiceLogger.podspec  

The CLI will validate your .podspec and attempt to install the CocoaPod by downloading the source .zip and validating its contents. If works, you have just published your first universal binary CocoaPod!

If everything works, you would find the following output:


--------------------------------------------------------------------------------
 🎉  Congrats

 🚀  NiceLogger (0.0.1) successfully published
 📅  October 14th, 23:14
 🌎  https://cocoapods.org/pods/NiceLogger
 👍  Tell your friends!
--------------------------------------------------------------------------------

3. Create a Demo to verify the framework

3.1 Create a demo project

3.2 Init cocopods

pod init

Add dependency to Podfile

target 'Demo' do
  use_frameworks!
  pod 'NiceLogger'

  # Pods for Demo

  target 'DemoTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'DemoUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end

3.3 install pod dependency

In the demo project home directory, run

pod install

yeah… we got it……, wait……

[!] Unable to find a specification for `NiceLogger`

In that case please fix it by:

pod repo update

Then run pod install again

pod install

if you are lucky again, you probably get following output:

$ pod install
Analyzing dependencies
Downloading dependencies
Installing NiceLogger (0.0.1)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `Test.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed.

3.4 Code with NiceLogger

Navigate to the demo project, double the Demo.xcworkspace to open the demo project. You would see the source code the framework is invisible in Pods.

Now add code to verify the framework works.
Add dependency:

import NiceLogger

Add Code to viewDidLoad():

NiceLogger.d("hehe, viewDidLoad")

3.5 Run the demo project

You would find the log as follows:

2017-10-14T21:26:46.796Z [ViewController.swift#viewDidLoad()#16]-D: hehe, viewDidLoad

Thanks

Many thanks to MR. Elad Nava, I borrowed some words form his blog:
https://eladnava.com/publish-a-universal-binary-ios-framework-in-swift-using-cocoapods/

Posted in iOS, Mobile | Tagged , | 5 Comments

CoreLocation in iOS

CoreLocation in iOS11

Posted in iOS | Tagged | Leave a comment

iOS Grand Central Dispatch

GCD provides three main types of queues:

1.1 Main queue

Main queue runs on the main thread and is a serial queue.
This is a common choice to update the UI after completing work in a task on a concurrent queue. To do this, you’ll code one closure inside another. Targeting the main queue and calling async guarantees that this new task will execute sometime after the current method finishes.

// Get the main queue
let mainQueue = DispatchQueue.main

1.2 Global queues

This is a common choice to perform non-UI work in the background.
Global queques are Concurrent queues that are shared by the whole system. There are four such queues with different priorities : high, default, low, and background. The background priority queue is I/O throttled.

// Get the .userInitiated global dispatch queue
let userQueue = DispatchQueue.global(qos: .userInitiated)
// Get the .default global dispatch queue
let defaultQueue = DispatchQueue.global()

When setting up the global concurrent queues, you don’t specify the priority directly. Instead you specify a Quality of Service (QoS) class property. This will indicate the task’s importance and guide GCD into determining the priority to give to the task.

The QoS classes are:

  • User-interactive
    This represents tasks that need to be done immediately in order to provide a nice user experience. Use it for UI updates, event handling and small workloads that require low latency. The total amount of work done in this class during the execution of your app should be small. This should run on the main thread.

  • User-initiated
    The represents tasks that are initiated from the UI and can be performed asynchronously. It should be used when the user is waiting for immediate results, and for tasks required to continue user interaction. This will get mapped into the high priority global queue.

DispatchQueue.global(qos: .userInitiated).async { // 1
  let overlayImage = self.faceOverlayImageFromImage(self.image)
  DispatchQueue.main.async { // 2
    self.fadeInNewImage(overlayImage) // 3
  }
}
  • Utility
    This represents long-running tasks, typically with a user-visible progress indicator. Use it for computations, I/O, networking, continuous data feeds and similar tasks. This class is designed to be energy efficient. This will get mapped into the low priority global queue.

  • Background
    This represents tasks that the user is not directly aware of. Use it for prefetching, maintenance, and other tasks that don’t require user interaction and aren’t time-sensitive. This will get mapped into the background priority global queue.

1.3 Custom queues

Queues that you create which can be serial or concurrent. These actually trickle down into being handled by one of the global queues.

  • Serial Queue
    The only global serial queue is DispatchQueue.main, but you can create a private serial queue. Note that .serial is the default attribute for a private dispatch queue:
// Create mySerialQueue
let mySerialqueue = DispatchQueue(label: "com.tang.max")
  • Concurrent Queue
    A good choice when you want to perform background work serially and track it. This eliminates resource contention since you know only one task at a time is executing. Note that if you need the data from a method, you must inline another closure to retrieve it or consider using sync.
    To create a private concurrent queue, specify the .concurrent attribute.
// Create workerQueue
let workerQueue = DispatchQueue(label: "com.tang.max", qos: .background, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)

Synchronous vs. Asynchronous

With GCD, you can dispatch a task either synchronously or asynchronously.
A synchronous function returns control to the caller after the task is completed.
An asynchronous function returns immediately, ordering the task to be done but not waiting for it. Thus, an asynchronous function does not block the current thread of execution from proceeding on to the next function.

Delaying Task Execution

DispatchQueue allows you to delay task execution. Care should be taken not to use this to solve race conditions or other timing bugs through hacks like introducing delays. Use this when you want a task to run at a specific time.
Consider the user experience of your app for a moment. It’s possible that users might be confused about what to do when they open the app for the first time — were you? :]
It would be a good idea to display a prompt to the user if there aren’t any photos. You should also consider how the user’s eyes will navigate the home screen. If you display a prompt too quickly, they might miss it as their eyes linger on other parts of the view. A one-second delay before displaying the prompt should be enough to catch the user’s attention and guide them.

let delayInSeconds = 1.0 // 1
DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { // 2
  let count = PhotoManager.sharedManager.photos.count
  if count > 0 {
    self.navigationItem.prompt = nil
  } else {
    self.navigationItem.prompt = "Add photos with faces to Googlyify them!"
  }
}

Part 2: Operation Queues

GCD is a low-level C API that enables developers to execute tasks concurrently. Operation queue, on the other hand, is high level abstraction of the queue model, and is built on top of GCD. That means you can execute tasks concurrently just like GCD, but in an object-oriented fashion. In short, operation queues just make developers’ life even simpler.

Unlike GCD, they don’t conform to the First-In-First-Out order. Here are how operation queues are different from dispatch queues:

  • Don’t follow FIFO: in operation queues, you can set an execution priority for operations and you can add dependencies between operations which means you can define that some operations will only be executed after the completion of other operations. This is why they don’t follow First-In-First-Out.
  • By default, they operate concurrently: while you can’t change its type to serial queues, there is still a workaround to execute tasks in operation queues in sequence by using dependencies between operations.
  • Operation queues are instances of class OperationQueue and its tasks are encapsulated in instances of Operation.

2.1 Operation

Tasks submitted to operation queues are in the form of Operation instances. You can simply think of Operation as a single unit of work.
Operation is an abstract class which can’t be used directly so you have to use Operation subclasses.

Here’s a quick comparison of the two that will help you decide when and where to use GCD or Operation:

  • GCD is a lightweight way to represent units of work that are going to be executed concurrently. You don’t schedule these units of work; the system takes care of scheduling for you. Adding dependency among blocks can be a headache. Canceling or suspending a block creates extra work for you as a developer! :]
  • Operation adds a little extra overhead compared to GCD, but you can add dependency among various operations and re-use, cancel or suspend them.
  • A stand alone Operation runs synchronously. To run it off the main queue, we have to dispatch it to a queue, ,either to a dispatch queue or an Operation
open class Operation : NSObject {
    open func start()
    open func main()
    open var isCancelled: Bool { get }
    open func cancel()
    open var isExecuting: Bool { get }
    open var isFinished: Bool { get }
    open var isConcurrent: Bool { get }

    @available(iOS 7.0, *)
    open var isAsynchronous: Bool { get }

    open var isReady: Bool { get }
    open func addDependency(_ op: Operation)
    open func removeDependency(_ op: Operation)
    open var dependencies: [Operation] { get }
    open var queuePriority: Operation.QueuePriority
    
    @available(iOS 4.0, *)
    open var completionBlock: (() -> Swift.Void)?

    @available(iOS 4.0, *)
    open func waitUntilFinished()
    
    @available(iOS, introduced: 4.0, deprecated: 8.0, message: "Not supported")
    open var threadPriority: Double
    
    @available(iOS 8.0, *)
    open var qualityOfService: QualityOfService

    @available(iOS 8.0, *)
    open var name: String?
}

In the iOS SDK, we are provided with two concrete subclasses of Operation. These classes can be used directly, but you can also subclass Operation and create your own class to perform the operations. The two classes that we can use directly are:

  • BlockOperation – A BlockOperation is just a wrapper of around the default global dispatch queue, it manages the concurrent execution of one or more blocks on the default global queue. This class provides an object oriented wrapper for the apps that are already using object Operation queues and don't want to create dispatch queues as well. But being an Operation, it has more features than a dispatch queue task. It can take advantage of Operation dependencies, KVO notifications and cancelling. A BlockOperation also behaves like a dispatch group, it marks itself as finished when all its blocks have finished executing. So you can use it to track a group of executing blocks. BlockOperation blocks run concurrently, synchronously, if you need to execute blocks serially, submit them directly to a private dispatch queue, or set them up with dependencies.
  • InvocationOperation – Use this class to initiate an operation that consists of invoking a selector on a specified object.

So what’s the advantages of Operation?

  • First, they support dependencies through the method addDependency(op: Operation) in the Operation class. When you need to start an operation that depends on the execution of the other, you will want to use Operation.

  • Secondly, you can change the execution priority by setting the property queuePriority with one of these values. The operations with high priority will be executed first.

    public enum OperationQueuePriority : Int {
        case VeryLow
        case Low
        case Normal
        case High
        case VeryHigh
    }
    
  • You can cancel a particular operation or all operations for any given queue. The operation can be cancelled after being added to the queue. Cancellation is done by calling method cancel() in the Operation class. When you cancel any operation, we have three scenarios that one of them will happen:

    • Your operation is already finished. In that case, the cancel method has no effect.
    • Your operation is already being executing. In that case, system will NOT force your operation code to stop but instead, cancelled property will be set to true.
    • Your operation is still in the queue waiting to be executed. In that case, your operation will not be executed.
  • Operation has 3 helpful boolean properties which are finished, cancelled, and ready. finished will be set to true once operation execution is done. cancelled is set to true once the operation has been cancelled. ready is set to true once the operation is about to be executed now.

  • Any Operation has an option to set completion block to be called once the task being finished. The block will be called once the property finished is set to true in Operation.

For example:

@IBAction func didClickOnStart(sender: AnyObject) {
    queue = OperationQueue()

    queue.addOperationWithBlock { () -> Void in
        
        let img1 = Downloader.downloadImageWithURL(imageURLs[0])

        OperationOperationQueue.mainQueue().addOperationWithBlock({
            self.imageView1.image = img1
        })
    }
    
    queue.addOperationWithBlock { () -> Void in
        let img2 = Downloader.downloadImageWithURL(imageURLs[1])
        
        OperationQueue.mainQueue().addOperationWithBlock({
            self.imageView2.image = img2
        })

    }
    
    queue.addOperationWithBlock { () -> Void in
        let img3 = Downloader.downloadImageWithURL(imageURLs[2])
        
        OperationQueue.mainQueue().addOperationWithBlock({
            self.imageView3.image = img3
        })

    }
    
    queue.addOperationWithBlock { () -> Void in
        let img4 = Downloader.downloadImageWithURL(imageURLs[3])
        
        OperationQueue.mainQueue().addOperationWithBlock({
            self.imageView4.image = img4
        })

    }
}

How we can use NSBlockOperation to do the same, but at the same time, giving us more functionalities and options such as setting completion handler. The didClickOnStart method is rewritten like this:

@IBAction func didClickOnStart(sender: AnyObject) {
    
    queue = OperationQueue()
    let operation1 = NSBlockOperation(block: {
        let img1 = Downloader.downloadImageWithURL(imageURLs[0])
        OperationQueue.mainQueue().addOperationWithBlock({
            self.imageView1.image = img1
        })
    })
    
    operation1.completionBlock = {
        print("Operation 1 completed")
    }
    queue.addOperation(operation1)
    
    let operation2 = NSBlockOperation(block: {
        let img2 = Downloader.downloadImageWithURL(imageURLs[1])
        OperationQueue.mainQueue().addOperationWithBlock({
            self.imageView2.image = img2
        })
    })
    
    operation2.completionBlock = {
        print("Operation 2 completed")
    }
    queue.addOperation(operation2)
    
    
    let operation3 = NSBlockOperation(block: {
        let img3 = Downloader.downloadImageWithURL(imageURLs[2])
        OperationQueue.mainQueue().addOperationWithBlock({
            self.imageView3.image = img3
        })
    })
    
    operation3.completionBlock = {
        print("Operation 3 completed")
    }
    queue.addOperation(operation3)
    
    let operation4 = NSBlockOperation(block: {
        let img4 = Downloader.downloadImageWithURL(imageURLs[3])
        OperationQueue.mainQueue().addOperationWithBlock({
            self.imageView4.image = img4
        })
    })
    
    operation4.completionBlock = {
        print("Operation 4 completed")
    }
    queue.addOperation(operation4)
}

For each operation, we create a new instance of NSBlockOperation to encapsulate the task into a block. By using NSBlockOperation, you’re allowed to set the completion handler. Now when the operation is done, the completion handler will be called. For simplicity, we just log a simple message to indicate the operation is done. If you run the demo, you would see something like this in console:

Operation 1 completed
Operation 3 completed
Operation 2 completed
Operation 4 completed

2.2 Canceling Operations

As mentioned before, NSBlockOperation allows you to manage the operations:



operation2.addDependency(operation1)
operation3.addDependency(operation2)

operation1.completionBlock = {
    print("Operation 1 completed, cancelled:\(operation1.cancelled) ")
}
// ...

queue.cancelAllOperations()

General advice

  • One QoS for tasks accessing shared resource
  • Serial queue to access shared resource
  • Avoid Operation dependency cycles
  • Be careful when calling sync()
  • Never call sync() on the current queue
  • Never ever call sync from the main queue
    *

Reference:
Grand Central Dispatch Tutorial for Swift 3(1)
Grand Central Dispatch Tutorial for Swift 3(2)
iOS Concurrency: Getting Started with NSOperation and Dispatch Queues
Apple’s Concurrency Guide
iOS Concurrency repository on Github

Posted in iOS | Tagged , , , , | Leave a comment

RxSwift Scheduler

Where we call subscribeOn() in a chain doesn't really matter when to call it. Where we call observeOn() does matter.

subscribeOn() tells the whole chain which thread to start processing on. We should only call it once per chain. If we call it again lower down the stream it will have no effect.

observeOn() causes all operations which happen below it to be executed on the specified scheduler. We can call it multiple times per stream to move between different threads.

@IBAction func rxSchedulerTest(_ sender: UIButton) {
    print("==UI \(Thread.current)")
    
    Observable.create { (observer: AnyObserver<Int>) -> Disposable in
            print("==Observable \(Thread.current)")
            observer.onNext(1)
            observer.onCompleted()
            return Disposables.create()
        }
        .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
        .map({ (n) -> Int in
            print("==A \(Thread.current)")
            return n + 10
        })
        .observeOn(MainScheduler.instance)
        .map({ (m) -> String in
            print("==B \(Thread.current)")
            return String(m)
        })
        .observeOn(ConcurrentDispatchQueueScheduler(qos: .utility))
        .map({ (text) -> String in
            print("==C \(Thread.current)")
            return "X" + text
        })
        .observeOn(MainScheduler.instance)
        .subscribe(onNext: { (text) in
            print("==D \(Thread.current)")
            print("got \(text)")
        }, onError: nil, onCompleted: nil, onDisposed: nil)
        .addDisposableTo(disposeBag)
}

the output is

==UI <NSThread: 0x6100000748c0>{number = 1, name = main}
==Observable <NSThread: 0x60800007d400>{number = 3, name = (null)}
==A <NSThread: 0x60800007d400>{number = 3, name = (null)}
==B <NSThread: 0x6100000748c0>{number = 1, name = main}
==C <NSThread: 0x60800007d400>{number = 3, name = (null)}
==D <NSThread: 0x6100000748c0>{number = 1, name = main}
got X11

RxSwift Schedulers:

  • ConcurrentDispatchQueueScheduler
  • ConcurrentMainScheduler
  • CurrentThreadScheduler
  • DispatchQueueSchedulerQOS
  • HistoricalScheduler
  • HistoricalSchedulerTimeConverter
  • MainScheduler
  • OperationQueueScheduler
  • SerialDispatchQueueScheduler
  • VirtualTimeConverterType
  • VirtualTimeScheduler

reference: http://cocoadocs.org/docsets/RxSwift/2.6.0/RxSwift/Schedulers.html#/s:C7RxSwift13MainScheduler

Posted in iOS, React | Tagged , , | Leave a comment

How to generate **.cer** and **.p12** file for iOS push notification

Every time I created a new app with push notifications, iOS, I'm sure I've got everything right and then something refused to work. So this time I'm writing it down so I can't possibly get it wrong again.

Generally, we need 2 files for push notification:
* .cer file
* .p12 file

Generate cer file

  1. Apple developer, Identifiers, select the App you want to generate:

    Click Edit:
  2. Go the detail page of the App, enable Push Notifications, click Create Certificate

    It depends which environment you want to create. For AppStore environment, please click the button of Production SSL Certificate.

  3. In next page, press continue, click choose File

  4. Download the certificate and click Done

Generate p12 file:

  1. Open Keychain Access, drag&drop the cer file you just generated above:

Oooops, there is no triangle icon before the App id. That means there is problem when we generated the cer file. Probably we used the wrong certSigningRequest file.
2. Redo the generating cer file, select the correct certSigningRequest file, now we can see the small lovely triangle icon:

Right click the App ID, select Export "Apple Development iOS Push Service: …."
3. Set File Format to Personal Information Exchange(p12), click save:

Set password and click OK

  1. Done

Congratulations, now you have both cer and p12 file

reference:
https://developer.apple.com/library/content/documentation/IDEs/Conceptual/AppDistributionGuide/MaintainingProfiles/MaintainingProfiles.html

Posted in iOS, Mac, Mobile | Tagged , , , | 1 Comment