Opaque return types

2022-04-26

紀錄 Opaque types 的一些資訊。

struct ContentView: View {
    var body: some View {
        Text("hello world")
    }
}
public protocol View {
    /// The type of view representing the body of this view.
    associatedtype Body : View

    /// The content and behavior of the view.
    @ViewBuilder var body: Self.Body { get }
}

此 ContentView 定義 body 的 Read-Only computed properties,getter 回傳 some View,某個符合 View protocol 的型別,稱為 Opaque type。

官網定義:

A function or method with an opaque return type hides its return value’s type information. Instead of providing a concrete type as the function’s return type, the return value is described in terms of the protocols it supports. Hiding type information is useful at boundaries between a module and code that calls into the module, because the underlying type of the return value can remain private. Unlike returning a value whose type is a protocol type, opaque types preserve type identity—the compiler has access to the type information, but clients of the module don’t.

由以上說明可知,Opaque return type 可以隱藏型別資訊。

我們可以藉著以下方法來得知真實的 body 型別資訊。在這個例子中,真實回傳的型別是 VStack<Button<Text>>

struct ContentView: View {
    var body: some View {
        VStack {
            Button("print my type") {
                print(type(of: self.body))
                // VStack<Button<Text>>
            }
        }
    }
}

SwiftUI NavigationLink lazy loading

2022-04-23

紀錄一個 SwiftUI 的坑及解法。

NavigationLink 的 destination 會立即被創建,在還沒有實際按下按鈕之前。 此時若有做些複雜的計算則會被立即執行… (依照 SwiftUI 的思維邏輯應該延後此複雜操作)。

struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink {
                // Create immediately.
                // What if need some expensive computation here...
                Text("detail view")
            } label: {
                Text("show detail view")
            }
        }
    }
}

解法 1 : onAppear

在 destination 出現時才執行複雜的操作。 另外要注意 View 反覆多次出現的情況。

struct ContentView: View {
    var body: some View {
        NavigationView {
                NavigationLink(
                    // Destination created immediately.
                    destination: {
                        Text("Detail View")
                            .onAppear {
                                // do expensive computation at onAppear
                            }
                    },
                    label: { Text("show detail view") }
                )
        }
    }
}

解法 2 : Lazy loading

View 的 body 在被顯示的時候才會執行, 所以可透過另一個 Wrapper View 把 destination 的創建,用 closure 傳入,延後到 Wrapper View 的 body 被執行時。

struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink(
                destination: { LazyView(<...
      

WWDC 2021 Get ready to optimize your App Store product page

2021-06-21

在 WWDC 2021 中,有發佈未來 AppStore 將有的新功能。也許跟 Developer 無直接相關,但從產品推廣的角度上很有幫助。分別為

  • Custom product pages
  • Product page optimization

Custom product pages

除了預設的產品頁之外,可以設計額外的 custom product page,去客製化預覽影片,螢幕截圖,都可以做語言對應。

從搜尋或瀏覽來的使用者,會進到預設產品頁,而從專屬的連結進來的使用者,則會連到客製化的產品頁。

然後可以分析在這樣的情況下各自的

  • Impression
  • Downloads
  • Conversion rate
  • Retention
  • Average proceeds per paying user

最多可以建立 35 個客製化產品頁,而且此流程不需要重新上傳 App,只需要審核產品頁。

Product page optimization

而在預設的產品頁中,可以設定不同組合,或稱 bucket or A/B testing,來優化下載轉換率。

如圖中,右邊的紅色 App Icon 有較高的轉換率,則可以考慮修改產品頁。

能夠變動的部分,有 App Icon,螢幕截圖及預覽影片。

最多可以做出三個 treatments 組合,然後設定各自出現在使用者目前的機率,圖中設定機率為各 10%

App Icon 是重要的使用者體驗,也會是要不要打開 App 的重要關鍵。從 A/B testing 的分眾下載後的 App Icon,會維持著跟當初 AppStore 產品頁組合一致的樣式。

可以指定針對特定語系的 AppStore 產品頁做優化。

要開一組新的 A/B testing 仍然要過審核,但不用上傳新的 binary。

如果要測試 App Icon 的不同組合,則要事先把所有的 Icon 放在 binary 中。最終預設的 App Icon 如果要更換的話,仍然要在下一個新版本的 binary 中更換。

最後,上述的 Custom product pages 以及 Product page optimization 都有支援 App Store Connect API.

能夠開 35 個 custom product page,手動處理會是場災難,善用 API 與自動化才能將效率提到最高。

Reference

Bazel cache behind the scenes

2021-06-09

Bazel did a great help on speedup build by remote cache, let’s see how it works behind the scenes.

Show Bazel logs

Turn on bazel logs, we could take a closer look at how Bazel works, and do some analysis.

Here are some options to turn on the logs.

  • –disk_cache=<a path>: A path to a directory where Bazel can read and write actions and action outputs. If the directory does not exist, it will be created.

  • –execution_log_binary_file=<a path>: Log the executed spawns into this file as delimited Spawn protos.

  • –execution_log_json_file=<a path>: Log the executed spawns into this file as json representation of the delimited Spawn protos.

Let’s try the following command.

bazel build //BazelDemo --disk_cache=./local_cache --execution_log_json_file=./json.log

Based on Bazel document, there are 2 types of cache data.

  • The action cache, which is a map of action hashes to action result metadata.
  • A content-addressable store (CAS) of output files.

So the folder will also have 2 sub-folders, ac and cas.

Take a look at the cas files by command: file local_cache/cas/*/*, it will shows result as

Before dive right in to see what are these, let’s read more about the build process.

Xcode Build Process

From 2018 WWDC - Behind The Scenes of The Xcode Build Process, the xcode build process could roughly split into compiling, processing, linking.

The compiler compiles source code into Mach object file (Mach-O)

The linker links .o files together as final executable output.

Flutter Plugins Ordering Issue and Solution

2020-09-03

About

Flutter plugins 被註冊的順序,會影響到 plugin code 執行的順序,進而可能會造成第三方登入失敗,或無法正確地收到 deep link 的呼叫。 Android 及 iOS 的 Flutter 來自同一個 Engine,所以同樣都有載入順序的問題。 本篇想紀錄問題探詢的過程,以及解決的方法。

Issue Intro

iOS 上,其中一個處理從外部呼叫 App 的 callback function 是 application(_:open:options:)

常見的實作如下:

// sample code
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey: Any] = [:]) -> Bool {
    if FacebookLogin.handle(url) {
        return true // 處理 Facebook 登入
    } else if LineLogin.handle(url) {
        return true // 處理 LINE 登入
    } else if FirebaseDynamicLinks.handle(url) {
        return true // 處理 Firebase Dynamic Links
    } else {
        return AppRouter.handle(url) // 對這個 deeplink url,在 App 內部使用 webview 或使用新的 native 畫面呈現
    }
}

一個呼叫 App 起來的 url,只會屬於一種情況。通常會在處理完登入及特殊的 deeplink (ex: FirebaseDynamicLinks) 之後,才使用 App 內的 Router 來呈現這個 url。 此時 code 的順序有其必要,把呈現 url 放在最後。

而在 Flutter 中,plugin 會實作 FlutterPlugin 的 protocol registerWithRegistrar:registrar。 在其中,plugin 跟 Flutter 使用 addApplicationDelegate: 註冊,聲明自己要處理 UIApplicationDelegate 的呼叫。此時若多個 plugin 都有聲明,則這個順序是如何被決定的呢?

Solve the issue about cannot open iOS simulator in Android Studio

2020-04-22

Start using Flutter in our new project. Record some of the issues and solutions.

One of the weird things that happened recently, is that although iOS simulator is opened, it still cannot be selected as deploy target in Android Studio. The opened iOS simulator not shown in the list.

At this time, try to check the simulator version, with the current selected Xcode version.

xcode-select -p                                                        
/Applications/Xcode-11.3.app/Contents/Developer
// Should be same as opened simulator version as below image.

WWDC 2019 朝聖與充電之旅

2019-10-27

前陣子相當忙碌,也通過了內部的審核程序,WWDC 2019 的參與心得終於發佈在公司的 LINE Engineering Blog 之上了。

感謝公司贊助,回來後各種 Sharing 也相當紮實。

相當精實的會期,每天都在跑 Lab,最後跟總部同事們一起完成了一大張 Sheet 的 Lab Q&A 任務。與各國參加者交流,也多認識了日本與韓國的同事。現在陸續補 Session 的錄影中。

WWDC,期待再見面!

SwiftUI dyld Library not loaded

2019-09-17

紀錄一個踩到的 Apple Known Issue。

因為 SwiftUI 是 iOS 13 以上才支援,所以使用在既有的專案要加上版本的判斷: @available

@available(iOS 13.0, *)
struct ContentView: View {
    var body: some View {
        Text("Hello")
    }
}

即使加上了這個保護,但卻在 iOS 12 的實機遇到了 Crash。

dyld: Library not loaded: /System/Library/Frameworks/SwiftUI.framework/SwiftUI

Reason: image not found

查了以後才知道這是個已列在 Release Notes 的問題。

解法為加上 -weak_framework SwiftUI flag to the Other Linker Flags setting in the Build Settings tab。

https://developer.apple.com/documentation/ios_ipados_release_notes/ios_13_release_notes

Apps containing SwiftUI inside a Swift package might not run on versions of iOS earlier than iOS 13. (53706729) Workaround: When back-deploying to an OS which doesn’t contain the SwiftUI framework, add the -weak_framework SwiftUI flag to the Other Linker Flags setting in the Build Settings tab. See Frameworks and Weak Linking for more information on weak linking a framework. This workaround doesn’t apply when using dynamically linked Swift packages which import SwiftUI.

Hello World

2019-09-03

Blog 連載再開 :p

一直想從 http://cj-lin.logdown.com 搬家出來,作為一個支援 Markdown 且易於使用的 Blog 來說,logdown 在當時的確算是滿順手的,奈何後來已經停止更新了。

接下來就一篇一篇地備份出來吧。

期望這個 Blog 持續紀錄技術路上遇到的點滴,分擔一些大腦的記憶體。

Put the ++ and -- operators back at Swift

2018-01-18

Swift had remove the ++ and -- operators for the reason that it might be confusing to users.

Reference: Proposal from Chris Lattner


extension Int {
    static prefix func --(i: inout Int) -> Int {
        i -= 1
        return i
    }
    static prefix func ++(i: inout Int) -> Int {
        i += 1
        return i
    }
    static postfix func --(i: inout Int) -> Int {
        let n = i
        i -= 1
        return n
    }
    static postfix func ++(i: inout Int) -> Int {
        let n = i
        i += 1
        return n
    }
}

Just saw this extension, now we get to use these handy operators yet again. :p

About typeof in Objective-C

I want to share something about my misunderstanding about typeof.

The story goes from my pull request to mopub repo. I found following code occurs in a block. It seems not using weakSelf for weak-strong dance, but capturing self by the __typeof__(self).

__strong __typeof__(self) strongSelf = weakSelf;

So I create a pull request and change it to

__strong __typeof__(weakSelf) strongSelf = weakSelf;

In fact, my PR had a mistake. The typeof, __typeof and __typeof__ are compile-time flag. Thus self in this block is not retained.

Another thing is that, __typeof__(weakSelf) is not a good pattern. This will carry __weak attribute, thus we need an explicit __strong attribute to overwrite it. Thus using __typeof__(self) is better, to prevent accidentally creating a strongSelf which is actually not.

Use Branch.io to implement App Banner and Deep Link

2016-01-27

這篇網誌主要是說明為何應該使用 Branch 來實作 Smart App Banner 及 Deep Link,以及 Web 端的設定方法。

Smart App Banner Intro

Smart App Banner 能提高網頁到 App 的轉換率,如下方附圖。

按下 banner 動作後

  • 如果沒有安裝 App,跳轉到 AppStore 以便使用者進行下載。
  • 如果已安裝 App,則跳轉打開 App

UIDynamic Animator

Set up physics relating animatable objects and let them run until they resolve to stasis Easily possible to set it up so that stasis never occurs, but that could be performance problem