Intermediate iOS Debugging

1. Stop when exception

1.1 Open Breakpoint navigator (Cmd + 8)
1.2 Add Exception Breakpoint

1.3 Keep default values

1.4 Move breakpoint to User level

2. Edit breakpoint

  • Edit Breakpoint

  • Add Debugger Command:

    We will see the following output:

7.89903245455977
7.890427382096
7.87502883137136
7.87004694731339
  • or Add Log Message

ScreensFromBottom: @screensFromBottom@ Threshold: @screensFromBottomToLoadMoreCats@

We will see the logs in output window:

ScreensFromBottom: 8.5084718344868104 Threshold: 2.5
ScreensFromBottom: 8.5034899504288379 Threshold: 2.5
ScreensFromBottom: 8.499866762023041 Threshold: 2.5
ScreensFromBottom: 8.4966964721679688 Threshold: 2.5
ScreensFromBottom: 8.4948848779650703 Threshold: 2.5
ScreensFromBottom: 8.4921674866607226 Threshold: 2.5
ScreensFromBottom: 8.4899029939070996 Threshold: 2.5
ScreensFromBottom: 8.4880913997042011 Threshold: 2.5

3. Symbolic breakpoint – Condition

For example, check if some specified UIViewController is released properly.

  1. Add Symbolic Breakpoint...

  2. (Optional) Add Condition:

Check if it is UIViewController:

(BOOL)[$arg1 isKindOfClass: isKindOfClass: [UIViewController class]]

Check if it is user's custom CatDetailViewController:

(BOOL)[$arg1 isKindOfClass: (id)NSClassFromString(@"Catstagram.CatDetailViewController")]

3. Symbolic breakpoint – Action

Print the class name when UIView released:

We will see the following output:

<_UIVisualEffectSubview: 0x7f9e6e41aea0; frame = (0 0; 414 64); autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x60000003e300>>
<UIImageView: 0x7f9e70334b60; frame = (0 64; 414 0.333333); userInteractionEnabled = NO; layer = <CALayer: 0x604000030f60>>
<UIView: 0x7f9e7030f210; frame = (0 0; 414 0); layer = <CALayer: 0x6040000312a0>>
<UIImageView: 0x7f9e70334930; frame = (0 0; 414 64); userInteractionEnabled = NO; layer = <CALayer: 0x604000031140>>
<UINavigationTransitionView: 0x7f9e6e41ddc0; frame = (0 0; 414 736); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x60000003c340>>
<UIViewControllerWrapperView: 0x7f9e70004550; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x60000003e8c0>>
<UIView: 0x7f9e6e51c850; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x60c00003b040>>

4. LLDB

LLDB is Apple’s “from the ground up” replacement for GDB, developed in close coordination with the LLVM compilers to bring you state-of-the-art debugging with extensive capabilities in flow control and data inspection. Starting with Xcode 5, all new and preexisting development projects are automatically reconfigured to use LLDB.

reference: http://lldb.llvm.org/lldb-gdb.html

4.1 p/po

4.2 frame variable(fr v)

frame variable is only for printing the contents of variables, no side effect to variable

(lldb) frame var global
(int32_t) global = 5

example:

(lldb) p screensFromBottom
(CGFloat) $R1 = 9.3780370518781133
(lldb) po screensFromBottom
9.37803705187811

(lldb) frame variable screensFromBottom
(CGFloat) screensFromBottom = 9.3780370518781133
(lldb) fr v screensFromBottom
(CGFloat) screensFromBottom = 9.3780370518781133
(lldb) 

4.3 Variable out of scope

Create a variable out of debug session scope by using $:

(lldb) p let $label = cell.titleLabel
(lldb) p print($label.text!)

5. Changing UI when debugging

run CATransaction.flush(), flush pending changing to the UI:

(lldb) p titleLabel.text = "Hello lldb"
(lldb) p CATransaction.flush()

6. Get memory address of return value of a function:

(lldb) register read $rax
rax = 0x000062401232eb99a

Print out the memory in LLDB:

(lldb) po unsafeBitCast(0x000062401232eb99a, to UIImage.self)
<UIImage: 0x000062401232eb99a>, {40, 40}

(lldb)
Posted in iOS, Mobile | Tagged | Leave a comment

Auto Layout

let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false

Interface Builder will automatically set its value to false if the view has constraints defined. But for the views created in code, it defaults to true.

UIView has a property called auto resizing mask, but its type is UIView auto resizing.

var autoresizingMast: UIViewAutoresizing { get set }

Hugging & Compression

Intrinsic size for a view is whatever size will exactly fit its size of wear content.

  • Content Hugging: Don't grow

  • Compression Resistance: Don't shink

Autolayout formula

attribute 1 = multiplier * attribute 2 + constant

https://www.raywenderlich.com/162311/adaptive-layout-tutorial-ios-11-getting-started

Xcode provides two size classes: Regular and Compact. Although they are related to the physical dimensions of a view, they also represent the semantic size of the view.

The following table shows how the size classes apply to the different devices and orientations:

AutoLayout in TableView

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 140

When you set the rowHeight as UITableViewAutomaticDimension, the table view is told to use the Auto Layout constraints and the contents of its cells to determine each cell’s height.

In order for the table view to do this, you must also provide an estimatedRowHeight. In this case, 140 is just an arbitrary value that works well in this particular instance. For your own projects, you should pick a value that better conforms to the type of data that you’ll be displaying.

Posted in iOS | Tagged | Leave a comment

Key-Value Observing

Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects. You can use key-value observing with a Swift class, as long as the class inherits from the NSObject class. You can use these two steps to implement key-value observing in Swift.

  1. Add the dynamic modifier and @objc attribute to any property you want to observe. For more information on dynamic, see Requiring Dynamic Dispatch.
class MyObjectToObserve: NSObject {
    @objc dynamic var myDate = NSDate()
    func updateDate() {
        myDate = NSDate()
    }
}
  1. Create an observer for the key path and call the observe(_:options:changeHandler) method like description on Apple doc. Or create a observer on observable(MyObjectToObserve) directly
var myContext = 0

class MyObserver: NSObject {
    @objc var objectToObserve: MyObjectToObserve
    var observation: NSKeyValueObservation?
    
    init(object: MyObjectToObserve) {
        objectToObserve = object
        super.init()
        objectToObserve.addObserver(self, forKeyPath: "myDate", options: .new, context: &myContext)
    }
    deinit {
        objectToObserve.removeObserver(self, forKeyPath: "myDate")
    }
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        //print("here , \(keyPath), \(object), \(change?[NSKeyValueChangeKey.newKey])")
        guard context == &myContext else { return }
        if let value = change?[NSKeyValueChangeKey.newKey] {
            print("Observation,  myDate changed: \(value)")
        }
    }
}

  1. Verifiy observer:
let observed = MyObjectToObserve()
let observer = MyObserver(object: observed)

observed.updateDate()
sleep(3)
observed.updateDate()

print("finished")

You will see output in Xcode debug area:

Observation:  myDate changed: 2018-01-30 18:15:41 +0000
Observation:  myDate changed: 2018-01-30 18:15:44 +0000
finished
Posted in iOS, Mobile | Tagged | 1 Comment

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 , | 4 Comments

CoreLocation in iOS

CoreLocation in iOS11

Posted in iOS | Tagged | Leave a comment